mirror of
https://github.com/SigNoz/signoz.git
synced 2026-02-07 10:22:12 +00:00
Compare commits
2 Commits
develop
...
fixProduce
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf74ac7b5e | ||
|
|
55a4056aa5 |
1
.github/workflows/build.yaml
vendored
1
.github/workflows/build.yaml
vendored
@@ -3,6 +3,7 @@ name: build-pipeline
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
- develop
|
||||||
- main
|
- main
|
||||||
- release/v*
|
- release/v*
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@@ -3,7 +3,7 @@ name: "Update PR labels and Block PR until related docs are shipped for the feat
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- develop
|
||||||
types: [opened, edited, labeled, unlabeled]
|
types: [opened, edited, labeled, unlabeled]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
2
.github/workflows/e2e-k3s.yaml
vendored
2
.github/workflows/e2e-k3s.yaml
vendored
@@ -42,7 +42,7 @@ jobs:
|
|||||||
kubectl create ns sample-application
|
kubectl create ns sample-application
|
||||||
|
|
||||||
# apply hotrod k8s manifest file
|
# apply hotrod k8s manifest file
|
||||||
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/main/sample-apps/hotrod/hotrod.yaml
|
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/develop/sample-apps/hotrod/hotrod.yaml
|
||||||
|
|
||||||
# wait for all deployments in sample-application namespace to be READY
|
# wait for all deployments in sample-application namespace to be READY
|
||||||
kubectl -n sample-application get deploy --output name | xargs -r -n1 -t kubectl -n sample-application rollout status --timeout=300s
|
kubectl -n sample-application get deploy --output name | xargs -r -n1 -t kubectl -n sample-application rollout status --timeout=300s
|
||||||
|
|||||||
5
.github/workflows/jest-coverage-changes.yml
vendored
5
.github/workflows/jest-coverage-changes.yml
vendored
@@ -2,8 +2,7 @@ name: Jest Coverage - changed files
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches: develop
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -12,7 +11,7 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: "refs/heads/main"
|
ref: "refs/heads/develop"
|
||||||
token: ${{ secrets.GITHUB_TOKEN }} # Provide the GitHub token for authentication
|
token: ${{ secrets.GITHUB_TOKEN }} # Provide the GitHub token for authentication
|
||||||
|
|
||||||
- name: Fetch branch
|
- name: Fetch branch
|
||||||
|
|||||||
1
.github/workflows/push.yaml
vendored
1
.github/workflows/push.yaml
vendored
@@ -4,6 +4,7 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- develop
|
||||||
tags:
|
tags:
|
||||||
- v*
|
- v*
|
||||||
|
|
||||||
|
|||||||
1
.github/workflows/sonar.yml
vendored
1
.github/workflows/sonar.yml
vendored
@@ -3,6 +3,7 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- develop
|
||||||
paths:
|
paths:
|
||||||
- 'frontend/**'
|
- 'frontend/**'
|
||||||
defaults:
|
defaults:
|
||||||
|
|||||||
6
.github/workflows/staging-deployment.yaml
vendored
6
.github/workflows/staging-deployment.yaml
vendored
@@ -1,12 +1,12 @@
|
|||||||
name: staging-deployment
|
name: staging-deployment
|
||||||
# Trigger deployment only on push to main branch
|
# Trigger deployment only on push to develop branch
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- develop
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy latest main branch to staging
|
name: Deploy latest develop branch to staging
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: staging
|
environment: staging
|
||||||
permissions:
|
permissions:
|
||||||
|
|||||||
2
.github/workflows/testing-deployment.yaml
vendored
2
.github/workflows/testing-deployment.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
git add .
|
git add .
|
||||||
git stash push -m "stashed on $(date --iso-8601=seconds)"
|
git stash push -m "stashed on $(date --iso-8601=seconds)"
|
||||||
git fetch origin
|
git fetch origin
|
||||||
git checkout main
|
git checkout develop
|
||||||
git pull
|
git pull
|
||||||
# This is added to include the scenerio when new commit in PR is force-pushed
|
# This is added to include the scenerio when new commit in PR is force-pushed
|
||||||
git branch -D ${GITHUB_BRANCH}
|
git branch -D ${GITHUB_BRANCH}
|
||||||
|
|||||||
@@ -339,7 +339,7 @@ to make SigNoz UI available at [localhost:3301](http://localhost:3301)
|
|||||||
**5.1.1 To install the HotROD sample app:**
|
**5.1.1 To install the HotROD sample app:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-install.sh \
|
curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod-install.sh \
|
||||||
| HELM_RELEASE=my-release SIGNOZ_NAMESPACE=platform bash
|
| HELM_RELEASE=my-release SIGNOZ_NAMESPACE=platform bash
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -362,7 +362,7 @@ kubectl -n sample-application run strzal --image=djbingham/curl \
|
|||||||
**5.1.4 To delete the HotROD sample app:**
|
**5.1.4 To delete the HotROD sample app:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -sL https://github.com/SigNoz/signoz/raw/main/sample-apps/hotrod/hotrod-delete.sh \
|
curl -sL https://github.com/SigNoz/signoz/raw/develop/sample-apps/hotrod/hotrod-delete.sh \
|
||||||
| HOTROD_NAMESPACE=sample-application bash
|
| HOTROD_NAMESPACE=sample-application bash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -8,7 +8,6 @@ BUILD_HASH ?= $(shell git rev-parse --short HEAD)
|
|||||||
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||||
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
||||||
ZEUS_URL ?= https://api.signoz.cloud
|
|
||||||
DEV_BUILD ?= "" # set to any non-empty value to enable dev build
|
DEV_BUILD ?= "" # set to any non-empty value to enable dev build
|
||||||
|
|
||||||
# Internal variables or constants.
|
# Internal variables or constants.
|
||||||
@@ -34,9 +33,8 @@ buildHash=${PACKAGE}/pkg/query-service/version.buildHash
|
|||||||
buildTime=${PACKAGE}/pkg/query-service/version.buildTime
|
buildTime=${PACKAGE}/pkg/query-service/version.buildTime
|
||||||
gitBranch=${PACKAGE}/pkg/query-service/version.gitBranch
|
gitBranch=${PACKAGE}/pkg/query-service/version.gitBranch
|
||||||
licenseSignozIo=${PACKAGE}/ee/query-service/constants.LicenseSignozIo
|
licenseSignozIo=${PACKAGE}/ee/query-service/constants.LicenseSignozIo
|
||||||
zeusURL=${PACKAGE}/ee/query-service/constants.ZeusURL
|
|
||||||
|
|
||||||
LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildVersion}=${BUILD_VERSION} -X ${gitBranch}=${BUILD_BRANCH} -X ${zeusURL}=${ZEUS_URL}
|
LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildVersion}=${BUILD_VERSION} -X ${gitBranch}=${BUILD_BRANCH}
|
||||||
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
||||||
|
|
||||||
all: build-push-frontend build-push-query-service
|
all: build-push-frontend build-push-query-service
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ from the HotROD application, you should see the data generated from hotrod in Si
|
|||||||
```sh
|
```sh
|
||||||
kubectl create ns sample-application
|
kubectl create ns sample-application
|
||||||
|
|
||||||
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/main/sample-apps/hotrod/hotrod.yaml
|
kubectl -n sample-application apply -f https://raw.githubusercontent.com/SigNoz/signoz/develop/sample-apps/hotrod/hotrod.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
To generate load:
|
To generate load:
|
||||||
|
|||||||
@@ -146,12 +146,11 @@ services:
|
|||||||
condition: on-failure
|
condition: on-failure
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:0.64.0
|
image: signoz/query-service:0.56.0
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
"--use-logs-new-schema=true",
|
"--use-logs-new-schema=true"
|
||||||
"--use-trace-new-schema=true"
|
|
||||||
]
|
]
|
||||||
# ports:
|
# ports:
|
||||||
# - "6060:6060" # pprof port
|
# - "6060:6060" # pprof port
|
||||||
@@ -187,7 +186,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:0.64.0
|
image: signoz/frontend:0.56.0
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
@@ -200,7 +199,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:0.111.16
|
image: signoz/signoz-otel-collector:0.111.5
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
@@ -238,15 +237,13 @@ services:
|
|||||||
- query-service
|
- query-service
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:0.111.16
|
image: signoz/signoz-schema-migrator:0.111.5
|
||||||
deploy:
|
deploy:
|
||||||
restart_policy:
|
restart_policy:
|
||||||
condition: on-failure
|
condition: on-failure
|
||||||
delay: 5s
|
delay: 5s
|
||||||
command:
|
command:
|
||||||
- "sync"
|
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
- "--up="
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- clickhouse
|
- clickhouse
|
||||||
# - clickhouse-2
|
# - clickhouse-2
|
||||||
|
|||||||
@@ -66,6 +66,28 @@ processors:
|
|||||||
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
|
# Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels.
|
||||||
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
|
detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure.
|
||||||
timeout: 2s
|
timeout: 2s
|
||||||
|
signozspanmetrics/cumulative:
|
||||||
|
metrics_exporter: clickhousemetricswrite
|
||||||
|
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
||||||
|
dimensions_cache_size: 100000
|
||||||
|
dimensions:
|
||||||
|
- name: service.namespace
|
||||||
|
default: default
|
||||||
|
- name: deployment.environment
|
||||||
|
default: default
|
||||||
|
# This is added to ensure the uniqueness of the timeseries
|
||||||
|
# Otherwise, identical timeseries produced by multiple replicas of
|
||||||
|
# collectors result in incorrect APM metrics
|
||||||
|
- name: signoz.collector.id
|
||||||
|
- name: service.version
|
||||||
|
- name: browser.platform
|
||||||
|
- name: browser.mobile
|
||||||
|
- name: k8s.cluster.name
|
||||||
|
- name: k8s.node.name
|
||||||
|
- name: k8s.namespace.name
|
||||||
|
- name: host.name
|
||||||
|
- name: host.type
|
||||||
|
- name: container.name
|
||||||
# memory_limiter:
|
# memory_limiter:
|
||||||
# # 80% of maximum memory up to 2G
|
# # 80% of maximum memory up to 2G
|
||||||
# limit_mib: 1500
|
# limit_mib: 1500
|
||||||
@@ -110,15 +132,12 @@ exporters:
|
|||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/signoz_traces
|
||||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
use_new_schema: true
|
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
enabled: true
|
enabled: true
|
||||||
clickhousemetricswrite/prometheus:
|
clickhousemetricswrite/prometheus:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
clickhousemetricswritev2:
|
|
||||||
dsn: tcp://clickhouse:9000/signoz_metrics
|
|
||||||
# logging: {}
|
# logging: {}
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/signoz_logs
|
||||||
@@ -142,20 +161,20 @@ service:
|
|||||||
pipelines:
|
pipelines:
|
||||||
traces:
|
traces:
|
||||||
receivers: [jaeger, otlp]
|
receivers: [jaeger, otlp]
|
||||||
processors: [signozspanmetrics/delta, batch]
|
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
|
||||||
exporters: [clickhousetraces]
|
exporters: [clickhousetraces]
|
||||||
metrics:
|
metrics:
|
||||||
receivers: [otlp]
|
receivers: [otlp]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite]
|
||||||
metrics/hostmetrics:
|
metrics/generic:
|
||||||
receivers: [hostmetrics]
|
receivers: [hostmetrics]
|
||||||
processors: [resourcedetection, batch]
|
processors: [resourcedetection, batch]
|
||||||
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite]
|
||||||
metrics/prometheus:
|
metrics/prometheus:
|
||||||
receivers: [prometheus]
|
receivers: [prometheus]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [clickhousemetricswrite/prometheus, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite/prometheus]
|
||||||
logs:
|
logs:
|
||||||
receivers: [otlp, tcplog/docker]
|
receivers: [otlp, tcplog/docker]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
|
|||||||
@@ -69,12 +69,10 @@ services:
|
|||||||
- --storage.path=/data
|
- --storage.path=/data
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "sync"
|
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
- "--up="
|
|
||||||
depends_on:
|
depends_on:
|
||||||
clickhouse:
|
clickhouse:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -86,7 +84,7 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
otel-collector:
|
otel-collector:
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
image: signoz/signoz-otel-collector:0.111.16
|
image: signoz/signoz-otel-collector:0.111.5
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"--config=/etc/otel-collector-config.yaml",
|
"--config=/etc/otel-collector-config.yaml",
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ services:
|
|||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
"--use-logs-new-schema=true",
|
"--use-logs-new-schema=true"
|
||||||
"--use-trace-new-schema=true"
|
|
||||||
]
|
]
|
||||||
ports:
|
ports:
|
||||||
- "6060:6060"
|
- "6060:6060"
|
||||||
|
|||||||
@@ -162,13 +162,12 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.64.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
"--use-logs-new-schema=true",
|
"--use-logs-new-schema=true"
|
||||||
"--use-trace-new-schema=true"
|
|
||||||
]
|
]
|
||||||
# ports:
|
# ports:
|
||||||
# - "6060:6060" # pprof port
|
# - "6060:6060" # pprof port
|
||||||
@@ -202,7 +201,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.64.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -214,7 +213,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector-migrator-sync:
|
otel-collector-migrator-sync:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator-sync
|
container_name: otel-migrator-sync
|
||||||
command:
|
command:
|
||||||
- "sync"
|
- "sync"
|
||||||
@@ -229,7 +228,7 @@ services:
|
|||||||
# condition: service_healthy
|
# condition: service_healthy
|
||||||
|
|
||||||
otel-collector-migrator-async:
|
otel-collector-migrator-async:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator-async
|
container_name: otel-migrator-async
|
||||||
command:
|
command:
|
||||||
- "async"
|
- "async"
|
||||||
@@ -246,7 +245,7 @@ services:
|
|||||||
# condition: service_healthy
|
# condition: service_healthy
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -167,14 +167,13 @@ services:
|
|||||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||||
|
|
||||||
query-service:
|
query-service:
|
||||||
image: signoz/query-service:${DOCKER_TAG:-0.64.0}
|
image: signoz/query-service:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-query-service
|
container_name: signoz-query-service
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
"-config=/root/config/prometheus.yml",
|
"-config=/root/config/prometheus.yml",
|
||||||
"-gateway-url=https://api.staging.signoz.cloud",
|
"-gateway-url=https://api.staging.signoz.cloud",
|
||||||
"--use-logs-new-schema=true",
|
"--use-logs-new-schema=true"
|
||||||
"--use-trace-new-schema=true"
|
|
||||||
]
|
]
|
||||||
# ports:
|
# ports:
|
||||||
# - "6060:6060" # pprof port
|
# - "6060:6060" # pprof port
|
||||||
@@ -209,7 +208,7 @@ services:
|
|||||||
<<: *db-depend
|
<<: *db-depend
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
image: signoz/frontend:${DOCKER_TAG:-0.64.0}
|
image: signoz/frontend:${DOCKER_TAG:-0.56.0}
|
||||||
container_name: signoz-frontend
|
container_name: signoz-frontend
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -221,7 +220,7 @@ services:
|
|||||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
otel-collector-migrator:
|
otel-collector-migrator:
|
||||||
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: otel-migrator
|
container_name: otel-migrator
|
||||||
command:
|
command:
|
||||||
- "--dsn=tcp://clickhouse:9000"
|
- "--dsn=tcp://clickhouse:9000"
|
||||||
@@ -235,7 +234,7 @@ services:
|
|||||||
|
|
||||||
|
|
||||||
otel-collector:
|
otel-collector:
|
||||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.16}
|
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.111.5}
|
||||||
container_name: signoz-otel-collector
|
container_name: signoz-otel-collector
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -57,11 +57,35 @@ receivers:
|
|||||||
labels:
|
labels:
|
||||||
job_name: otel-collector
|
job_name: otel-collector
|
||||||
|
|
||||||
|
|
||||||
processors:
|
processors:
|
||||||
batch:
|
batch:
|
||||||
send_batch_size: 10000
|
send_batch_size: 10000
|
||||||
send_batch_max_size: 11000
|
send_batch_max_size: 11000
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
|
signozspanmetrics/cumulative:
|
||||||
|
metrics_exporter: clickhousemetricswrite
|
||||||
|
metrics_flush_interval: 60s
|
||||||
|
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
||||||
|
dimensions_cache_size: 100000
|
||||||
|
dimensions:
|
||||||
|
- name: service.namespace
|
||||||
|
default: default
|
||||||
|
- name: deployment.environment
|
||||||
|
default: default
|
||||||
|
# This is added to ensure the uniqueness of the timeseries
|
||||||
|
# Otherwise, identical timeseries produced by multiple replicas of
|
||||||
|
# collectors result in incorrect APM metrics
|
||||||
|
- name: signoz.collector.id
|
||||||
|
- name: service.version
|
||||||
|
- name: browser.platform
|
||||||
|
- name: browser.mobile
|
||||||
|
- name: k8s.cluster.name
|
||||||
|
- name: k8s.node.name
|
||||||
|
- name: k8s.namespace.name
|
||||||
|
- name: host.name
|
||||||
|
- name: host.type
|
||||||
|
- name: container.name
|
||||||
# memory_limiter:
|
# memory_limiter:
|
||||||
# # 80% of maximum memory up to 2G
|
# # 80% of maximum memory up to 2G
|
||||||
# limit_mib: 1500
|
# limit_mib: 1500
|
||||||
@@ -119,15 +143,12 @@ exporters:
|
|||||||
clickhousetraces:
|
clickhousetraces:
|
||||||
datasource: tcp://clickhouse:9000/signoz_traces
|
datasource: tcp://clickhouse:9000/signoz_traces
|
||||||
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
low_cardinal_exception_grouping: ${env:LOW_CARDINAL_EXCEPTION_GROUPING}
|
||||||
use_new_schema: true
|
|
||||||
clickhousemetricswrite:
|
clickhousemetricswrite:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
resource_to_telemetry_conversion:
|
resource_to_telemetry_conversion:
|
||||||
enabled: true
|
enabled: true
|
||||||
clickhousemetricswrite/prometheus:
|
clickhousemetricswrite/prometheus:
|
||||||
endpoint: tcp://clickhouse:9000/signoz_metrics
|
endpoint: tcp://clickhouse:9000/signoz_metrics
|
||||||
clickhousemetricswritev2:
|
|
||||||
dsn: tcp://clickhouse:9000/signoz_metrics
|
|
||||||
clickhouselogsexporter:
|
clickhouselogsexporter:
|
||||||
dsn: tcp://clickhouse:9000/signoz_logs
|
dsn: tcp://clickhouse:9000/signoz_logs
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
@@ -147,20 +168,20 @@ service:
|
|||||||
pipelines:
|
pipelines:
|
||||||
traces:
|
traces:
|
||||||
receivers: [jaeger, otlp]
|
receivers: [jaeger, otlp]
|
||||||
processors: [signozspanmetrics/delta, batch]
|
processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch]
|
||||||
exporters: [clickhousetraces]
|
exporters: [clickhousetraces]
|
||||||
metrics:
|
metrics:
|
||||||
receivers: [otlp]
|
receivers: [otlp]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite]
|
||||||
metrics/hostmetrics:
|
metrics/generic:
|
||||||
receivers: [hostmetrics]
|
receivers: [hostmetrics]
|
||||||
processors: [resourcedetection, batch]
|
processors: [resourcedetection, batch]
|
||||||
exporters: [clickhousemetricswrite, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite]
|
||||||
metrics/prometheus:
|
metrics/prometheus:
|
||||||
receivers: [prometheus]
|
receivers: [prometheus]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
exporters: [clickhousemetricswrite/prometheus, clickhousemetricswritev2]
|
exporters: [clickhousemetricswrite/prometheus]
|
||||||
logs:
|
logs:
|
||||||
receivers: [otlp, tcplog/docker]
|
receivers: [otlp, tcplog/docker]
|
||||||
processors: [batch]
|
processors: [batch]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# use a minimal alpine image
|
# use a minimal alpine image
|
||||||
FROM alpine:3.20.3
|
FROM alpine:3.18.6
|
||||||
|
|
||||||
# Add Maintainer Info
|
# Add Maintainer Info
|
||||||
LABEL maintainer="signoz"
|
LABEL maintainer="signoz"
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ type APIHandlerOptions struct {
|
|||||||
Cache cache.Cache
|
Cache cache.Cache
|
||||||
Gateway *httputil.ReverseProxy
|
Gateway *httputil.ReverseProxy
|
||||||
// Querier Influx Interval
|
// Querier Influx Interval
|
||||||
FluxInterval time.Duration
|
FluxInterval time.Duration
|
||||||
UseLogsNewSchema bool
|
UseLogsNewSchema bool
|
||||||
UseTraceNewSchema bool
|
UseLicensesV3 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type APIHandler struct {
|
type APIHandler struct {
|
||||||
@@ -66,7 +66,7 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
|||||||
Cache: opts.Cache,
|
Cache: opts.Cache,
|
||||||
FluxInterval: opts.FluxInterval,
|
FluxInterval: opts.FluxInterval,
|
||||||
UseLogsNewSchema: opts.UseLogsNewSchema,
|
UseLogsNewSchema: opts.UseLogsNewSchema,
|
||||||
UseTraceNewSchema: opts.UseTraceNewSchema,
|
UseLicensesV3: opts.UseLicensesV3,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -181,16 +181,23 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
|||||||
Methods(http.MethodGet)
|
Methods(http.MethodGet)
|
||||||
|
|
||||||
// v3
|
// v3
|
||||||
router.HandleFunc("/api/v3/licenses", am.ViewAccess(ah.listLicensesV3)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v3/licenses",
|
||||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.applyLicenseV3)).Methods(http.MethodPost)
|
am.ViewAccess(ah.listLicensesV3)).
|
||||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.refreshLicensesV3)).Methods(http.MethodPut)
|
Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(ah.getActiveLicenseV3)).Methods(http.MethodGet)
|
|
||||||
|
router.HandleFunc("/api/v3/licenses",
|
||||||
|
am.AdminAccess(ah.applyLicenseV3)).
|
||||||
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/api/v3/licenses",
|
||||||
|
am.AdminAccess(ah.refreshLicensesV3)).
|
||||||
|
Methods(http.MethodPut)
|
||||||
|
|
||||||
// v4
|
// v4
|
||||||
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
||||||
|
|
||||||
// Gateway
|
// Gateway
|
||||||
router.PathPrefix(gateway.RoutePrefix).HandlerFunc(am.EditAccess(ah.ServeGatewayHTTP))
|
router.PathPrefix(gateway.RoutePrefix).HandlerFunc(am.AdminAccess(ah.ServeGatewayHTTP))
|
||||||
|
|
||||||
ah.APIHandler.RegisterRoutes(router, am)
|
ah.APIHandler.RegisterRoutes(router, am)
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
|
|||||||
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
license, apiError := ah.LM().ActivateV3(r.Context(), l.Key)
|
license, apiError := ah.LM().Activate(r.Context(), l.Key)
|
||||||
if apiError != nil {
|
if apiError != nil {
|
||||||
RespondError(w, apiError, nil)
|
RespondError(w, apiError, nil)
|
||||||
return
|
return
|
||||||
@@ -115,23 +115,6 @@ func (ah *APIHandler) listLicensesV3(w http.ResponseWriter, r *http.Request) {
|
|||||||
ah.Respond(w, convertLicenseV3ToListLicenseResponse(licenses))
|
ah.Respond(w, convertLicenseV3ToListLicenseResponse(licenses))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getActiveLicenseV3(w http.ResponseWriter, r *http.Request) {
|
|
||||||
activeLicense, err := ah.LM().GetRepo().GetActiveLicenseV3(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// return 404 not found if there is no active license
|
|
||||||
if activeLicense == nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no active license found")}, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO deprecate this when we move away from key for stripe
|
|
||||||
activeLicense.Data["key"] = activeLicense.Key
|
|
||||||
render.Success(w, http.StatusOK, activeLicense.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function is called by zeus when inserting licenses in the query-service
|
// this function is called by zeus when inserting licenses in the query-service
|
||||||
func (ah *APIHandler) applyLicenseV3(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) applyLicenseV3(w http.ResponseWriter, r *http.Request) {
|
||||||
var licenseKey ApplyLicenseRequest
|
var licenseKey ApplyLicenseRequest
|
||||||
@@ -235,10 +218,6 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
|||||||
func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
||||||
licensesV2 := []model.License{}
|
licensesV2 := []model.License{}
|
||||||
for _, l := range licenses {
|
for _, l := range licenses {
|
||||||
planKeyFromPlanName, ok := model.MapOldPlanKeyToNewPlanName[l.PlanName]
|
|
||||||
if !ok {
|
|
||||||
planKeyFromPlanName = model.Basic
|
|
||||||
}
|
|
||||||
licenseV2 := model.License{
|
licenseV2 := model.License{
|
||||||
Key: l.Key,
|
Key: l.Key,
|
||||||
ActivationId: "",
|
ActivationId: "",
|
||||||
@@ -247,7 +226,7 @@ func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
|||||||
ValidationMessage: "",
|
ValidationMessage: "",
|
||||||
IsCurrent: l.IsCurrent,
|
IsCurrent: l.IsCurrent,
|
||||||
LicensePlan: model.LicensePlan{
|
LicensePlan: model.LicensePlan{
|
||||||
PlanKey: planKeyFromPlanName,
|
PlanKey: l.PlanName,
|
||||||
ValidFrom: l.ValidFrom,
|
ValidFrom: l.ValidFrom,
|
||||||
ValidUntil: l.ValidUntil,
|
ValidUntil: l.ValidUntil,
|
||||||
Status: l.Status},
|
Status: l.Status},
|
||||||
@@ -258,12 +237,24 @@ func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
||||||
licensesV3, apierr := ah.LM().GetLicensesV3(r.Context())
|
|
||||||
if apierr != nil {
|
var licenses []model.License
|
||||||
RespondError(w, apierr, nil)
|
|
||||||
return
|
if ah.UseLicensesV3 {
|
||||||
|
licensesV3, err := ah.LM().GetLicensesV3(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
RespondError(w, err, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
licenses = convertLicenseV3ToLicenseV2(licensesV3)
|
||||||
|
} else {
|
||||||
|
_licenses, apiError := ah.LM().GetLicenses(r.Context())
|
||||||
|
if apiError != nil {
|
||||||
|
RespondError(w, apiError, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
licenses = _licenses
|
||||||
}
|
}
|
||||||
licenses := convertLicenseV3ToLicenseV2(licensesV3)
|
|
||||||
|
|
||||||
resp := model.Licenses{
|
resp := model.Licenses{
|
||||||
TrialStart: -1,
|
TrialStart: -1,
|
||||||
|
|||||||
@@ -26,9 +26,8 @@ func NewDataConnector(
|
|||||||
dialTimeout time.Duration,
|
dialTimeout time.Duration,
|
||||||
cluster string,
|
cluster string,
|
||||||
useLogsNewSchema bool,
|
useLogsNewSchema bool,
|
||||||
useTraceNewSchema bool,
|
|
||||||
) *ClickhouseReader {
|
) *ClickhouseReader {
|
||||||
ch := basechr.NewReader(localDB, promConfigPath, lm, maxIdleConns, maxOpenConns, dialTimeout, cluster, useLogsNewSchema, useTraceNewSchema)
|
ch := basechr.NewReader(localDB, promConfigPath, lm, maxIdleConns, maxOpenConns, dialTimeout, cluster, useLogsNewSchema)
|
||||||
return &ClickhouseReader{
|
return &ClickhouseReader{
|
||||||
conn: ch.GetConn(),
|
conn: ch.GetConn(),
|
||||||
appdb: localDB,
|
appdb: localDB,
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ type ServerOptions struct {
|
|||||||
Cluster string
|
Cluster string
|
||||||
GatewayUrl string
|
GatewayUrl string
|
||||||
UseLogsNewSchema bool
|
UseLogsNewSchema bool
|
||||||
UseTraceNewSchema bool
|
UseLicensesV3 bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server runs HTTP api service
|
// Server runs HTTP api service
|
||||||
@@ -134,7 +134,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initiate license manager
|
// initiate license manager
|
||||||
lm, err := licensepkg.StartManager("sqlite", localDB)
|
lm, err := licensepkg.StartManager("sqlite", localDB, serverOptions.UseLicensesV3)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
serverOptions.DialTimeout,
|
serverOptions.DialTimeout,
|
||||||
serverOptions.Cluster,
|
serverOptions.Cluster,
|
||||||
serverOptions.UseLogsNewSchema,
|
serverOptions.UseLogsNewSchema,
|
||||||
serverOptions.UseTraceNewSchema,
|
|
||||||
)
|
)
|
||||||
go qb.Start(readerReady)
|
go qb.Start(readerReady)
|
||||||
reader = qb
|
reader = qb
|
||||||
@@ -190,7 +189,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
serverOptions.DisableRules,
|
serverOptions.DisableRules,
|
||||||
lm,
|
lm,
|
||||||
serverOptions.UseLogsNewSchema,
|
serverOptions.UseLogsNewSchema,
|
||||||
serverOptions.UseTraceNewSchema,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -272,7 +270,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
FluxInterval: fluxInterval,
|
FluxInterval: fluxInterval,
|
||||||
Gateway: gatewayProxy,
|
Gateway: gatewayProxy,
|
||||||
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
UseLogsNewSchema: serverOptions.UseLogsNewSchema,
|
||||||
UseTraceNewSchema: serverOptions.UseTraceNewSchema,
|
UseLicensesV3: serverOptions.UseLicensesV3,
|
||||||
}
|
}
|
||||||
|
|
||||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||||
@@ -315,10 +313,10 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
|
|||||||
|
|
||||||
r := baseapp.NewRouter()
|
r := baseapp.NewRouter()
|
||||||
|
|
||||||
|
r.Use(baseapp.LogCommentEnricher)
|
||||||
r.Use(setTimeoutMiddleware)
|
r.Use(setTimeoutMiddleware)
|
||||||
r.Use(s.analyticsMiddleware)
|
r.Use(s.analyticsMiddleware)
|
||||||
r.Use(loggingMiddlewarePrivate)
|
r.Use(loggingMiddlewarePrivate)
|
||||||
r.Use(baseapp.LogCommentEnricher)
|
|
||||||
|
|
||||||
apiHandler.RegisterPrivateRoutes(r)
|
apiHandler.RegisterPrivateRoutes(r)
|
||||||
|
|
||||||
@@ -358,10 +356,10 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e
|
|||||||
}
|
}
|
||||||
am := baseapp.NewAuthMiddleware(getUserFromRequest)
|
am := baseapp.NewAuthMiddleware(getUserFromRequest)
|
||||||
|
|
||||||
|
r.Use(baseapp.LogCommentEnricher)
|
||||||
r.Use(setTimeoutMiddleware)
|
r.Use(setTimeoutMiddleware)
|
||||||
r.Use(s.analyticsMiddleware)
|
r.Use(s.analyticsMiddleware)
|
||||||
r.Use(loggingMiddleware)
|
r.Use(loggingMiddleware)
|
||||||
r.Use(baseapp.LogCommentEnricher)
|
|
||||||
|
|
||||||
apiHandler.RegisterRoutes(r, am)
|
apiHandler.RegisterRoutes(r, am)
|
||||||
apiHandler.RegisterLogsRoutes(r, am)
|
apiHandler.RegisterLogsRoutes(r, am)
|
||||||
@@ -739,8 +737,7 @@ func makeRulesManager(
|
|||||||
cache cache.Cache,
|
cache cache.Cache,
|
||||||
disableRules bool,
|
disableRules bool,
|
||||||
fm baseint.FeatureLookup,
|
fm baseint.FeatureLookup,
|
||||||
useLogsNewSchema bool,
|
useLogsNewSchema bool) (*baserules.Manager, error) {
|
||||||
useTraceNewSchema bool) (*baserules.Manager, error) {
|
|
||||||
|
|
||||||
// create engine
|
// create engine
|
||||||
pqle, err := pqle.FromConfigPath(promConfigPath)
|
pqle, err := pqle.FromConfigPath(promConfigPath)
|
||||||
@@ -770,9 +767,8 @@ func makeRulesManager(
|
|||||||
EvalDelay: baseconst.GetEvalDelay(),
|
EvalDelay: baseconst.GetEvalDelay(),
|
||||||
|
|
||||||
PrepareTaskFunc: rules.PrepareTaskFunc,
|
PrepareTaskFunc: rules.PrepareTaskFunc,
|
||||||
UseLogsNewSchema: useLogsNewSchema,
|
|
||||||
UseTraceNewSchema: useTraceNewSchema,
|
|
||||||
PrepareTestRuleFunc: rules.TestNotification,
|
PrepareTestRuleFunc: rules.TestNotification,
|
||||||
|
UseLogsNewSchema: useLogsNewSchema,
|
||||||
}
|
}
|
||||||
|
|
||||||
// create Manager
|
// create Manager
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
|||||||
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
var SaasSegmentKey = GetOrDefaultEnv("SIGNOZ_SAAS_SEGMENT_KEY", "")
|
||||||
var FetchFeatures = GetOrDefaultEnv("FETCH_FEATURES", "false")
|
var FetchFeatures = GetOrDefaultEnv("FETCH_FEATURES", "false")
|
||||||
var ZeusFeaturesURL = GetOrDefaultEnv("ZEUS_FEATURES_URL", "ZeusFeaturesURL")
|
var ZeusFeaturesURL = GetOrDefaultEnv("ZEUS_FEATURES_URL", "ZeusFeaturesURL")
|
||||||
|
var ZeusURL = GetOrDefaultEnv("ZEUS_URL", "ZeusURL")
|
||||||
// this is set via build time variable
|
|
||||||
var ZeusURL = "https://api.signoz.cloud"
|
|
||||||
|
|
||||||
func GetOrDefaultEnv(key string, fallback string) string {
|
func GetOrDefaultEnv(key string, fallback string) string {
|
||||||
v := os.Getenv(key)
|
v := os.Getenv(key)
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ package signozio
|
|||||||
|
|
||||||
type status string
|
type status string
|
||||||
|
|
||||||
|
type ActivationResult struct {
|
||||||
|
Status status `json:"status"`
|
||||||
|
Data *ActivationResponse `json:"data,omitempty"`
|
||||||
|
ErrorType string `json:"errorType,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivationResponse struct {
|
||||||
|
ActivationId string `json:"ActivationId"`
|
||||||
|
PlanDetails string `json:"PlanDetails"`
|
||||||
|
}
|
||||||
|
|
||||||
type ValidateLicenseResponse struct {
|
type ValidateLicenseResponse struct {
|
||||||
Status status `json:"status"`
|
Status status `json:"status"`
|
||||||
Data map[string]interface{} `json:"data"`
|
Data map[string]interface{} `json:"data"`
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"go.signoz.io/signoz/ee/query-service/constants"
|
"go.signoz.io/signoz/ee/query-service/constants"
|
||||||
"go.signoz.io/signoz/ee/query-service/model"
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
@@ -38,6 +39,86 @@ func init() {
|
|||||||
C = New()
|
C = New()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActivateLicense sends key to license.signoz.io and gets activation data
|
||||||
|
func ActivateLicense(key, siteId string) (*ActivationResponse, *model.ApiError) {
|
||||||
|
licenseReq := map[string]string{
|
||||||
|
"key": key,
|
||||||
|
"siteId": siteId,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqString, _ := json.Marshal(licenseReq)
|
||||||
|
httpResponse, err := http.Post(C.Prefix+"/licenses/activate", APPLICATION_JSON, bytes.NewBuffer(reqString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to connect to license.signoz.io", zap.Error(err))
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("unable to connect with license.signoz.io, please check your network connection"))
|
||||||
|
}
|
||||||
|
|
||||||
|
httpBody, err := io.ReadAll(httpResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to read activation response from license.signoz.io", zap.Error(err))
|
||||||
|
return nil, model.BadRequest(fmt.Errorf("failed to read activation response from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer httpResponse.Body.Close()
|
||||||
|
|
||||||
|
// read api request result
|
||||||
|
result := ActivationResult{}
|
||||||
|
err = json.Unmarshal(httpBody, &result)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to marshal activation response from license.signoz.io", zap.Error(err))
|
||||||
|
return nil, model.InternalError(errors.Wrap(err, "failed to marshal license activation response"))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch httpResponse.StatusCode {
|
||||||
|
case 200, 201:
|
||||||
|
return result.Data, nil
|
||||||
|
case 400, 401:
|
||||||
|
return nil, model.BadRequest(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
|
||||||
|
default:
|
||||||
|
return nil, model.InternalError(fmt.Errorf(fmt.Sprintf("failed to activate: %s", result.Error)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLicense validates the license key
|
||||||
|
func ValidateLicense(activationId string) (*ActivationResponse, *model.ApiError) {
|
||||||
|
validReq := map[string]string{
|
||||||
|
"activationId": activationId,
|
||||||
|
}
|
||||||
|
|
||||||
|
reqString, _ := json.Marshal(validReq)
|
||||||
|
response, err := http.Post(C.Prefix+"/licenses/validate", APPLICATION_JSON, bytes.NewBuffer(reqString))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "unable to connect with license.signoz.io, please check your network connection"))
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := io.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "failed to read validation response from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
switch response.StatusCode {
|
||||||
|
case 200, 201:
|
||||||
|
a := ActivationResult{}
|
||||||
|
err = json.Unmarshal(body, &a)
|
||||||
|
if err != nil {
|
||||||
|
return nil, model.BadRequest(errors.Wrap(err, "failed to marshal license validation response"))
|
||||||
|
}
|
||||||
|
return a.Data, nil
|
||||||
|
case 400, 401:
|
||||||
|
return nil, model.BadRequest(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"bad request error received from license.signoz.io"))
|
||||||
|
default:
|
||||||
|
return nil, model.InternalError(errors.Wrap(fmt.Errorf(string(body)),
|
||||||
|
"internal error received from license.signoz.io"))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) {
|
func ValidateLicenseV3(licenseKey string) (*model.LicenseV3, *model.ApiError) {
|
||||||
|
|
||||||
// Creating an HTTP client with a timeout for better control
|
// Creating an HTTP client with a timeout for better control
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
|
|
||||||
"go.signoz.io/signoz/ee/query-service/license/sqlite"
|
"go.signoz.io/signoz/ee/query-service/license/sqlite"
|
||||||
"go.signoz.io/signoz/ee/query-service/model"
|
"go.signoz.io/signoz/ee/query-service/model"
|
||||||
@@ -78,7 +77,9 @@ func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
|||||||
return licenseV3Data, nil
|
return licenseV3Data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Repo) GetActiveLicenseV2(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
// GetActiveLicense fetches the latest active license from DB.
|
||||||
|
// If the license is not present, expect a nil license and a nil error in the output.
|
||||||
|
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
||||||
var err error
|
var err error
|
||||||
licenses := []model.License{}
|
licenses := []model.License{}
|
||||||
|
|
||||||
@@ -107,21 +108,6 @@ func (r *Repo) GetActiveLicenseV2(ctx context.Context) (*model.License, *basemod
|
|||||||
return active, nil
|
return active, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActiveLicense fetches the latest active license from DB.
|
|
||||||
// If the license is not present, expect a nil license and a nil error in the output.
|
|
||||||
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
|
||||||
activeLicenseV3, err := r.GetActiveLicenseV3(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if activeLicenseV3 == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
activeLicenseV2 := model.ConvertLicenseV3ToLicenseV2(activeLicenseV3)
|
|
||||||
return activeLicenseV2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {
|
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {
|
||||||
var err error
|
var err error
|
||||||
licenses := []model.LicenseDB{}
|
licenses := []model.LicenseDB{}
|
||||||
@@ -288,14 +274,14 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// InsertLicenseV3 inserts a new license v3 in db
|
// InsertLicenseV3 inserts a new license v3 in db
|
||||||
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {
|
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
||||||
|
|
||||||
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
|
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
|
||||||
|
|
||||||
// licsense is the entity of zeus so putting the entire license here without defining schema
|
// licsense is the entity of zeus so putting the entire license here without defining schema
|
||||||
licenseData, err := json.Marshal(l.Data)
|
licenseData, err := json.Marshal(l.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &model.ApiError{Typ: basemodel.ErrorBadData, Err: err}
|
return fmt.Errorf("insert license failed: license marshal error")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = r.db.ExecContext(ctx,
|
_, err = r.db.ExecContext(ctx,
|
||||||
@@ -306,14 +292,8 @@ func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.A
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if sqliteErr, ok := err.(sqlite3.Error); ok {
|
|
||||||
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
|
|
||||||
zap.L().Error("error in inserting license data: ", zap.Error(sqliteErr))
|
|
||||||
return &model.ApiError{Typ: model.ErrorConflict, Err: sqliteErr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zap.L().Error("error in inserting license data: ", zap.Error(err))
|
zap.L().Error("error in inserting license data: ", zap.Error(err))
|
||||||
return &model.ApiError{Typ: basemodel.ErrorExec, Err: err}
|
return fmt.Errorf("failed to insert license in db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ type Manager struct {
|
|||||||
activeFeatures basemodel.FeatureSet
|
activeFeatures basemodel.FeatureSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
|
func StartManager(dbType string, db *sqlx.DB, useLicensesV3 bool, features ...basemodel.Feature) (*Manager, error) {
|
||||||
if LM != nil {
|
if LM != nil {
|
||||||
return LM, nil
|
return LM, nil
|
||||||
}
|
}
|
||||||
@@ -67,7 +67,7 @@ func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*M
|
|||||||
repo: &repo,
|
repo: &repo,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.start(features...); err != nil {
|
if err := m.start(useLicensesV3, features...); err != nil {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
LM = m
|
LM = m
|
||||||
@@ -75,8 +75,16 @@ func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*M
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start loads active license in memory and initiates validator
|
// start loads active license in memory and initiates validator
|
||||||
func (lm *Manager) start(features ...basemodel.Feature) error {
|
func (lm *Manager) start(useLicensesV3 bool, features ...basemodel.Feature) error {
|
||||||
return lm.LoadActiveLicenseV3(features...)
|
|
||||||
|
var err error
|
||||||
|
if useLicensesV3 {
|
||||||
|
err = lm.LoadActiveLicenseV3(features...)
|
||||||
|
} else {
|
||||||
|
err = lm.LoadActiveLicense(features...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *Manager) Stop() {
|
func (lm *Manager) Stop() {
|
||||||
@@ -84,6 +92,31 @@ func (lm *Manager) Stop() {
|
|||||||
<-lm.terminated
|
<-lm.terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (lm *Manager) SetActive(l *model.License, features ...basemodel.Feature) {
|
||||||
|
lm.mutex.Lock()
|
||||||
|
defer lm.mutex.Unlock()
|
||||||
|
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lm.activeLicense = l
|
||||||
|
lm.activeFeatures = append(l.FeatureSet, features...)
|
||||||
|
// set default features
|
||||||
|
setDefaultFeatures(lm)
|
||||||
|
|
||||||
|
err := lm.InitFeatures(lm.activeFeatures)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Panic("Couldn't activate features", zap.Error(err))
|
||||||
|
}
|
||||||
|
if !lm.validatorRunning {
|
||||||
|
// we want to make sure only one validator runs,
|
||||||
|
// we already have lock() so good to go
|
||||||
|
lm.validatorRunning = true
|
||||||
|
go lm.Validator(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
|
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
|
||||||
lm.mutex.Lock()
|
lm.mutex.Lock()
|
||||||
defer lm.mutex.Unlock()
|
defer lm.mutex.Unlock()
|
||||||
@@ -114,6 +147,29 @@ func setDefaultFeatures(lm *Manager) {
|
|||||||
lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
|
lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadActiveLicense loads the most recent active license
|
||||||
|
func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
|
||||||
|
active, err := lm.repo.GetActiveLicense(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if active != nil {
|
||||||
|
lm.SetActive(active, features...)
|
||||||
|
} else {
|
||||||
|
zap.L().Info("No active license found, defaulting to basic plan")
|
||||||
|
// if no active license is found, we default to basic(free) plan with all default features
|
||||||
|
lm.activeFeatures = model.BasicPlan
|
||||||
|
setDefaultFeatures(lm)
|
||||||
|
err := lm.InitFeatures(lm.activeFeatures)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("Couldn't initialize features", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
||||||
active, err := lm.repo.GetActiveLicenseV3(context.Background())
|
active, err := lm.repo.GetActiveLicenseV3(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -173,20 +229,38 @@ func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.License
|
|||||||
if lm.activeLicenseV3 != nil && l.Key == lm.activeLicenseV3.Key {
|
if lm.activeLicenseV3 != nil && l.Key == lm.activeLicenseV3.Key {
|
||||||
l.IsCurrent = true
|
l.IsCurrent = true
|
||||||
}
|
}
|
||||||
if l.ValidUntil == -1 {
|
|
||||||
// for subscriptions, there is no end-date as such
|
|
||||||
// but for showing user some validity we default one year timespan
|
|
||||||
l.ValidUntil = l.ValidFrom + 31556926
|
|
||||||
}
|
|
||||||
response = append(response, l)
|
response = append(response, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validator validates license after an epoch of time
|
||||||
|
func (lm *Manager) Validator(ctx context.Context) {
|
||||||
|
defer close(lm.terminated)
|
||||||
|
tick := time.NewTicker(validationFrequency)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
lm.Validate(ctx)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-lm.done:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
select {
|
||||||
|
case <-lm.done:
|
||||||
|
return
|
||||||
|
case <-tick.C:
|
||||||
|
lm.Validate(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Validator validates license after an epoch of time
|
// Validator validates license after an epoch of time
|
||||||
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
||||||
zap.L().Info("ValidatorV3 started!")
|
|
||||||
defer close(lm.terminated)
|
defer close(lm.terminated)
|
||||||
tick := time.NewTicker(validationFrequency)
|
tick := time.NewTicker(validationFrequency)
|
||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
@@ -209,6 +283,74 @@ func (lm *Manager) ValidatorV3(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates the current active license
|
||||||
|
func (lm *Manager) Validate(ctx context.Context) (reterr error) {
|
||||||
|
zap.L().Info("License validation started")
|
||||||
|
if lm.activeLicense == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
lm.mutex.Lock()
|
||||||
|
|
||||||
|
lm.lastValidated = time.Now().Unix()
|
||||||
|
if reterr != nil {
|
||||||
|
zap.L().Error("License validation completed with error", zap.Error(reterr))
|
||||||
|
atomic.AddUint64(&lm.failedAttempts, 1)
|
||||||
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
||||||
|
map[string]interface{}{"err": reterr.Error()}, "", true, false)
|
||||||
|
} else {
|
||||||
|
zap.L().Info("License validation completed with no errors")
|
||||||
|
}
|
||||||
|
|
||||||
|
lm.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
response, apiError := validate.ValidateLicense(lm.activeLicense.ActivationId)
|
||||||
|
if apiError != nil {
|
||||||
|
zap.L().Error("failed to validate license", zap.Error(apiError.Err))
|
||||||
|
return apiError.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.PlanDetails == lm.activeLicense.PlanDetails {
|
||||||
|
// license plan hasnt changed, nothing to do
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.PlanDetails != "" {
|
||||||
|
|
||||||
|
// copy and replace the active license record
|
||||||
|
l := model.License{
|
||||||
|
Key: lm.activeLicense.Key,
|
||||||
|
CreatedAt: lm.activeLicense.CreatedAt,
|
||||||
|
PlanDetails: response.PlanDetails,
|
||||||
|
ValidationMessage: lm.activeLicense.ValidationMessage,
|
||||||
|
ActivationId: lm.activeLicense.ActivationId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.ParsePlan(); err != nil {
|
||||||
|
zap.L().Error("failed to parse updated license", zap.Error(err))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// updated plan is parsable, check if plan has changed
|
||||||
|
if lm.activeLicense.PlanDetails != response.PlanDetails {
|
||||||
|
err := lm.repo.UpdatePlanDetails(ctx, lm.activeLicense.Key, response.PlanDetails)
|
||||||
|
if err != nil {
|
||||||
|
// unexpected db write issue but we can let the user continue
|
||||||
|
// and wait for update to work in next cycle.
|
||||||
|
zap.L().Error("failed to validate license", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// activate the update license plan
|
||||||
|
lm.SetActive(&l)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo[vikrantgupta25]: check the comparison here between old and new license!
|
||||||
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
|
func (lm *Manager) RefreshLicense(ctx context.Context) *model.ApiError {
|
||||||
|
|
||||||
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
|
license, apiError := validate.ValidateLicenseV3(lm.activeLicenseV3.Key)
|
||||||
@@ -256,6 +398,50 @@ func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Activate activates a license key with signoz server
|
||||||
|
func (lm *Manager) Activate(ctx context.Context, key string) (licenseResponse *model.License, errResponse *model.ApiError) {
|
||||||
|
defer func() {
|
||||||
|
if errResponse != nil {
|
||||||
|
userEmail, err := auth.GetEmailFromJwt(ctx)
|
||||||
|
if err == nil {
|
||||||
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED,
|
||||||
|
map[string]interface{}{"err": errResponse.Err.Error()}, userEmail, true, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
response, apiError := validate.ActivateLicense(key, "")
|
||||||
|
if apiError != nil {
|
||||||
|
zap.L().Error("failed to activate license", zap.Error(apiError.Err))
|
||||||
|
return nil, apiError
|
||||||
|
}
|
||||||
|
|
||||||
|
l := &model.License{
|
||||||
|
Key: key,
|
||||||
|
ActivationId: response.ActivationId,
|
||||||
|
PlanDetails: response.PlanDetails,
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse validity and features from the plan details
|
||||||
|
err := l.ParsePlan()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to activate license", zap.Error(err))
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the license before activating it
|
||||||
|
err = lm.repo.InsertLicense(ctx, l)
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to activate license", zap.Error(err))
|
||||||
|
return nil, model.InternalError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// license is valid, activate it
|
||||||
|
lm.SetActive(l)
|
||||||
|
return l, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
|
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseResponse *model.LicenseV3, errResponse *model.ApiError) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if errResponse != nil {
|
if errResponse != nil {
|
||||||
@@ -277,7 +463,7 @@ func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (licenseRe
|
|||||||
err := lm.repo.InsertLicenseV3(ctx, license)
|
err := lm.repo.InsertLicenseV3(ctx, license)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("failed to activate license", zap.Error(err))
|
zap.L().Error("failed to activate license", zap.Error(err))
|
||||||
return nil, err
|
return nil, model.InternalError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// license is valid, activate it
|
// license is valid, activate it
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func main() {
|
|||||||
var cluster string
|
var cluster string
|
||||||
|
|
||||||
var useLogsNewSchema bool
|
var useLogsNewSchema bool
|
||||||
var useTraceNewSchema bool
|
var useLicensesV3 bool
|
||||||
var cacheConfigPath, fluxInterval string
|
var cacheConfigPath, fluxInterval string
|
||||||
var enableQueryServiceLogOTLPExport bool
|
var enableQueryServiceLogOTLPExport bool
|
||||||
var preferSpanMetrics bool
|
var preferSpanMetrics bool
|
||||||
@@ -103,10 +103,9 @@ func main() {
|
|||||||
var maxOpenConns int
|
var maxOpenConns int
|
||||||
var dialTimeout time.Duration
|
var dialTimeout time.Duration
|
||||||
var gatewayUrl string
|
var gatewayUrl string
|
||||||
var useLicensesV3 bool
|
|
||||||
|
|
||||||
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
flag.BoolVar(&useLogsNewSchema, "use-logs-new-schema", false, "use logs_v2 schema for logs")
|
||||||
flag.BoolVar(&useTraceNewSchema, "use-trace-new-schema", false, "use new schema for traces")
|
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
|
||||||
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
|
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
|
||||||
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
|
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
|
||||||
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
|
flag.BoolVar(&disableRules, "rules.disable", false, "(disable rule evaluation)")
|
||||||
@@ -120,7 +119,6 @@ func main() {
|
|||||||
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
|
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
|
||||||
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
|
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
|
||||||
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
|
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
|
||||||
flag.BoolVar(&useLicensesV3, "use-licenses-v3", false, "use licenses_v3 schema for licenses")
|
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@@ -147,7 +145,7 @@ func main() {
|
|||||||
Cluster: cluster,
|
Cluster: cluster,
|
||||||
GatewayUrl: gatewayUrl,
|
GatewayUrl: gatewayUrl,
|
||||||
UseLogsNewSchema: useLogsNewSchema,
|
UseLogsNewSchema: useLogsNewSchema,
|
||||||
UseTraceNewSchema: useTraceNewSchema,
|
UseLicensesV3: useLicensesV3,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the jwt secret key
|
// Read the jwt secret key
|
||||||
|
|||||||
@@ -247,24 +247,3 @@ func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}
|
|||||||
licenseDataWithIdAndKey["key"] = key
|
licenseDataWithIdAndKey["key"] = key
|
||||||
return NewLicenseV3(licenseDataWithIdAndKey)
|
return NewLicenseV3(licenseDataWithIdAndKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
|
|
||||||
planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
|
|
||||||
if !ok {
|
|
||||||
planKeyFromPlanName = Basic
|
|
||||||
}
|
|
||||||
return &License{
|
|
||||||
Key: l.Key,
|
|
||||||
ActivationId: "",
|
|
||||||
PlanDetails: "",
|
|
||||||
FeatureSet: l.Features,
|
|
||||||
ValidationMessage: "",
|
|
||||||
IsCurrent: l.IsCurrent,
|
|
||||||
LicensePlan: LicensePlan{
|
|
||||||
PlanKey: planKeyFromPlanName,
|
|
||||||
ValidFrom: l.ValidFrom,
|
|
||||||
ValidUntil: l.ValidUntil,
|
|
||||||
Status: l.Status},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ var (
|
|||||||
PlanNameBasic = "BASIC"
|
PlanNameBasic = "BASIC"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
MapOldPlanKeyToNewPlanName map[string]string = map[string]string{PlanNameBasic: Basic, PlanNameTeams: Pro, PlanNameEnterprise: Enterprise}
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
LicenseStatusInactive = "INACTIVE"
|
LicenseStatusInactive = "INACTIVE"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -61,11 +61,6 @@ func NewAnomalyRule(
|
|||||||
|
|
||||||
zap.L().Info("creating new AnomalyRule", zap.String("id", id), zap.Any("opts", opts))
|
zap.L().Info("creating new AnomalyRule", zap.String("id", id), zap.Any("opts", opts))
|
||||||
|
|
||||||
if p.RuleCondition.CompareOp == baserules.ValueIsBelow {
|
|
||||||
target := -1 * *p.RuleCondition.Target
|
|
||||||
p.RuleCondition.Target = &target
|
|
||||||
}
|
|
||||||
|
|
||||||
baseRule, err := baserules.NewBaseRule(id, p, reader, opts...)
|
baseRule, err := baserules.NewBaseRule(id, p, reader, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ func PrepareTaskFunc(opts baserules.PrepareTaskOptions) (baserules.Task, error)
|
|||||||
opts.FF,
|
opts.FF,
|
||||||
opts.Reader,
|
opts.Reader,
|
||||||
opts.UseLogsNewSchema,
|
opts.UseLogsNewSchema,
|
||||||
opts.UseTraceNewSchema,
|
|
||||||
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
baserules.WithEvalDelay(opts.ManagerOpts.EvalDelay),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -123,7 +122,6 @@ func TestNotification(opts baserules.PrepareTestRuleOptions) (int, *basemodel.Ap
|
|||||||
opts.FF,
|
opts.FF,
|
||||||
opts.Reader,
|
opts.Reader,
|
||||||
opts.UseLogsNewSchema,
|
opts.UseLogsNewSchema,
|
||||||
opts.UseTraceNewSchema,
|
|
||||||
baserules.WithSendAlways(),
|
baserules.WithSendAlways(),
|
||||||
baserules.WithSendUnmatched(),
|
baserules.WithSendUnmatched(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -13,3 +13,8 @@ if [ "$branch" = "main" ]; then
|
|||||||
echo "${color_red}${bold}You can't commit directly to the main branch${reset}"
|
echo "${color_red}${bold}You can't commit directly to the main branch${reset}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ "$branch" = "develop" ]; then
|
||||||
|
echo "${color_red}${bold}You can't commit directly to the develop branch${reset}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -40,8 +40,8 @@
|
|||||||
"@monaco-editor/react": "^4.3.1",
|
"@monaco-editor/react": "^4.3.1",
|
||||||
"@radix-ui/react-tabs": "1.0.4",
|
"@radix-ui/react-tabs": "1.0.4",
|
||||||
"@radix-ui/react-tooltip": "1.0.7",
|
"@radix-ui/react-tooltip": "1.0.7",
|
||||||
"@sentry/react": "8.41.0",
|
"@sentry/react": "7.102.1",
|
||||||
"@sentry/webpack-plugin": "2.22.6",
|
"@sentry/webpack-plugin": "2.16.0",
|
||||||
"@signozhq/design-tokens": "1.1.4",
|
"@signozhq/design-tokens": "1.1.4",
|
||||||
"@uiw/react-md-editor": "3.23.5",
|
"@uiw/react-md-editor": "3.23.5",
|
||||||
"@visx/group": "3.3.0",
|
"@visx/group": "3.3.0",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"fontfaceobserver": "2.3.0",
|
"fontfaceobserver": "2.3.0",
|
||||||
"history": "4.10.1",
|
"history": "4.10.1",
|
||||||
"html-webpack-plugin": "5.5.0",
|
"html-webpack-plugin": "5.5.0",
|
||||||
"http-proxy-middleware": "3.0.3",
|
"http-proxy-middleware": "2.0.7",
|
||||||
"i18next": "^21.6.12",
|
"i18next": "^21.6.12",
|
||||||
"i18next-browser-languagedetector": "^6.1.3",
|
"i18next-browser-languagedetector": "^6.1.3",
|
||||||
"i18next-http-backend": "^1.3.2",
|
"i18next-http-backend": "^1.3.2",
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"web-vitals": "^0.2.4",
|
"web-vitals": "^0.2.4",
|
||||||
"webpack": "5.94.0",
|
"webpack": "5.94.0",
|
||||||
"webpack-dev-server": "^4.15.2",
|
"webpack-dev-server": "^4.15.1",
|
||||||
"webpack-retry-chunk-load-plugin": "3.1.1",
|
"webpack-retry-chunk-load-plugin": "3.1.1",
|
||||||
"xstate": "^4.31.0"
|
"xstate": "^4.31.0"
|
||||||
},
|
},
|
||||||
@@ -241,7 +241,6 @@
|
|||||||
"semver": "7.5.4",
|
"semver": "7.5.4",
|
||||||
"xml2js": "0.5.0",
|
"xml2js": "0.5.0",
|
||||||
"phin": "^3.7.1",
|
"phin": "^3.7.1",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "1.20.3"
|
||||||
"http-proxy-middleware": "3.0.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="none"><path fill="#A65F3E" d="M8.04 10.331a.41.41 0 0 1-.414-.414.4.4 0 0 1 .121-.292l8.071-8.071a.414.414 0 1 1 .585.585l-8.07 8.071a.4.4 0 0 1-.293.121"/><path fill="#A65F3E" d="M16.11 1.5c.09 0 .178.034.245.101a.35.35 0 0 1 0 .492l-8.07 8.07a.346.346 0 0 1-.49 0 .35.35 0 0 1 0-.49l8.07-8.072a.35.35 0 0 1 .245-.101m0-.133a.48.48 0 0 0-.338.14L7.7 9.578a.47.47 0 0 0-.14.34.475.475 0 0 0 .478.478c.13 0 .25-.05.34-.14l8.07-8.071a.48.48 0 0 0-.339-.818"/><path fill="#FFE082" d="m1.701 12.438 3.89 3.889c.873-.963 1.62-2.057 2.023-3.313.03-.091.034-.24.128-.359.451-.566 1.865-2.008.706-3.167-1.106-1.106-2.438.227-2.994.686-.17.14-.384.228-.606.276-1.493.326-3.034 1.869-3.147 1.988"/><path fill="#FFE082" d="M8.385 8.577a.62.62 0 0 1 .393-.085c.098.018.237.135.38.28.144.143.28.304.32.408s-.005.242-.005.242c-.116.23-.383.69-.6.624-.24-.074-.482-.305-.66-.479a1.5 1.5 0 0 1-.276-.328c-.096-.177.008-.324.129-.447.086-.082.232-.17.319-.215"/><path fill="#F9C248" d="M8.327 8.975c.116.11.21.243.339.338.252.185.455.097.62-.052.049-.044.122-.1.17-.055a.1.1 0 0 1 .025.051.45.45 0 0 1-.045.273 1.3 1.3 0 0 1-.433.529c-.032.022-.07.044-.11.032a.12.12 0 0 1-.056-.045c-.207-.244-.37-.533-.626-.724-.103-.076-.364-.132-.298-.303.1-.262.317-.137.414-.044"/><path fill="#F9C248" d="M7.614 13.014c.028-.091.033-.24.127-.359.515-.645 1.223-1.38 1.145-2.275-.01-.123-.169-.75-.342-.514-.04.052-.024.315-.03.379-.1 1.172-1.02 1.821-1.19 2.024s-.164.393-.31.695a5 5 0 0 1-.61.947c-.379.47-.825.88-1.286 1.27a.8.8 0 0 0-.203.217c-.131.241.153.406.305.558l.369.368c.873-.961 1.62-2.055 2.025-3.31"/><path fill="#E2A610" d="M5.537 15.809c-.1-.157-.242-.3-.317-.458a.24.24 0 0 1-.03-.123c.01-.08.13-.15.187-.198q.129-.108.254-.22c.162-.149.314-.314.419-.509.017-.031.032-.07.016-.102-.035-.065-.238.152-.275.186-.105.092-.208.187-.318.272-.146.113-.422.304-.618.213-.1-.046-.19-.169-.263-.249-.084-.094-.164-.191-.252-.283a17 17 0 0 0-.592-.582c-.05-.046-.06-.066-.003-.122a10 10 0 0 0 .546-.58c.022-.025.044-.067.017-.09-.018-.015-.048-.004-.07.007-.26.138-.467.354-.692.544-.055.046-.214-.13-.249-.158-.092-.073-.154-.102-.046-.21.484-.49.972-.946 1.554-1.323.107-.07.22-.14.28-.253-.01-.03-.054-.026-.085-.015-.807.29-1.89 1.291-1.983 1.38-.162.158-.454-.206-.885-.481-.147-.094 0-.235.038-.279.26-.307.603-.642.603-.642-.127.013-.956.76-1.054.873-.084.097-.17.184-.175.318a.52.52 0 0 0 .107.325c.77 1.05 2.586 2.794 3.23 3.253.384.274.502.224.659.068a.35.35 0 0 0 .105-.3.65.65 0 0 0-.108-.263"/><path fill="#A65F3E" d="M8.835 10.176s-.438-.825-1.017-1.074c0 0-.02-.054.02-.1.052-.057.12-.07.157-.046.427.265.812.619 1.007 1.102.039.093-.131.23-.167.118"/><path fill="#F44336" d="M7.64 12.88c-.528-.818-1.63-1.937-2.46-2.524-.066-.046.204-.204.272-.156a9.7 9.7 0 0 1 2.31 2.398c.045.067-.091.33-.123.282M8.193 12.078c-.506-.71-1.521-1.738-2.238-2.312-.062-.05.182-.22.232-.181.755.602 1.668 1.499 2.18 2.263.037.057-.138.282-.174.23"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 2.9 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" fill="none"><path fill="#616161" fill-rule="evenodd" d="M8.096 2.885H4.372V2.51h3.724zM8.096 4.79H4.372v-.375h3.724z" clip-rule="evenodd"/><path fill="#9E9E9E" d="M7.098 15.539H5.662V.936s.134-.311.719-.311.719.311.719.311v14.603z"/><path fill="#757575" d="M6.73.671V12.47H5.662v1.074c.181.001.345.023.493.055.336.074.576.37.576.714v1.227H7.1V.936c-.002 0-.08-.179-.37-.265"/><path fill="#2196F3" d="M10.58.54a3.03 3.03 0 0 0-3.028 3.038 3.02 3.02 0 0 0 3.027 3.028 3.025 3.025 0 0 0 3.028-3.028A3.035 3.035 0 0 0 10.579.54"/><path fill="#fff" d="M11.902 1.671c-.19-.048-.569-.098-1.321-.098-.753 0-1.132.05-1.322.098-.112.029-.488.185-.488.606v2.598c0 .142.115.258.258.258h.095v.288c0 .084.068.151.152.151h.306a.15.15 0 0 0 .151-.151v-.288h1.693v.288c0 .084.067.151.15.151h.307a.15.15 0 0 0 .151-.151v-.288h.098a.26.26 0 0 0 .259-.258V2.277c0-.404-.377-.579-.49-.606m-2.139.206c0-.064.051-.115.115-.115h1.403c.063 0 .115.051.115.115v.204a.115.115 0 0 1-.115.115H9.878a.115.115 0 0 1-.115-.115zm.024 2.736a.08.08 0 0 1-.078.078h-.308a.264.264 0 0 1-.264-.264v-.139c0-.042.035-.077.077-.077h.31c.144 0 .263.117.263.264zm2.235-.186a.264.264 0 0 1-.264.264h-.309a.08.08 0 0 1-.077-.078v-.138c0-.145.117-.264.264-.264h.308c.043 0 .078.035.078.077zm.07-1.129c0 .168-.363.46-1.513.46s-1.512-.27-1.512-.46v-.767c0-.05.05-.175.175-.175h2.695c.125 0 .155.126.155.175z"/><path fill="#F5F5F5" d="M8.61 12.867H4.15a.285.285 0 0 1-.285-.285v-5.15c0-.158.127-.285.285-.285h4.457c.158 0 .285.127.285.285v5.15a.285.285 0 0 1-.284.285"/><path fill="#82AEC0" d="M8.128 12.015H4.632l-.01-4.07H8.12z" opacity=".8"/><path fill="#F5F5F5" fill-rule="evenodd" d="M6.246 12.07V7.945h.25v4.123z" clip-rule="evenodd"/><path fill="#616161" d="M6.246 7.946H4.622v.34h1.624z"/><path fill="#F5F5F5" fill-rule="evenodd" d="M8.142 11.307H4.618v-.125h3.524zM8.142 10.482H4.618v-.125h3.524zM8.12 9.657H4.621v-.125H8.12zM8.12 8.833H4.617v-.125H8.12z" clip-rule="evenodd"/><path fill="#616161" d="M8.118 9.426H6.495v.34h1.623zM6.253 10.25H4.635v.34h1.618z"/><path fill="#9E9E9E" fill-rule="evenodd" d="M4.15 7.334a.097.097 0 0 0-.097.098v5.15c0 .054.044.097.098.097h4.458a.097.097 0 0 0 .097-.097v-5.15a.097.097 0 0 0-.098-.098zm-.472.098c0-.261.212-.473.473-.473h4.457c.261 0 .473.212.473.473v5.15c0 .26-.211.472-.472.472H4.151a.47.47 0 0 1-.473-.472z" clip-rule="evenodd"/><path fill="#757575" d="M4.17 12.682c-.194.017-.194-.11-.194-.145V7.493c0-.141.115-.256.256-.256H8.56c.118 0 .172.071.148.216 0 0-.015-.092-.128-.092H4.233a.133.133 0 0 0-.132.132v5.045c0 .117.069.144.069.144"/><path fill="#FFCA28" d="M4.9 1.775H.642v3.7h4.26z"/><path fill="#9E9E9E" d="M4.663 1.775c.132 0 .238.106.238.237v3.225a.237.237 0 0 1-.238.237H.88a.237.237 0 0 1-.238-.237V2.012c0-.131.107-.237.238-.237zm0-.25H.88a.49.49 0 0 0-.488.487v3.225c0 .269.22.487.488.487h3.783a.49.49 0 0 0 .488-.487V2.012a.487.487 0 0 0-.488-.487"/><path fill="#FFFDE7" fill-rule="evenodd" d="M4.902 3.11H.642v-.25h4.26zM4.902 4.388H.642v-.25h4.26z" clip-rule="evenodd"/><path fill="#757575" d="M1.975 2.186H.904v.282h1.07zM1.711 4.777H.904v.283h.807zM4.552 4.777h-.807v.283h.807zM4.552 2.186h-.807v.282h.807zM3.795 3.482H3.33v.282h.465zM4.552 3.482h-.465v.282h.465zM2.388 3.482H.904v.282h1.484z"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 3.2 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 408 KiB |
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"workspaceSuspended": "Your workspace is locked",
|
|
||||||
"gotQuestions": "Got Questions?",
|
|
||||||
"contactUs": "Contact Us",
|
|
||||||
"actionHeader": "Pay to continue",
|
|
||||||
"actionDescription": "Pay now to keep enjoying all the great features you’ve been using.",
|
|
||||||
"yourDataIsSafe": "Your data is safe with us until",
|
|
||||||
"actNow": "Act now to avoid any disruptions and continue where you left off.",
|
|
||||||
"contactAdmin": "Contact your admin to proceed with the upgrade.",
|
|
||||||
"continueMyJourney": "Settle your bill to continue",
|
|
||||||
"somethingWentWrong": "Something went wrong"
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"containers_visualization_message": "The ability to visualise containers is in active development and should be available to you soon.",
|
|
||||||
"processes_visualization_message": "The ability to visualise processes is in active development and should be available to you soon.",
|
|
||||||
"working_message": "We're working to extend infrastructure monitoring to take care of a bunch of different cases. Thank you for your patience.",
|
|
||||||
"waitlist_message": "Join the waitlist for early access.",
|
|
||||||
"waitlist_success_message": "We have received your request for early access. We will get back to you as soon as we launch the feature.",
|
|
||||||
"contact_support": "Contact Support"
|
|
||||||
}
|
|
||||||
@@ -37,10 +37,8 @@
|
|||||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||||
"WORKSPACE_LOCKED": "SigNoz | Workspace Locked",
|
"WORKSPACE_LOCKED": "SigNoz | Workspace Locked",
|
||||||
"WORKSPACE_SUSPENDED": "SigNoz | Workspace Suspended",
|
|
||||||
"SUPPORT": "SigNoz | Support",
|
"SUPPORT": "SigNoz | Support",
|
||||||
"DEFAULT": "Open source Observability Platform | SigNoz",
|
"DEFAULT": "Open source Observability Platform | SigNoz",
|
||||||
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
||||||
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview"
|
||||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"save": "Save",
|
"save": "Save",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"logged_in": "Logged In",
|
"logged_in": "Logged In",
|
||||||
"pending_data_placeholder": "Retrieving your {{dataSource}}!"
|
"pending_data_placeholder": "Just a bit of patience, just a little bit’s enough ⎯ we’re getting your {{dataSource}}!"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"workspaceSuspended": "Your workspace is locked",
|
|
||||||
"gotQuestions": "Got Questions?",
|
|
||||||
"contactUs": "Contact Us",
|
|
||||||
"actionHeader": "Pay to continue",
|
|
||||||
"actionDescription": "Pay now to keep enjoying all the great features you’ve been using.",
|
|
||||||
"yourDataIsSafe": "Your data is safe with us until",
|
|
||||||
"actNow": "Act now to avoid any disruptions and continue where you left off.",
|
|
||||||
"contactAdmin": "Contact your admin to proceed with the upgrade.",
|
|
||||||
"continueMyJourney": "Settle your bill to continue",
|
|
||||||
"somethingWentWrong": "Something went wrong"
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"containers_visualization_message": "The ability to visualise containers is in active development and should be available to you soon.",
|
|
||||||
"processes_visualization_message": "The ability to visualise processes is in active development and should be available to you soon.",
|
|
||||||
"working_message": "We're working to extend infrastructure monitoring to take care of a bunch of different cases. Thank you for your patience.",
|
|
||||||
"waitlist_message": "Join the waitlist for early access.",
|
|
||||||
"waitlist_success_message": "We have received your request for early access. We will get back to you as soon as we launch the feature.",
|
|
||||||
"contact_support": "Contact Support"
|
|
||||||
}
|
|
||||||
@@ -45,7 +45,6 @@
|
|||||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||||
"WORKSPACE_LOCKED": "SigNoz | Workspace Locked",
|
"WORKSPACE_LOCKED": "SigNoz | Workspace Locked",
|
||||||
"WORKSPACE_SUSPENDED": "SigNoz | Workspace Suspended",
|
|
||||||
"SUPPORT": "SigNoz | Support",
|
"SUPPORT": "SigNoz | Support",
|
||||||
"LOGS_SAVE_VIEWS": "SigNoz | Logs Saved Views",
|
"LOGS_SAVE_VIEWS": "SigNoz | Logs Saved Views",
|
||||||
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
"TRACES_SAVE_VIEWS": "SigNoz | Traces Saved Views",
|
||||||
@@ -54,6 +53,5 @@
|
|||||||
"INTEGRATIONS": "SigNoz | Integrations",
|
"INTEGRATIONS": "SigNoz | Integrations",
|
||||||
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
"ALERT_HISTORY": "SigNoz | Alert Rule History",
|
||||||
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
"ALERT_OVERVIEW": "SigNoz | Alert Rule Overview",
|
||||||
"MESSAGING_QUEUES": "SigNoz | Messaging Queues",
|
"MESSAGING_QUEUES": "SigNoz | Messaging Queues"
|
||||||
"INFRASTRUCTURE_MONITORING_HOSTS": "SigNoz | Infra Monitoring"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import useLicense from 'hooks/useLicense';
|
|||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import { isEmpty, isNull } from 'lodash-es';
|
import { isEmpty, isNull } from 'lodash-es';
|
||||||
import { useAppContext } from 'providers/App/App';
|
|
||||||
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
import { ReactChild, useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
@@ -21,10 +20,8 @@ import { AppState } from 'store/reducers';
|
|||||||
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
import { getInitialUserTokenRefreshToken } from 'store/utils';
|
||||||
import AppActions from 'types/actions';
|
import AppActions from 'types/actions';
|
||||||
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
import { UPDATE_USER_IS_FETCH } from 'types/actions/app';
|
||||||
import { LicenseState, LicenseStatus } from 'types/api/licensesV3/getActive';
|
|
||||||
import { Organization } from 'types/api/user/getOrganization';
|
import { Organization } from 'types/api/user/getOrganization';
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { isCloudUser } from 'utils/app';
|
|
||||||
import { routePermission } from 'utils/permission';
|
import { routePermission } from 'utils/permission';
|
||||||
|
|
||||||
import routes, {
|
import routes, {
|
||||||
@@ -51,8 +48,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
isFetchingOrgPreferences,
|
isFetchingOrgPreferences,
|
||||||
} = useSelector<AppState, AppReducer>((state) => state.app);
|
} = useSelector<AppState, AppReducer>((state) => state.app);
|
||||||
|
|
||||||
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
|
|
||||||
|
|
||||||
const mapRoutes = useMemo(
|
const mapRoutes = useMemo(
|
||||||
() =>
|
() =>
|
||||||
new Map(
|
new Map(
|
||||||
@@ -81,8 +76,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
|
|
||||||
const { t } = useTranslation(['common']);
|
const { t } = useTranslation(['common']);
|
||||||
|
|
||||||
const isCloudUserVal = isCloudUser();
|
|
||||||
|
|
||||||
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
const localStorageUserAuthToken = getInitialUserTokenRefreshToken();
|
||||||
|
|
||||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||||
@@ -150,7 +143,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
const handleRedirectForOrgOnboarding = (key: string): void => {
|
const handleRedirectForOrgOnboarding = (key: string): void => {
|
||||||
if (
|
if (
|
||||||
isLoggedInState &&
|
isLoggedInState &&
|
||||||
isCloudUserVal &&
|
|
||||||
!isFetchingOrgPreferences &&
|
!isFetchingOrgPreferences &&
|
||||||
!isLoadingOrgUsers &&
|
!isLoadingOrgUsers &&
|
||||||
!isEmpty(orgUsers?.payload) &&
|
!isEmpty(orgUsers?.payload) &&
|
||||||
@@ -166,10 +158,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
history.push(ROUTES.ONBOARDING);
|
history.push(ROUTES.ONBOARDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCloudUserVal && key === 'ONBOARDING') {
|
|
||||||
history.push(ROUTES.APPLICATION);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUserLoginIfTokenPresent = async (
|
const handleUserLoginIfTokenPresent = async (
|
||||||
@@ -253,33 +241,6 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [isFetchingLicensesData]);
|
}, [isFetchingLicensesData]);
|
||||||
|
|
||||||
const navigateToWorkSpaceSuspended = (route: any): void => {
|
|
||||||
const { path } = route;
|
|
||||||
|
|
||||||
if (path && path !== ROUTES.WORKSPACE_SUSPENDED) {
|
|
||||||
history.push(ROUTES.WORKSPACE_SUSPENDED);
|
|
||||||
|
|
||||||
dispatch({
|
|
||||||
type: UPDATE_USER_IS_FETCH,
|
|
||||||
payload: {
|
|
||||||
isUserFetching: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
|
||||||
const shouldSuspendWorkspace =
|
|
||||||
activeLicenseV3.status === LicenseStatus.SUSPENDED &&
|
|
||||||
activeLicenseV3.state === LicenseState.PAYMENT_FAILED;
|
|
||||||
|
|
||||||
if (shouldSuspendWorkspace) {
|
|
||||||
navigateToWorkSpaceSuspended(currentRoute);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (org && org.length > 0 && org[0].id !== undefined) {
|
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||||
setOrgData(org[0]);
|
setOrgData(org[0]);
|
||||||
@@ -289,7 +250,7 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
const handleRouting = (): void => {
|
const handleRouting = (): void => {
|
||||||
const showOrgOnboarding = shouldShowOnboarding();
|
const showOrgOnboarding = shouldShowOnboarding();
|
||||||
|
|
||||||
if (showOrgOnboarding && !isOnboardingComplete && isCloudUserVal) {
|
if (showOrgOnboarding && !isOnboardingComplete) {
|
||||||
history.push(ROUTES.ONBOARDING);
|
history.push(ROUTES.ONBOARDING);
|
||||||
} else {
|
} else {
|
||||||
history.push(ROUTES.APPLICATION);
|
history.push(ROUTES.APPLICATION);
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import history from 'lib/history';
|
|||||||
import { identity, pick, pickBy } from 'lodash-es';
|
import { identity, pick, pickBy } from 'lodash-es';
|
||||||
import posthog from 'posthog-js';
|
import posthog from 'posthog-js';
|
||||||
import AlertRuleProvider from 'providers/Alert';
|
import AlertRuleProvider from 'providers/Alert';
|
||||||
import { AppProvider } from 'providers/App/App';
|
|
||||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||||
import { Suspense, useEffect, useState } from 'react';
|
import { Suspense, useEffect, useState } from 'react';
|
||||||
@@ -292,44 +291,42 @@ function App(): JSX.Element {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppProvider>
|
<ConfigProvider theme={themeConfig}>
|
||||||
<ConfigProvider theme={themeConfig}>
|
<Router history={history}>
|
||||||
<Router history={history}>
|
<CompatRouter>
|
||||||
<CompatRouter>
|
<NotificationProvider>
|
||||||
<NotificationProvider>
|
<PrivateRoute>
|
||||||
<PrivateRoute>
|
<ResourceProvider>
|
||||||
<ResourceProvider>
|
<QueryBuilderProvider>
|
||||||
<QueryBuilderProvider>
|
<DashboardProvider>
|
||||||
<DashboardProvider>
|
<KeyboardHotkeysProvider>
|
||||||
<KeyboardHotkeysProvider>
|
<AlertRuleProvider>
|
||||||
<AlertRuleProvider>
|
<AppLayout>
|
||||||
<AppLayout>
|
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
<Switch>
|
||||||
<Switch>
|
{routes.map(({ path, component, exact }) => (
|
||||||
{routes.map(({ path, component, exact }) => (
|
<Route
|
||||||
<Route
|
key={`${path}`}
|
||||||
key={`${path}`}
|
exact={exact}
|
||||||
exact={exact}
|
path={path}
|
||||||
path={path}
|
component={component}
|
||||||
component={component}
|
/>
|
||||||
/>
|
))}
|
||||||
))}
|
|
||||||
|
|
||||||
<Route path="*" component={NotFound} />
|
<Route path="*" component={NotFound} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
</AlertRuleProvider>
|
</AlertRuleProvider>
|
||||||
</KeyboardHotkeysProvider>
|
</KeyboardHotkeysProvider>
|
||||||
</DashboardProvider>
|
</DashboardProvider>
|
||||||
</QueryBuilderProvider>
|
</QueryBuilderProvider>
|
||||||
</ResourceProvider>
|
</ResourceProvider>
|
||||||
</PrivateRoute>
|
</PrivateRoute>
|
||||||
</NotificationProvider>
|
</NotificationProvider>
|
||||||
</CompatRouter>
|
</CompatRouter>
|
||||||
</Router>
|
</Router>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</AppProvider>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -206,13 +206,6 @@ export const WorkspaceBlocked = Loadable(
|
|||||||
import(/* webpackChunkName: "WorkspaceLocked" */ 'pages/WorkspaceLocked'),
|
import(/* webpackChunkName: "WorkspaceLocked" */ 'pages/WorkspaceLocked'),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const WorkspaceSuspended = Loadable(
|
|
||||||
() =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "WorkspaceSuspended" */ 'pages/WorkspaceSuspended/WorkspaceSuspended'
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const ShortcutsPage = Loadable(
|
export const ShortcutsPage = Loadable(
|
||||||
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'),
|
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Shortcuts'),
|
||||||
);
|
);
|
||||||
@@ -235,10 +228,3 @@ export const MQDetailPage = Loadable(
|
|||||||
/* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage'
|
/* webpackChunkName: "MQDetailPage" */ 'pages/MessagingQueues/MQDetailPage'
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const InfrastructureMonitoring = Loadable(
|
|
||||||
() =>
|
|
||||||
import(
|
|
||||||
/* webpackChunkName: "InfrastructureMonitoring" */ 'pages/InfrastructureMonitoring'
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import {
|
|||||||
EditAlertChannelsAlerts,
|
EditAlertChannelsAlerts,
|
||||||
EditRulesPage,
|
EditRulesPage,
|
||||||
ErrorDetails,
|
ErrorDetails,
|
||||||
InfrastructureMonitoring,
|
|
||||||
IngestionSettings,
|
IngestionSettings,
|
||||||
InstalledIntegrations,
|
InstalledIntegrations,
|
||||||
LicensePage,
|
LicensePage,
|
||||||
@@ -53,7 +52,6 @@ import {
|
|||||||
UnAuthorized,
|
UnAuthorized,
|
||||||
UsageExplorerPage,
|
UsageExplorerPage,
|
||||||
WorkspaceBlocked,
|
WorkspaceBlocked,
|
||||||
WorkspaceSuspended,
|
|
||||||
} from './pageComponents';
|
} from './pageComponents';
|
||||||
|
|
||||||
const routes: AppRoutes[] = [
|
const routes: AppRoutes[] = [
|
||||||
@@ -365,13 +363,6 @@ const routes: AppRoutes[] = [
|
|||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
key: 'WORKSPACE_LOCKED',
|
key: 'WORKSPACE_LOCKED',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: ROUTES.WORKSPACE_SUSPENDED,
|
|
||||||
exact: true,
|
|
||||||
component: WorkspaceSuspended,
|
|
||||||
isPrivate: true,
|
|
||||||
key: 'WORKSPACE_SUSPENDED',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: ROUTES.SHORTCUTS,
|
path: ROUTES.SHORTCUTS,
|
||||||
exact: true,
|
exact: true,
|
||||||
@@ -400,13 +391,6 @@ const routes: AppRoutes[] = [
|
|||||||
key: 'MESSAGING_QUEUES_DETAIL',
|
key: 'MESSAGING_QUEUES_DETAIL',
|
||||||
isPrivate: true,
|
isPrivate: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: ROUTES.INFRASTRUCTURE_MONITORING_HOSTS,
|
|
||||||
exact: true,
|
|
||||||
component: InfrastructureMonitoring,
|
|
||||||
key: 'INFRASTRUCTURE_MONITORING_HOSTS',
|
|
||||||
isPrivate: true,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SUPPORT_ROUTE: AppRoutes = {
|
export const SUPPORT_ROUTE: AppRoutes = {
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
|
||||||
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
|
||||||
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import {
|
|
||||||
BaseAutocompleteData,
|
|
||||||
IQueryAutocompleteResponse,
|
|
||||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
|
|
||||||
export const getHostAttributeKeys = async (
|
|
||||||
searchText = '',
|
|
||||||
): Promise<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response: AxiosResponse<{
|
|
||||||
data: IQueryAutocompleteResponse;
|
|
||||||
}> = await ApiBaseInstance.get(
|
|
||||||
`/hosts/attribute_keys?dataSource=metrics&searchText=${searchText}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const payload: BaseAutocompleteData[] =
|
|
||||||
response.data.data.attributeKeys?.map(({ id: _, ...item }) => ({
|
|
||||||
...item,
|
|
||||||
id: createIdFromObjectFields(item, baseAutoCompleteIdKeysOrder),
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.statusText,
|
|
||||||
payload: { attributeKeys: payload },
|
|
||||||
};
|
|
||||||
} catch (e) {
|
|
||||||
return ErrorResponseHandler(e as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
|
|
||||||
export interface HostListPayload {
|
|
||||||
filters: TagFilter;
|
|
||||||
groupBy: BaseAutocompleteData[];
|
|
||||||
offset?: number;
|
|
||||||
limit?: number;
|
|
||||||
orderBy?: {
|
|
||||||
columnName: string;
|
|
||||||
order: 'asc' | 'desc';
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TimeSeriesValue {
|
|
||||||
timestamp: number;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface TimeSeries {
|
|
||||||
labels: Record<string, string>;
|
|
||||||
labelsArray: Array<Record<string, string>>;
|
|
||||||
values: TimeSeriesValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HostData {
|
|
||||||
hostName: string;
|
|
||||||
active: boolean;
|
|
||||||
os: string;
|
|
||||||
cpu: number;
|
|
||||||
cpuTimeSeries: TimeSeries;
|
|
||||||
memory: number;
|
|
||||||
memoryTimeSeries: TimeSeries;
|
|
||||||
wait: number;
|
|
||||||
waitTimeSeries: TimeSeries;
|
|
||||||
load15: number;
|
|
||||||
load15TimeSeries: TimeSeries;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HostListResponse {
|
|
||||||
status: string;
|
|
||||||
data: {
|
|
||||||
type: string;
|
|
||||||
records: HostData[];
|
|
||||||
groups: null;
|
|
||||||
total: number;
|
|
||||||
sentAnyHostMetricsData: boolean;
|
|
||||||
isSendingK8SAgentMetrics: boolean;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getHostLists = async (
|
|
||||||
props: HostListPayload,
|
|
||||||
signal?: AbortSignal,
|
|
||||||
headers?: Record<string, string>,
|
|
||||||
): Promise<SuccessResponse<HostListResponse> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await ApiBaseInstance.post('/hosts/list', props, {
|
|
||||||
signal,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: 'Success',
|
|
||||||
payload: response.data,
|
|
||||||
params: props,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
import { ApiBaseInstance } from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import {
|
|
||||||
IAttributeValuesResponse,
|
|
||||||
IGetAttributeValuesPayload,
|
|
||||||
} from 'types/api/queryBuilder/getAttributesValues';
|
|
||||||
|
|
||||||
export const getInfraAttributesValues = async ({
|
|
||||||
dataSource,
|
|
||||||
attributeKey,
|
|
||||||
filterAttributeKeyDataType,
|
|
||||||
tagType,
|
|
||||||
searchText,
|
|
||||||
}: IGetAttributeValuesPayload): Promise<
|
|
||||||
SuccessResponse<IAttributeValuesResponse> | ErrorResponse
|
|
||||||
> => {
|
|
||||||
try {
|
|
||||||
const response = await ApiBaseInstance.get(
|
|
||||||
`/hosts/attribute_values?${createQueryParams({
|
|
||||||
dataSource,
|
|
||||||
attributeKey,
|
|
||||||
searchText,
|
|
||||||
})}&filterAttributeKeyDataType=${filterAttributeKeyDataType}&tagType=${tagType}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { ApiV3Instance as axios } from 'api';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { LicenseV3EventQueueResModel } from 'types/api/licensesV3/getActive';
|
|
||||||
|
|
||||||
const getActive = async (): Promise<
|
|
||||||
SuccessResponse<LicenseV3EventQueueResModel> | ErrorResponse
|
|
||||||
> => {
|
|
||||||
const response = await axios.get('/licenses/active');
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getActive;
|
|
||||||
@@ -5,6 +5,7 @@ import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
|
|||||||
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
|
||||||
import createQueryParams from 'lib/createQueryParams';
|
import createQueryParams from 'lib/createQueryParams';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||||
|
// ** Types
|
||||||
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
import { IGetAttributeKeysPayload } from 'types/api/queryBuilder/getAttributeKeys';
|
||||||
import {
|
import {
|
||||||
BaseAutocompleteData,
|
BaseAutocompleteData,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@
|
|||||||
|
|
||||||
&.custom-time {
|
&.custom-time {
|
||||||
input:not(:focus) {
|
input:not(:focus) {
|
||||||
min-width: 280px;
|
min-width: 240px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,69 +119,3 @@
|
|||||||
color: var(--bg-slate-400) !important;
|
color: var(--bg-slate-400) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-time-popover__footer {
|
|
||||||
border-top: 1px solid var(--bg-ink-200);
|
|
||||||
padding: 8px 14px;
|
|
||||||
.timezone-container {
|
|
||||||
&,
|
|
||||||
.timezone {
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 16px;
|
|
||||||
letter-spacing: -0.06px;
|
|
||||||
}
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
gap: 6px;
|
|
||||||
.timezone {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: rgba(171, 189, 255, 0.04);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0px 4px;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.timezone-badge {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0 4px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: rgba(171, 189, 255, 0.04);
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 16px;
|
|
||||||
letter-spacing: -0.06px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.date-time-popover__footer {
|
|
||||||
border-color: var(--bg-vanilla-400);
|
|
||||||
}
|
|
||||||
.timezone-container {
|
|
||||||
color: var(--bg-ink-400);
|
|
||||||
&__clock-icon {
|
|
||||||
stroke: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
.timezone {
|
|
||||||
color: var(--bg-ink-100);
|
|
||||||
background: rgb(179 179 179 / 15%);
|
|
||||||
&__icon {
|
|
||||||
stroke: var(--bg-ink-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.timezone-badge {
|
|
||||||
color: var(--bg-ink-100);
|
|
||||||
background: rgb(179 179 179 / 15%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,14 +15,11 @@ import { isValidTimeFormat } from 'lib/getMinMax';
|
|||||||
import { defaultTo, isFunction, noop } from 'lodash-es';
|
import { defaultTo, isFunction, noop } from 'lodash-es';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
|
import { CheckCircle, ChevronDown, Clock } from 'lucide-react';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import {
|
import {
|
||||||
ChangeEvent,
|
ChangeEvent,
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
@@ -31,8 +28,6 @@ import { popupContainer } from 'utils/selectPopupContainer';
|
|||||||
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
|
import CustomTimePickerPopoverContent from './CustomTimePickerPopoverContent';
|
||||||
|
|
||||||
const maxAllowedMinTimeInMonths = 6;
|
const maxAllowedMinTimeInMonths = 6;
|
||||||
type ViewType = 'datetime' | 'timezone';
|
|
||||||
const DEFAULT_VIEW: ViewType = 'datetime';
|
|
||||||
|
|
||||||
interface CustomTimePickerProps {
|
interface CustomTimePickerProps {
|
||||||
onSelect: (value: string) => void;
|
onSelect: (value: string) => void;
|
||||||
@@ -86,42 +81,11 @@ function CustomTimePicker({
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
|
|
||||||
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
|
|
||||||
|
|
||||||
const { timezone, browserTimezone } = useTimezone();
|
|
||||||
const activeTimezoneOffset = timezone.offset;
|
|
||||||
const isTimezoneOverridden = useMemo(
|
|
||||||
() => timezone.offset !== browserTimezone.offset,
|
|
||||||
[timezone, browserTimezone],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleViewChange = useCallback(
|
|
||||||
(newView: 'timezone' | 'datetime'): void => {
|
|
||||||
if (activeView !== newView) {
|
|
||||||
setActiveView(newView);
|
|
||||||
}
|
|
||||||
setOpen(true);
|
|
||||||
},
|
|
||||||
[activeView, setOpen],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [isOpenedFromFooter, setIsOpenedFromFooter] = useState(false);
|
|
||||||
|
|
||||||
const getSelectedTimeRangeLabel = (
|
const getSelectedTimeRangeLabel = (
|
||||||
selectedTime: string,
|
selectedTime: string,
|
||||||
selectedTimeValue: string,
|
selectedTimeValue: string,
|
||||||
): string => {
|
): string => {
|
||||||
if (selectedTime === 'custom') {
|
if (selectedTime === 'custom') {
|
||||||
// Convert the date range string to 12-hour format
|
|
||||||
const dates = selectedTimeValue.split(' - ');
|
|
||||||
if (dates.length === 2) {
|
|
||||||
const startDate = dayjs(dates[0], 'DD/MM/YYYY HH:mm');
|
|
||||||
const endDate = dayjs(dates[1], 'DD/MM/YYYY HH:mm');
|
|
||||||
|
|
||||||
return `${startDate.format('DD/MM/YYYY hh:mm A')} - ${endDate.format(
|
|
||||||
'DD/MM/YYYY hh:mm A',
|
|
||||||
)}`;
|
|
||||||
}
|
|
||||||
return selectedTimeValue;
|
return selectedTimeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,6 +120,7 @@ function CustomTimePicker({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
|
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
|
||||||
|
|
||||||
setSelectedTimePlaceholderValue(value);
|
setSelectedTimePlaceholderValue(value);
|
||||||
}, [selectedTime, selectedValue]);
|
}, [selectedTime, selectedValue]);
|
||||||
|
|
||||||
@@ -167,7 +132,6 @@ function CustomTimePicker({
|
|||||||
setOpen(newOpen);
|
setOpen(newOpen);
|
||||||
if (!newOpen) {
|
if (!newOpen) {
|
||||||
setCustomDTPickerVisible?.(false);
|
setCustomDTPickerVisible?.(false);
|
||||||
setActiveView('datetime');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -281,7 +245,6 @@ function CustomTimePicker({
|
|||||||
|
|
||||||
const handleFocus = (): void => {
|
const handleFocus = (): void => {
|
||||||
setIsInputFocused(true);
|
setIsInputFocused(true);
|
||||||
setActiveView('datetime');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlur = (): void => {
|
const handleBlur = (): void => {
|
||||||
@@ -318,10 +281,6 @@ function CustomTimePicker({
|
|||||||
handleGoLive={defaultTo(handleGoLive, noop)}
|
handleGoLive={defaultTo(handleGoLive, noop)}
|
||||||
options={items}
|
options={items}
|
||||||
selectedTime={selectedTime}
|
selectedTime={selectedTime}
|
||||||
activeView={activeView}
|
|
||||||
setActiveView={setActiveView}
|
|
||||||
setIsOpenedFromFooter={setIsOpenedFromFooter}
|
|
||||||
isOpenedFromFooter={isOpenedFromFooter}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
content
|
content
|
||||||
@@ -358,24 +317,12 @@ function CustomTimePicker({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
suffix={
|
suffix={
|
||||||
<>
|
<ChevronDown
|
||||||
{!!isTimezoneOverridden && activeTimezoneOffset && (
|
size={14}
|
||||||
<div
|
onClick={(): void => {
|
||||||
className="timezone-badge"
|
setOpen(!open);
|
||||||
onClick={(e): void => {
|
}}
|
||||||
e.stopPropagation();
|
/>
|
||||||
handleViewChange('timezone');
|
|
||||||
setIsOpenedFromFooter(false);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span>{activeTimezoneOffset}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<ChevronDown
|
|
||||||
size={14}
|
|
||||||
onClick={(): void => handleViewChange('datetime')}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import './CustomTimePicker.styles.scss';
|
import './CustomTimePicker.styles.scss';
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
@@ -10,13 +9,10 @@ import {
|
|||||||
Option,
|
Option,
|
||||||
RelativeDurationSuggestionOptions,
|
RelativeDurationSuggestionOptions,
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
} from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import { Clock, PenLine } from 'lucide-react';
|
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import { Dispatch, SetStateAction, useMemo } from 'react';
|
import { Dispatch, SetStateAction, useMemo } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import RangePickerModal from './RangePickerModal';
|
import RangePickerModal from './RangePickerModal';
|
||||||
import TimezonePicker from './TimezonePicker';
|
|
||||||
|
|
||||||
interface CustomTimePickerPopoverContentProps {
|
interface CustomTimePickerPopoverContentProps {
|
||||||
options: any[];
|
options: any[];
|
||||||
@@ -30,13 +26,8 @@ interface CustomTimePickerPopoverContentProps {
|
|||||||
onSelectHandler: (label: string, value: string) => void;
|
onSelectHandler: (label: string, value: string) => void;
|
||||||
handleGoLive: () => void;
|
handleGoLive: () => void;
|
||||||
selectedTime: string;
|
selectedTime: string;
|
||||||
activeView: 'datetime' | 'timezone';
|
|
||||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
|
||||||
isOpenedFromFooter: boolean;
|
|
||||||
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
function CustomTimePickerPopoverContent({
|
function CustomTimePickerPopoverContent({
|
||||||
options,
|
options,
|
||||||
setIsOpen,
|
setIsOpen,
|
||||||
@@ -46,18 +37,12 @@ function CustomTimePickerPopoverContent({
|
|||||||
onSelectHandler,
|
onSelectHandler,
|
||||||
handleGoLive,
|
handleGoLive,
|
||||||
selectedTime,
|
selectedTime,
|
||||||
activeView,
|
|
||||||
setActiveView,
|
|
||||||
isOpenedFromFooter,
|
|
||||||
setIsOpenedFromFooter,
|
|
||||||
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
}: CustomTimePickerPopoverContentProps): JSX.Element {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
const isLogsExplorerPage = useMemo(() => pathname === ROUTES.LOGS_EXPLORER, [
|
||||||
pathname,
|
pathname,
|
||||||
]);
|
]);
|
||||||
const { timezone } = useTimezone();
|
|
||||||
const activeTimezoneOffset = timezone.offset;
|
|
||||||
|
|
||||||
function getTimeChips(options: Option[]): JSX.Element {
|
function getTimeChips(options: Option[]): JSX.Element {
|
||||||
return (
|
return (
|
||||||
@@ -78,99 +63,55 @@ function CustomTimePickerPopoverContent({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTimezoneHintClick = (): void => {
|
|
||||||
setActiveView('timezone');
|
|
||||||
setIsOpenedFromFooter(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (activeView === 'timezone') {
|
|
||||||
return (
|
|
||||||
<div className="date-time-popover">
|
|
||||||
<TimezonePicker
|
|
||||||
setActiveView={setActiveView}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
isOpenedFromFooter={isOpenedFromFooter}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="date-time-popover">
|
||||||
<div className="date-time-popover">
|
<div className="date-time-options">
|
||||||
<div className="date-time-options">
|
{isLogsExplorerPage && (
|
||||||
{isLogsExplorerPage && (
|
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
||||||
<Button className="data-time-live" type="text" onClick={handleGoLive}>
|
Live
|
||||||
Live
|
</Button>
|
||||||
</Button>
|
)}
|
||||||
)}
|
{options.map((option) => (
|
||||||
{options.map((option) => (
|
<Button
|
||||||
<Button
|
type="text"
|
||||||
type="text"
|
key={option.label + option.value}
|
||||||
key={option.label + option.value}
|
onClick={(): void => {
|
||||||
onClick={(): void => {
|
onSelectHandler(option.label, option.value);
|
||||||
onSelectHandler(option.label, option.value);
|
}}
|
||||||
}}
|
className={cx(
|
||||||
className={cx(
|
'date-time-options-btn',
|
||||||
'date-time-options-btn',
|
customDateTimeVisible
|
||||||
customDateTimeVisible
|
? option.value === 'custom' && 'active'
|
||||||
? option.value === 'custom' && 'active'
|
: selectedTime === option.value && 'active',
|
||||||
: selectedTime === option.value && 'active',
|
)}
|
||||||
)}
|
|
||||||
>
|
|
||||||
{option.label}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={cx(
|
|
||||||
'relative-date-time',
|
|
||||||
selectedTime === 'custom' || customDateTimeVisible
|
|
||||||
? 'date-picker'
|
|
||||||
: 'relative-times',
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{selectedTime === 'custom' || customDateTimeVisible ? (
|
|
||||||
<RangePickerModal
|
|
||||||
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
onCustomDateHandler={onCustomDateHandler}
|
|
||||||
selectedTime={selectedTime}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="relative-times-container">
|
|
||||||
<div className="time-heading">RELATIVE TIMES</div>
|
|
||||||
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="date-time-popover__footer">
|
|
||||||
<div className="timezone-container">
|
|
||||||
<Clock
|
|
||||||
color={Color.BG_VANILLA_400}
|
|
||||||
className="timezone-container__clock-icon"
|
|
||||||
height={12}
|
|
||||||
width={12}
|
|
||||||
/>
|
|
||||||
<span className="timezone__icon">Current timezone</span>
|
|
||||||
<div>⎯</div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="timezone"
|
|
||||||
onClick={handleTimezoneHintClick}
|
|
||||||
>
|
>
|
||||||
<span>{activeTimezoneOffset}</span>
|
{option.label}
|
||||||
<PenLine
|
</Button>
|
||||||
color={Color.BG_VANILLA_100}
|
))}
|
||||||
className="timezone__icon"
|
|
||||||
size={10}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div
|
||||||
|
className={cx(
|
||||||
|
'relative-date-time',
|
||||||
|
selectedTime === 'custom' || customDateTimeVisible
|
||||||
|
? 'date-picker'
|
||||||
|
: 'relative-times',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{selectedTime === 'custom' || customDateTimeVisible ? (
|
||||||
|
<RangePickerModal
|
||||||
|
setCustomDTPickerVisible={setCustomDTPickerVisible}
|
||||||
|
setIsOpen={setIsOpen}
|
||||||
|
onCustomDateHandler={onCustomDateHandler}
|
||||||
|
selectedTime={selectedTime}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="relative-times-container">
|
||||||
|
<div className="time-heading">RELATIVE TIMES</div>
|
||||||
|
<div>{getTimeChips(RelativeDurationSuggestionOptions)}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ import './RangePickerModal.styles.scss';
|
|||||||
import { DatePicker } from 'antd';
|
import { DatePicker } from 'antd';
|
||||||
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
import { DateTimeRangeType } from 'container/TopNav/CustomDateTimeModal';
|
||||||
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
import { LexicalContext } from 'container/TopNav/DateTimeSelectionV2/config';
|
||||||
import dayjs from 'dayjs';
|
import dayjs, { Dayjs } from 'dayjs';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import { Dispatch, SetStateAction } from 'react';
|
import { Dispatch, SetStateAction } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
@@ -32,10 +31,7 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Using any type here because antd's DatePicker expects its own internal Dayjs type
|
const disabledDate = (current: Dayjs): boolean => {
|
||||||
// which conflicts with our project's Dayjs type that has additional plugins (tz, utc etc).
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
|
|
||||||
const disabledDate = (current: any): boolean => {
|
|
||||||
const currentDay = dayjs(current);
|
const currentDay = dayjs(current);
|
||||||
return currentDay.isAfter(dayjs());
|
return currentDay.isAfter(dayjs());
|
||||||
};
|
};
|
||||||
@@ -53,22 +49,16 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
|
|||||||
}
|
}
|
||||||
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
|
onCustomDateHandler(date_time, LexicalContext.CUSTOM_DATE_PICKER);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { timezone } = useTimezone();
|
|
||||||
return (
|
return (
|
||||||
<div className="custom-date-picker">
|
<div className="custom-date-picker">
|
||||||
<RangePicker
|
<RangePicker
|
||||||
disabledDate={disabledDate}
|
disabledDate={disabledDate}
|
||||||
allowClear
|
allowClear
|
||||||
showTime
|
showTime
|
||||||
format="YYYY-MM-DD hh:mm A"
|
|
||||||
onOk={onModalOkHandler}
|
onOk={onModalOkHandler}
|
||||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||||
{...(selectedTime === 'custom' && {
|
{...(selectedTime === 'custom' && {
|
||||||
defaultValue: [
|
defaultValue: [dayjs(minTime / 1000000), dayjs(maxTime / 1000000)],
|
||||||
dayjs(minTime / 1000000).tz(timezone.value),
|
|
||||||
dayjs(maxTime / 1000000).tz(timezone.value),
|
|
||||||
],
|
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
// Variables
|
|
||||||
$font-family: 'Inter';
|
|
||||||
$item-spacing: 8px;
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--border-color: var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
--border-color: var(--bg-vanilla-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mixins
|
|
||||||
@mixin text-style-base {
|
|
||||||
font-family: $font-family;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin flex-center {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timezone-picker {
|
|
||||||
width: 532px;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
font-family: $font-family;
|
|
||||||
|
|
||||||
&__search {
|
|
||||||
@include flex-center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 12px 14px;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input-container {
|
|
||||||
@include flex-center;
|
|
||||||
gap: 6px;
|
|
||||||
width: -webkit-fill-available;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
@include text-style-base;
|
|
||||||
width: 100%;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
padding: 0;
|
|
||||||
&.ant-input:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::placeholder {
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__esc-key {
|
|
||||||
@include text-style-base;
|
|
||||||
font-size: 8px;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
letter-spacing: -0.04px;
|
|
||||||
border-radius: 2.286px;
|
|
||||||
border: 1.143px solid var(--bg-ink-200);
|
|
||||||
border-bottom-width: 2.286px;
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
padding: 0 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__list {
|
|
||||||
max-height: 310px;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__item {
|
|
||||||
@include flex-center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 7.5px 6px 7.5px $item-spacing;
|
|
||||||
margin: 4px $item-spacing;
|
|
||||||
cursor: pointer;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
width: -webkit-fill-available;
|
|
||||||
color: var(--bg-vanilla-400);
|
|
||||||
font-family: $font-family;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.selected {
|
|
||||||
border-radius: 2px;
|
|
||||||
background: rgba(171, 189, 255, 0.04);
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-divider {
|
|
||||||
position: relative;
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -2px;
|
|
||||||
left: -$item-spacing;
|
|
||||||
right: -$item-spacing;
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__name {
|
|
||||||
@include text-style-base;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__offset {
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 16px;
|
|
||||||
letter-spacing: -0.06px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.timezone-name-wrapper {
|
|
||||||
@include flex-center;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
&__selected-icon {
|
|
||||||
height: 15px;
|
|
||||||
width: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.timezone-picker {
|
|
||||||
&__search {
|
|
||||||
.search-icon {
|
|
||||||
stroke: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&__input {
|
|
||||||
color: var(--bg-ink-100);
|
|
||||||
}
|
|
||||||
&__esc-key {
|
|
||||||
background-color: var(--bg-vanilla-100);
|
|
||||||
border-color: var(--bg-vanilla-400);
|
|
||||||
color: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
&__item {
|
|
||||||
color: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
&__offset {
|
|
||||||
color: var(--bg-ink-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.timezone-name-wrapper {
|
|
||||||
&__selected-icon {
|
|
||||||
.check-icon {
|
|
||||||
stroke: var(--bg-ink-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,201 +0,0 @@
|
|||||||
import './TimezonePicker.styles.scss';
|
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Input } from 'antd';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
|
|
||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
|
||||||
import { Check, Search } from 'lucide-react';
|
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import {
|
|
||||||
Dispatch,
|
|
||||||
SetStateAction,
|
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
|
|
||||||
import { Timezone, TIMEZONE_DATA } from './timezoneUtils';
|
|
||||||
|
|
||||||
interface SearchBarProps {
|
|
||||||
value: string;
|
|
||||||
onChange: (value: string) => void;
|
|
||||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
|
||||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
|
||||||
isOpenedFromFooter: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TimezoneItemProps {
|
|
||||||
timezone: Timezone;
|
|
||||||
isSelected?: boolean;
|
|
||||||
onClick?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ICON_SIZE = 14;
|
|
||||||
|
|
||||||
function SearchBar({
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
setIsOpen,
|
|
||||||
setActiveView,
|
|
||||||
isOpenedFromFooter = false,
|
|
||||||
}: SearchBarProps): JSX.Element {
|
|
||||||
const handleKeyDown = useCallback(
|
|
||||||
(e: React.KeyboardEvent): void => {
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
if (isOpenedFromFooter) {
|
|
||||||
setActiveView('datetime');
|
|
||||||
} else {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setActiveView, setIsOpen, isOpenedFromFooter],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="timezone-picker__search">
|
|
||||||
<div className="timezone-picker__input-container">
|
|
||||||
<Search
|
|
||||||
color={Color.BG_VANILLA_400}
|
|
||||||
className="search-icon"
|
|
||||||
height={ICON_SIZE}
|
|
||||||
width={ICON_SIZE}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
type="text"
|
|
||||||
className="timezone-picker__input"
|
|
||||||
placeholder="Search timezones..."
|
|
||||||
value={value}
|
|
||||||
onChange={(e): void => onChange(e.target.value)}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
tabIndex={0}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<kbd className="timezone-picker__esc-key">esc</kbd>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TimezoneItem({
|
|
||||||
timezone,
|
|
||||||
isSelected = false,
|
|
||||||
onClick,
|
|
||||||
}: TimezoneItemProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={cx('timezone-picker__item', {
|
|
||||||
selected: isSelected,
|
|
||||||
'has-divider': timezone.hasDivider,
|
|
||||||
})}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
<div className="timezone-name-wrapper">
|
|
||||||
<div className="timezone-name-wrapper__selected-icon">
|
|
||||||
{isSelected && (
|
|
||||||
<Check
|
|
||||||
className="check-icon"
|
|
||||||
color={Color.BG_VANILLA_100}
|
|
||||||
height={ICON_SIZE}
|
|
||||||
width={ICON_SIZE}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="timezone-picker__name">{timezone.name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="timezone-picker__offset">{timezone.offset}</div>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TimezoneItem.defaultProps = {
|
|
||||||
isSelected: false,
|
|
||||||
onClick: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface TimezonePickerProps {
|
|
||||||
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
|
|
||||||
setIsOpen: Dispatch<SetStateAction<boolean>>;
|
|
||||||
isOpenedFromFooter: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TimezonePicker({
|
|
||||||
setActiveView,
|
|
||||||
setIsOpen,
|
|
||||||
isOpenedFromFooter,
|
|
||||||
}: TimezonePickerProps): JSX.Element {
|
|
||||||
console.log({ isOpenedFromFooter });
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
|
||||||
const { timezone, updateTimezone } = useTimezone();
|
|
||||||
const [selectedTimezone, setSelectedTimezone] = useState<string>(
|
|
||||||
timezone.name ?? TIMEZONE_DATA[0].name,
|
|
||||||
);
|
|
||||||
|
|
||||||
const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => {
|
|
||||||
const normalizedSearch = searchTerm.toLowerCase();
|
|
||||||
return TIMEZONE_DATA.filter(
|
|
||||||
(tz) =>
|
|
||||||
tz.name.toLowerCase().includes(normalizedSearch) ||
|
|
||||||
tz.offset.toLowerCase().includes(normalizedSearch) ||
|
|
||||||
tz.searchIndex.toLowerCase().includes(normalizedSearch),
|
|
||||||
);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleCloseTimezonePicker = useCallback(() => {
|
|
||||||
if (isOpenedFromFooter) {
|
|
||||||
setActiveView('datetime');
|
|
||||||
} else {
|
|
||||||
setIsOpen(false);
|
|
||||||
}
|
|
||||||
}, [isOpenedFromFooter, setActiveView, setIsOpen]);
|
|
||||||
|
|
||||||
const handleTimezoneSelect = useCallback(
|
|
||||||
(timezone: Timezone) => {
|
|
||||||
setSelectedTimezone(timezone.name);
|
|
||||||
updateTimezone(timezone);
|
|
||||||
handleCloseTimezonePicker();
|
|
||||||
setIsOpen(false);
|
|
||||||
},
|
|
||||||
[handleCloseTimezonePicker, setIsOpen, updateTimezone],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Register keyboard shortcuts
|
|
||||||
const { registerShortcut, deregisterShortcut } = useKeyboardHotkeys();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
registerShortcut(
|
|
||||||
TimezonePickerShortcuts.CloseTimezonePicker,
|
|
||||||
handleCloseTimezonePicker,
|
|
||||||
);
|
|
||||||
|
|
||||||
return (): void => {
|
|
||||||
deregisterShortcut(TimezonePickerShortcuts.CloseTimezonePicker);
|
|
||||||
};
|
|
||||||
}, [deregisterShortcut, handleCloseTimezonePicker, registerShortcut]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="timezone-picker">
|
|
||||||
<SearchBar
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={setSearchTerm}
|
|
||||||
setIsOpen={setIsOpen}
|
|
||||||
setActiveView={setActiveView}
|
|
||||||
isOpenedFromFooter={isOpenedFromFooter}
|
|
||||||
/>
|
|
||||||
<div className="timezone-picker__list">
|
|
||||||
{getFilteredTimezones(searchTerm).map((timezone) => (
|
|
||||||
<TimezoneItem
|
|
||||||
key={timezone.value}
|
|
||||||
timezone={timezone}
|
|
||||||
isSelected={timezone.name === selectedTimezone}
|
|
||||||
onClick={(): void => handleTimezoneSelect(timezone)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default TimezonePicker;
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
import dayjs from 'dayjs';
|
|
||||||
import timezone from 'dayjs/plugin/timezone';
|
|
||||||
import utc from 'dayjs/plugin/utc';
|
|
||||||
|
|
||||||
dayjs.extend(utc);
|
|
||||||
dayjs.extend(timezone);
|
|
||||||
|
|
||||||
export interface Timezone {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
offset: string;
|
|
||||||
searchIndex: string;
|
|
||||||
hasDivider?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TIMEZONE_TYPES = {
|
|
||||||
BROWSER: 'BROWSER',
|
|
||||||
UTC: 'UTC',
|
|
||||||
STANDARD: 'STANDARD',
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
|
|
||||||
|
|
||||||
export const UTC_TIMEZONE: Timezone = {
|
|
||||||
name: 'Coordinated Universal Time — UTC, GMT',
|
|
||||||
value: 'UTC',
|
|
||||||
offset: 'UTC',
|
|
||||||
searchIndex: 'UTC',
|
|
||||||
hasDivider: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeTimezoneName = (timezone: string): string => {
|
|
||||||
// https://github.com/tc39/proposal-temporal/issues/1076
|
|
||||||
if (timezone === 'Asia/Calcutta') {
|
|
||||||
return 'Asia/Kolkata';
|
|
||||||
}
|
|
||||||
return timezone;
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatOffset = (offsetMinutes: number): string => {
|
|
||||||
if (offsetMinutes === 0) return 'UTC';
|
|
||||||
|
|
||||||
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
|
|
||||||
const minutes = Math.abs(offsetMinutes) % 60;
|
|
||||||
const sign = offsetMinutes > 0 ? '+' : '-';
|
|
||||||
|
|
||||||
return `UTC ${sign} ${hours}${
|
|
||||||
minutes ? `:${minutes.toString().padStart(2, '0')}` : ':00'
|
|
||||||
}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const createTimezoneEntry = (
|
|
||||||
name: string,
|
|
||||||
offsetMinutes: number,
|
|
||||||
type: TimezoneType = TIMEZONE_TYPES.STANDARD,
|
|
||||||
hasDivider = false,
|
|
||||||
): Timezone => {
|
|
||||||
const offset = formatOffset(offsetMinutes);
|
|
||||||
let value = name;
|
|
||||||
let displayName = name;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case TIMEZONE_TYPES.BROWSER:
|
|
||||||
displayName = `Browser time — ${name}`;
|
|
||||||
value = name;
|
|
||||||
break;
|
|
||||||
case TIMEZONE_TYPES.UTC:
|
|
||||||
displayName = 'Coordinated Universal Time — UTC, GMT';
|
|
||||||
value = 'UTC';
|
|
||||||
break;
|
|
||||||
case TIMEZONE_TYPES.STANDARD:
|
|
||||||
displayName = name;
|
|
||||||
value = name;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error(`Invalid timezone type: ${type}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: displayName,
|
|
||||||
value,
|
|
||||||
offset,
|
|
||||||
searchIndex: offset.replace(/ /g, ''),
|
|
||||||
...(hasDivider && { hasDivider }),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const getOffsetByTimezone = (timezone: string): number => {
|
|
||||||
const dayjsTimezone = dayjs().tz(timezone);
|
|
||||||
return dayjsTimezone.utcOffset();
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getBrowserTimezone = (): Timezone => {
|
|
||||||
const browserTz = dayjs.tz.guess();
|
|
||||||
const normalizedTz = normalizeTimezoneName(browserTz);
|
|
||||||
const browserOffset = getOffsetByTimezone(normalizedTz);
|
|
||||||
return createTimezoneEntry(
|
|
||||||
normalizedTz,
|
|
||||||
browserOffset,
|
|
||||||
TIMEZONE_TYPES.BROWSER,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const filterAndSortTimezones = (
|
|
||||||
allTimezones: string[],
|
|
||||||
browserTzName?: string,
|
|
||||||
includeEtcTimezones = false,
|
|
||||||
): Timezone[] =>
|
|
||||||
allTimezones
|
|
||||||
.filter((tz) => {
|
|
||||||
const isNotBrowserTz = tz !== browserTzName;
|
|
||||||
const isNotEtcTz = includeEtcTimezones || !tz.startsWith('Etc/');
|
|
||||||
return isNotBrowserTz && isNotEtcTz;
|
|
||||||
})
|
|
||||||
.sort((a, b) => a.localeCompare(b))
|
|
||||||
.map((tz) => {
|
|
||||||
const normalizedTz = normalizeTimezoneName(tz);
|
|
||||||
const offset = getOffsetByTimezone(normalizedTz);
|
|
||||||
return createTimezoneEntry(normalizedTz, offset);
|
|
||||||
});
|
|
||||||
|
|
||||||
const generateTimezoneData = (includeEtcTimezones = false): Timezone[] => {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const allTimezones = (Intl as any).supportedValuesOf('timeZone');
|
|
||||||
const timezones: Timezone[] = [];
|
|
||||||
|
|
||||||
// Add browser timezone
|
|
||||||
const browserTzObject = getBrowserTimezone();
|
|
||||||
timezones.push(browserTzObject);
|
|
||||||
|
|
||||||
// Add UTC timezone with divider
|
|
||||||
timezones.push(UTC_TIMEZONE);
|
|
||||||
|
|
||||||
timezones.push(
|
|
||||||
...filterAndSortTimezones(
|
|
||||||
allTimezones,
|
|
||||||
browserTzObject.value,
|
|
||||||
includeEtcTimezones,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return timezones;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTimezoneObjectByTimezoneString = (
|
|
||||||
timezone: string,
|
|
||||||
): Timezone => {
|
|
||||||
const utcOffset = getOffsetByTimezone(timezone);
|
|
||||||
return createTimezoneEntry(timezone, utcOffset);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TIMEZONE_DATA = generateTimezoneData();
|
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
_adapters,
|
|
||||||
BarController,
|
BarController,
|
||||||
BarElement,
|
BarElement,
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@@ -19,10 +18,8 @@ import {
|
|||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
import { generateGridTitle } from 'container/GridPanelSwitch/utils';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import isEqual from 'lodash-es/isEqual';
|
import isEqual from 'lodash-es/isEqual';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
memo,
|
memo,
|
||||||
@@ -65,17 +62,6 @@ Chart.register(
|
|||||||
|
|
||||||
Tooltip.positioners.custom = TooltipPositionHandler;
|
Tooltip.positioners.custom = TooltipPositionHandler;
|
||||||
|
|
||||||
// Map of Chart.js time formats to dayjs format strings
|
|
||||||
const formatMap = {
|
|
||||||
'HH:mm:ss': 'HH:mm:ss',
|
|
||||||
'HH:mm': 'HH:mm',
|
|
||||||
'MM/DD HH:mm': 'MM/DD HH:mm',
|
|
||||||
'MM/dd HH:mm': 'MM/DD HH:mm',
|
|
||||||
'MM/DD': 'MM/DD',
|
|
||||||
'YY-MM': 'YY-MM',
|
|
||||||
YY: 'YY',
|
|
||||||
};
|
|
||||||
|
|
||||||
const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
@@ -94,13 +80,11 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
dragSelectColor,
|
dragSelectColor,
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
): JSX.Element => {
|
): JSX.Element => {
|
||||||
const nearestDatasetIndex = useRef<null | number>(null);
|
const nearestDatasetIndex = useRef<null | number>(null);
|
||||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||||
const isDarkMode = useIsDarkMode();
|
const isDarkMode = useIsDarkMode();
|
||||||
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
const gridTitle = useMemo(() => generateGridTitle(title), [title]);
|
||||||
const { timezone } = useTimezone();
|
|
||||||
|
|
||||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||||
@@ -128,22 +112,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
return 'rgba(231,233,237,0.8)';
|
return 'rgba(231,233,237,0.8)';
|
||||||
}, [currentTheme]);
|
}, [currentTheme]);
|
||||||
|
|
||||||
// Override Chart.js date adapter to use dayjs with timezone support
|
|
||||||
useEffect(() => {
|
|
||||||
_adapters._date.override({
|
|
||||||
format(time: number | Date, fmt: string) {
|
|
||||||
const dayjsTime = dayjs(time).tz(timezone.value);
|
|
||||||
const format = formatMap[fmt as keyof typeof formatMap];
|
|
||||||
if (!format) {
|
|
||||||
console.warn(`Missing datetime format for ${fmt}`);
|
|
||||||
return dayjsTime.format('YYYY-MM-DD HH:mm:ss'); // fallback format
|
|
||||||
}
|
|
||||||
|
|
||||||
return dayjsTime.format(format);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [timezone]);
|
|
||||||
|
|
||||||
const buildChart = useCallback(() => {
|
const buildChart = useCallback(() => {
|
||||||
if (lineChartRef.current !== undefined) {
|
if (lineChartRef.current !== undefined) {
|
||||||
lineChartRef.current.destroy();
|
lineChartRef.current.destroy();
|
||||||
@@ -164,7 +132,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
isStacked,
|
isStacked,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
data,
|
data,
|
||||||
timezone,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const chartHasData = hasData(data);
|
const chartHasData = hasData(data);
|
||||||
@@ -199,7 +166,6 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
|
|||||||
isStacked,
|
isStacked,
|
||||||
onClickHandler,
|
onClickHandler,
|
||||||
data,
|
data,
|
||||||
timezone,
|
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
|
import { Chart, ChartConfiguration, ChartData, Color } from 'chart.js';
|
||||||
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
import * as chartjsAdapter from 'chartjs-adapter-date-fns';
|
||||||
import { Timezone } from 'components/CustomTimePicker/timezoneUtils';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { MutableRefObject } from 'react';
|
import { MutableRefObject } from 'react';
|
||||||
|
|
||||||
@@ -51,7 +50,6 @@ export const getGraphOptions = (
|
|||||||
isStacked: boolean | undefined,
|
isStacked: boolean | undefined,
|
||||||
onClickHandler: GraphOnClickHandler | undefined,
|
onClickHandler: GraphOnClickHandler | undefined,
|
||||||
data: ChartData,
|
data: ChartData,
|
||||||
timezone: Timezone,
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
): CustomChartOptions => ({
|
): CustomChartOptions => ({
|
||||||
animation: {
|
animation: {
|
||||||
@@ -99,7 +97,7 @@ export const getGraphOptions = (
|
|||||||
callbacks: {
|
callbacks: {
|
||||||
title(context): string | string[] {
|
title(context): string | string[] {
|
||||||
const date = dayjs(context[0].parsed.x);
|
const date = dayjs(context[0].parsed.x);
|
||||||
return date.tz(timezone.value).format('MMM DD, YYYY, HH:mm:ss');
|
return date.format('MMM DD, YYYY, HH:mm:ss');
|
||||||
},
|
},
|
||||||
label(context): string | string[] {
|
label(context): string | string[] {
|
||||||
let label = context.dataset.label || '';
|
let label = context.dataset.label || '';
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
.host-containers {
|
|
||||||
gap: 24px;
|
|
||||||
height: 60vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.infra-container-card-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-status-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-card-text {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
width: 400px;
|
|
||||||
font-family: 'Inter';
|
|
||||||
margin-top: 12px;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-working-msg {
|
|
||||||
display: flex;
|
|
||||||
width: 400px;
|
|
||||||
padding: 12px;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(171, 189, 255, 0.04);
|
|
||||||
|
|
||||||
.ant-space {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-contact-support-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.infra-container-card-text {
|
|
||||||
color: var(--text-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import './Containers.styles.scss';
|
|
||||||
|
|
||||||
import { Space, Typography } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import WaitlistFragment from '../WaitlistFragment/WaitlistFragment';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
function Containers(): JSX.Element {
|
|
||||||
const { t } = useTranslation(['infraMonitoring']);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space direction="vertical" className="host-containers" size={24}>
|
|
||||||
<div className="infra-container-card-container">
|
|
||||||
<div className="dev-status-container">
|
|
||||||
<div className="infra-container-card">
|
|
||||||
<img
|
|
||||||
src="/Icons/infraContainers.svg"
|
|
||||||
alt="infra-container"
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Text className="infra-container-card-text">
|
|
||||||
{t('containers_visualization_message')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="infra-container-working-msg">
|
|
||||||
<Space>
|
|
||||||
<img src="/Icons/broom.svg" alt="broom" width={24} height={24} />
|
|
||||||
<Text className="infra-container-card-text">{t('working_message')}</Text>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<WaitlistFragment entityType="containers" />
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Containers;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import { HostData } from 'api/infraMonitoring/getHostLists';
|
|
||||||
|
|
||||||
export type HostDetailProps = {
|
|
||||||
host: HostData | null;
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
};
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
.host-metric-traces {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.host-metric-traces-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400) !important;
|
|
||||||
background-color: var(--bg-ink-300) !important;
|
|
||||||
|
|
||||||
input {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag .ant-typography {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metric-traces-table {
|
|
||||||
.ant-table-content {
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
padding: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 18px;
|
|
||||||
|
|
||||||
background: rgba(171, 189, 255, 0.01);
|
|
||||||
border-bottom: none;
|
|
||||||
|
|
||||||
color: var(--Vanilla-400, #c0c1c3);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 18px; /* 163.636% */
|
|
||||||
letter-spacing: 0.44px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 20px;
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
background: rgba(171, 189, 255, 0.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hostname-column-value {
|
|
||||||
color: var(--bg-vanilla-100);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-cell {
|
|
||||||
.active-tag {
|
|
||||||
color: var(--bg-forest-500);
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
.ant-progress-bg {
|
|
||||||
height: 8px !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:first-child {
|
|
||||||
text-align: justify;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(2) {
|
|
||||||
padding-left: 16px;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:nth-child(n + 3) {
|
|
||||||
padding-right: 24px;
|
|
||||||
}
|
|
||||||
.column-header-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
.ant-table-tbody > tr > td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead
|
|
||||||
> tr
|
|
||||||
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-empty-normal {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-container::after {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.host-metric-traces-header {
|
|
||||||
.filter-section {
|
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-color: var(--bg-vanilla-300) !important;
|
|
||||||
background-color: var(--bg-vanilla-100) !important;
|
|
||||||
color: var(--bg-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metric-traces-table {
|
|
||||||
.ant-table {
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-thead > tr > th:has(.hostname-column-header) {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--bg-ink-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-cell:has(.hostname-column-value) {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.hostname-column-value {
|
|
||||||
color: var(--bg-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-tbody > tr:hover > td {
|
|
||||||
background: rgba(0, 0, 0, 0.04);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
import './HostMetricTraces.styles.scss';
|
|
||||||
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
|
||||||
import { QueryParams } from 'constants/query';
|
|
||||||
import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
|
|
||||||
import NoLogs from 'container/NoLogs/NoLogs';
|
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
|
||||||
import { ErrorText } from 'container/TimeSeriesView/styles';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import TraceExplorerControls from 'container/TracesExplorer/Controls';
|
|
||||||
import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
|
|
||||||
import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { Pagination } from 'hooks/queryPagination';
|
|
||||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
|
|
||||||
import { getHostTracesQueryPayload, selectedColumns } from './constants';
|
|
||||||
import { getListColumns } from './utils';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
handleChangeTracesFilters: (value: IBuilderQuery['filters']) => void;
|
|
||||||
tracesFilters: IBuilderQuery['filters'];
|
|
||||||
selectedInterval: Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
function HostMetricTraces({
|
|
||||||
timeRange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
handleTimeChange,
|
|
||||||
handleChangeTracesFilters,
|
|
||||||
tracesFilters,
|
|
||||||
selectedInterval,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const [traces, setTraces] = useState<any[]>([]);
|
|
||||||
const [offset] = useState<number>(0);
|
|
||||||
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
const updatedCurrentQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...currentQuery.builder.queryData[0],
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
|
||||||
|
|
||||||
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
|
|
||||||
QueryParams.pagination,
|
|
||||||
);
|
|
||||||
|
|
||||||
const queryPayload = useMemo(
|
|
||||||
() =>
|
|
||||||
getHostTracesQueryPayload(
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
paginationQueryData?.offset || offset,
|
|
||||||
tracesFilters,
|
|
||||||
),
|
|
||||||
[
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
offset,
|
|
||||||
tracesFilters,
|
|
||||||
paginationQueryData,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
|
||||||
queryKey: [
|
|
||||||
'hostMetricTraces',
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
offset,
|
|
||||||
tracesFilters,
|
|
||||||
DEFAULT_ENTITY_VERSION,
|
|
||||||
paginationQueryData,
|
|
||||||
],
|
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
|
||||||
enabled: !!queryPayload,
|
|
||||||
});
|
|
||||||
|
|
||||||
const traceListColumns = getListColumns(selectedColumns);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.payload?.data?.newResult?.data?.result) {
|
|
||||||
const currentData = data.payload.data.newResult.data.result;
|
|
||||||
if (currentData.length > 0 && currentData[0].list) {
|
|
||||||
if (offset === 0) {
|
|
||||||
setTraces(currentData[0].list ?? []);
|
|
||||||
} else {
|
|
||||||
setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data, offset]);
|
|
||||||
|
|
||||||
const isDataEmpty =
|
|
||||||
!isLoading && !isFetching && !isError && traces.length === 0;
|
|
||||||
const hasAdditionalFilters = tracesFilters.items.length > 1;
|
|
||||||
|
|
||||||
const totalCount =
|
|
||||||
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="host-metric-traces">
|
|
||||||
<div className="host-metric-traces-header">
|
|
||||||
<div className="filter-section">
|
|
||||||
{query && (
|
|
||||||
<QueryBuilderSearch
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeTracesFilters}
|
|
||||||
disableNavigationShortcuts
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>}
|
|
||||||
|
|
||||||
{isLoading && traces.length === 0 && <TracesLoading />}
|
|
||||||
|
|
||||||
{isDataEmpty && !hasAdditionalFilters && (
|
|
||||||
<NoLogs dataSource={DataSource.TRACES} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isDataEmpty && hasAdditionalFilters && (
|
|
||||||
<EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isError && traces.length > 0 && (
|
|
||||||
<div className="host-metric-traces-table">
|
|
||||||
<TraceExplorerControls
|
|
||||||
isLoading={isFetching}
|
|
||||||
totalCount={totalCount}
|
|
||||||
perPageOptions={PER_PAGE_OPTIONS}
|
|
||||||
showSizeChanger={false}
|
|
||||||
/>
|
|
||||||
<ResizeTable
|
|
||||||
tableLayout="fixed"
|
|
||||||
pagination={false}
|
|
||||||
scroll={{ x: true }}
|
|
||||||
loading={isFetching}
|
|
||||||
dataSource={traces}
|
|
||||||
columns={traceListColumns}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HostMetricTraces;
|
|
||||||
@@ -1,200 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import {
|
|
||||||
BaseAutocompleteData,
|
|
||||||
DataTypes,
|
|
||||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { nanoToMilli } from 'utils/timeUtils';
|
|
||||||
|
|
||||||
export const columns = [
|
|
||||||
{
|
|
||||||
dataIndex: 'timestamp',
|
|
||||||
key: 'timestamp',
|
|
||||||
title: 'Timestamp',
|
|
||||||
width: 200,
|
|
||||||
render: (timestamp: string): string => new Date(timestamp).toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Service Name',
|
|
||||||
dataIndex: ['data', 'serviceName'],
|
|
||||||
key: 'serviceName-string-tag',
|
|
||||||
width: 150,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Name',
|
|
||||||
dataIndex: ['data', 'name'],
|
|
||||||
key: 'name-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Duration',
|
|
||||||
dataIndex: ['data', 'durationNano'],
|
|
||||||
key: 'durationNano-float64-tag',
|
|
||||||
width: 145,
|
|
||||||
render: (duration: number): string => `${nanoToMilli(duration)}ms`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'HTTP Method',
|
|
||||||
dataIndex: ['data', 'httpMethod'],
|
|
||||||
key: 'httpMethod-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Status Code',
|
|
||||||
dataIndex: ['data', 'responseStatusCode'],
|
|
||||||
key: 'responseStatusCode-string-tag',
|
|
||||||
width: 145,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const selectedColumns: BaseAutocompleteData[] = [
|
|
||||||
{
|
|
||||||
key: 'timestamp',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'serviceName',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'durationNano',
|
|
||||||
dataType: DataTypes.Float64,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'httpMethod',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'responseStatusCode',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export const getHostTracesQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
offset = 0,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
query: {
|
|
||||||
promql: [],
|
|
||||||
clickhouse_sql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.EMPTY,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: '572f1d91-6ac0-46c0-b726-c21488b34434',
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
params: {
|
|
||||||
dataSource: DataSource.TRACES,
|
|
||||||
},
|
|
||||||
tableParams: {
|
|
||||||
pagination: {
|
|
||||||
limit: 10,
|
|
||||||
offset,
|
|
||||||
},
|
|
||||||
selectColumns: [
|
|
||||||
{
|
|
||||||
key: 'serviceName',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'serviceName--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'name',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'name--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'durationNano',
|
|
||||||
dataType: 'float64',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'durationNano--float64--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'httpMethod',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'httpMethod--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'responseStatusCode',
|
|
||||||
dataType: 'string',
|
|
||||||
type: 'tag',
|
|
||||||
isColumn: true,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'responseStatusCode--string--tag--true',
|
|
||||||
isIndexed: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import { Tag, Typography } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/es/table';
|
|
||||||
import { getMs } from 'container/Trace/Filters/Panel/PanelBody/Duration/util';
|
|
||||||
import {
|
|
||||||
BlockLink,
|
|
||||||
getTraceLink,
|
|
||||||
} from 'container/TracesExplorer/ListView/utils';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { RowData } from 'lib/query/createTableColumnsFromQuery';
|
|
||||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
|
|
||||||
const keyToLabelMap: Record<string, string> = {
|
|
||||||
timestamp: 'Timestamp',
|
|
||||||
serviceName: 'Service Name',
|
|
||||||
name: 'Name',
|
|
||||||
durationNano: 'Duration',
|
|
||||||
httpMethod: 'HTTP Method',
|
|
||||||
responseStatusCode: 'Status Code',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getListColumns = (
|
|
||||||
selectedColumns: BaseAutocompleteData[],
|
|
||||||
): ColumnsType<RowData> => {
|
|
||||||
const columns: ColumnsType<RowData> =
|
|
||||||
selectedColumns.map(({ dataType, key, type }) => ({
|
|
||||||
title: keyToLabelMap[key],
|
|
||||||
dataIndex: key,
|
|
||||||
key: `${key}-${dataType}-${type}`,
|
|
||||||
width: 145,
|
|
||||||
render: (value, item): JSX.Element => {
|
|
||||||
const itemData = item.data as any;
|
|
||||||
|
|
||||||
if (key === 'timestamp') {
|
|
||||||
const date =
|
|
||||||
typeof value === 'string'
|
|
||||||
? dayjs(value).format('YYYY-MM-DD HH:mm:ss.SSS')
|
|
||||||
: dayjs(value / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BlockLink to={getTraceLink(item)} openInNewTab>
|
|
||||||
<Typography.Text>{date}</Typography.Text>
|
|
||||||
</BlockLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === '') {
|
|
||||||
return (
|
|
||||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
|
||||||
<Typography data-testid={key}>N/A</Typography>
|
|
||||||
</BlockLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'httpMethod' || key === 'responseStatusCode') {
|
|
||||||
return (
|
|
||||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
|
||||||
<Tag data-testid={key} color="magenta">
|
|
||||||
{itemData[key]}
|
|
||||||
</Tag>
|
|
||||||
</BlockLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'durationNano') {
|
|
||||||
const durationNano = itemData[key];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BlockLink to={getTraceLink(item)} openInNewTab>
|
|
||||||
<Typography data-testid={key}>{getMs(durationNano)}ms</Typography>
|
|
||||||
</BlockLink>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BlockLink to={getTraceLink(itemData)} openInNewTab>
|
|
||||||
<Typography data-testid={key}>{itemData[key]}</Typography>
|
|
||||||
</BlockLink>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
responsive: ['md'],
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
};
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
.host-detail-drawer {
|
|
||||||
border-left: 1px solid var(--bg-slate-500);
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
|
||||||
|
|
||||||
.ant-drawer-header {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-bottom: none;
|
|
||||||
|
|
||||||
align-items: stretch;
|
|
||||||
|
|
||||||
border-bottom: 1px solid var(--bg-slate-500);
|
|
||||||
background: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-close {
|
|
||||||
margin-inline-end: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-drawer-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
font-family: 'Geist Mono';
|
|
||||||
font-size: 14px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding-top: var(--padding-1);
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-detail-drawer__host {
|
|
||||||
.host-details-grid {
|
|
||||||
.labels-row,
|
|
||||||
.values-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr;
|
|
||||||
gap: 30px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.labels-row {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-details-metadata-label {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 11px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 18px; /* 163.636% */
|
|
||||||
letter-spacing: 0.44px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-tag {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: var(--success-500);
|
|
||||||
background: var(--success-100);
|
|
||||||
border-color: var(--success-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.inactive {
|
|
||||||
color: var(--error-500);
|
|
||||||
background: var(--error-100);
|
|
||||||
border-color: var(--error-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
width: 158px;
|
|
||||||
.ant-progress {
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.ant-progress-text {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-card {
|
|
||||||
&.ant-card-bordered {
|
|
||||||
border: 1px solid var(--bg-slate-500) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-and-search {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin: 16px 0;
|
|
||||||
|
|
||||||
.action-btn {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.views-tabs-container {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.views-tabs {
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
|
|
||||||
.view-title {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--margin-2);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: var(--font-size-xs);
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: var(--font-weight-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
width: 114px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab::before {
|
|
||||||
background: var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
color: var(--text-vanilla-100);
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view::before {
|
|
||||||
background: var(--bg-slate-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.compass-button {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400);
|
|
||||||
background: var(--bg-ink-300);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.ant-drawer-close {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.ant-drawer-header {
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-detail-drawer {
|
|
||||||
.title {
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-detail-drawer__host {
|
|
||||||
.ant-typography {
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.radio-button {
|
|
||||||
border: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
.views-tabs {
|
|
||||||
.tab {
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view {
|
|
||||||
background: var(--bg-vanilla-300);
|
|
||||||
border: 1px solid var(--bg-slate-300);
|
|
||||||
color: var(--text-ink-400);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected_view::before {
|
|
||||||
background: var(--bg-vanilla-300);
|
|
||||||
border-left: 1px solid var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.compass-button {
|
|
||||||
border: 1px solid var(--bg-vanilla-300);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-and-search {
|
|
||||||
.action-btn {
|
|
||||||
border: 1px solid var(--bg-vanilla-400);
|
|
||||||
background: var(--bg-vanilla-100);
|
|
||||||
color: var(--text-ink-300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,517 +0,0 @@
|
|||||||
import './HostMetricsDetail.styles.scss';
|
|
||||||
|
|
||||||
import { Color, Spacing } from '@signozhq/design-tokens';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Drawer,
|
|
||||||
Progress,
|
|
||||||
Radio,
|
|
||||||
Tag,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import { RadioChangeEvent } from 'antd/lib';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import { QueryParams } from 'constants/query';
|
|
||||||
import {
|
|
||||||
initialQueryBuilderFormValuesMap,
|
|
||||||
initialQueryState,
|
|
||||||
} from 'constants/queryBuilder';
|
|
||||||
import ROUTES from 'constants/routes';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import useUrlQuery from 'hooks/useUrlQuery';
|
|
||||||
import GetMinMax from 'lib/getMinMax';
|
|
||||||
import {
|
|
||||||
BarChart2,
|
|
||||||
ChevronsLeftRight,
|
|
||||||
Compass,
|
|
||||||
DraftingCompass,
|
|
||||||
Package2,
|
|
||||||
ScrollText,
|
|
||||||
X,
|
|
||||||
} from 'lucide-react';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import {
|
|
||||||
IBuilderQuery,
|
|
||||||
TagFilterItem,
|
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import {
|
|
||||||
LogsAggregatorOperator,
|
|
||||||
TracesAggregatorOperator,
|
|
||||||
} from 'types/common/queryBuilder';
|
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
import { VIEW_TYPES, VIEWS } from './constants';
|
|
||||||
import Containers from './Containers/Containers';
|
|
||||||
import { HostDetailProps } from './HostMetricDetail.interfaces';
|
|
||||||
import HostMetricLogsDetailedView from './HostMetricsLogs/HostMetricLogsDetailedView';
|
|
||||||
import HostMetricTraces from './HostMetricTraces/HostMetricTraces';
|
|
||||||
import Metrics from './Metrics/Metrics';
|
|
||||||
import Processes from './Processes/Processes';
|
|
||||||
|
|
||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
|
||||||
function HostMetricsDetails({
|
|
||||||
host,
|
|
||||||
onClose,
|
|
||||||
isModalTimeSelection,
|
|
||||||
}: HostDetailProps): JSX.Element {
|
|
||||||
const { maxTime, minTime, selectedTime } = useSelector<
|
|
||||||
AppState,
|
|
||||||
GlobalReducer
|
|
||||||
>((state) => state.globalTime);
|
|
||||||
|
|
||||||
const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [
|
|
||||||
minTime,
|
|
||||||
]);
|
|
||||||
const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [
|
|
||||||
maxTime,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const urlQuery = useUrlQuery();
|
|
||||||
|
|
||||||
const [modalTimeRange, setModalTimeRange] = useState(() => ({
|
|
||||||
startTime: startMs,
|
|
||||||
endTime: endMs,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const [selectedInterval, setSelectedInterval] = useState<Time>(
|
|
||||||
selectedTime as Time,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [selectedView, setSelectedView] = useState<VIEWS>(VIEWS.METRICS);
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
|
|
||||||
const initialFilters = useMemo(
|
|
||||||
() => ({
|
|
||||||
op: 'AND',
|
|
||||||
items: [
|
|
||||||
{
|
|
||||||
id: uuidv4(),
|
|
||||||
key: {
|
|
||||||
key: 'host.name',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
type: 'resource',
|
|
||||||
isColumn: false,
|
|
||||||
isJSON: false,
|
|
||||||
id: 'host.name--string--resource--false',
|
|
||||||
},
|
|
||||||
op: '=',
|
|
||||||
value: host?.hostName || '',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
[host?.hostName],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
|
|
||||||
initialFilters,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
|
|
||||||
initialFilters,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
logEvent('Infra Monitoring: Hosts list details page visited', {
|
|
||||||
host: host?.hostName,
|
|
||||||
});
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLogFilters(initialFilters);
|
|
||||||
setTracesFilters(initialFilters);
|
|
||||||
}, [initialFilters]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSelectedInterval(selectedTime as Time);
|
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
|
||||||
|
|
||||||
setModalTimeRange({
|
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
|
||||||
endTime: Math.floor(maxTime / 1000000000),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [selectedTime, minTime, maxTime]);
|
|
||||||
|
|
||||||
const handleTabChange = (e: RadioChangeEvent): void => {
|
|
||||||
setSelectedView(e.target.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTimeChange = useCallback(
|
|
||||||
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
|
|
||||||
setSelectedInterval(interval as Time);
|
|
||||||
|
|
||||||
if (interval === 'custom' && dateTimeRange) {
|
|
||||||
setModalTimeRange({
|
|
||||||
startTime: Math.floor(dateTimeRange[0] / 1000),
|
|
||||||
endTime: Math.floor(dateTimeRange[1] / 1000),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const { maxTime, minTime } = GetMinMax(interval);
|
|
||||||
|
|
||||||
setModalTimeRange({
|
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
|
||||||
endTime: Math.floor(maxTime / 1000000000),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
logEvent('Infra Monitoring: Hosts list details time updated', {
|
|
||||||
host: host?.hostName,
|
|
||||||
interval,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeLogFilters = useCallback(
|
|
||||||
(value: IBuilderQuery['filters']) => {
|
|
||||||
setLogFilters((prevFilters) => {
|
|
||||||
const hostNameFilter = prevFilters.items.find(
|
|
||||||
(item) => item.key?.key === 'host.name',
|
|
||||||
);
|
|
||||||
const paginationFilter = value.items.find((item) => item.key?.key === 'id');
|
|
||||||
const newFilters = value.items.filter(
|
|
||||||
(item) => item.key?.key !== 'id' && item.key?.key !== 'host.name',
|
|
||||||
);
|
|
||||||
|
|
||||||
logEvent('Infra Monitoring: Hosts list details logs filters applied', {
|
|
||||||
host: host?.hostName,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
op: 'AND',
|
|
||||||
items: [
|
|
||||||
hostNameFilter,
|
|
||||||
...newFilters,
|
|
||||||
...(paginationFilter ? [paginationFilter] : []),
|
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleChangeTracesFilters = useCallback(
|
|
||||||
(value: IBuilderQuery['filters']) => {
|
|
||||||
setTracesFilters((prevFilters) => {
|
|
||||||
const hostNameFilter = prevFilters.items.find(
|
|
||||||
(item) => item.key?.key === 'host.name',
|
|
||||||
);
|
|
||||||
|
|
||||||
logEvent('Infra Monitoring: Hosts list details traces filters applied', {
|
|
||||||
host: host?.hostName,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
op: 'AND',
|
|
||||||
items: [
|
|
||||||
hostNameFilter,
|
|
||||||
...value.items.filter((item) => item.key?.key !== 'host.name'),
|
|
||||||
].filter((item): item is TagFilterItem => item !== undefined),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleExplorePagesRedirect = (): void => {
|
|
||||||
if (selectedInterval !== 'custom') {
|
|
||||||
urlQuery.set(QueryParams.relativeTime, selectedInterval);
|
|
||||||
} else {
|
|
||||||
urlQuery.delete(QueryParams.relativeTime);
|
|
||||||
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
|
|
||||||
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
logEvent('Infra Monitoring: Hosts list details explore clicked', {
|
|
||||||
host: host?.hostName,
|
|
||||||
view: selectedView,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (selectedView === VIEW_TYPES.LOGS) {
|
|
||||||
const filtersWithoutPagination = {
|
|
||||||
...logFilters,
|
|
||||||
items: logFilters.items.filter((item) => item.key?.key !== 'id'),
|
|
||||||
};
|
|
||||||
|
|
||||||
const compositeQuery = {
|
|
||||||
...initialQueryState,
|
|
||||||
queryType: 'builder',
|
|
||||||
builder: {
|
|
||||||
...initialQueryState.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...initialQueryBuilderFormValuesMap.logs,
|
|
||||||
aggregateOperator: LogsAggregatorOperator.NOOP,
|
|
||||||
filters: filtersWithoutPagination,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
|
||||||
|
|
||||||
window.open(
|
|
||||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
|
||||||
'_blank',
|
|
||||||
);
|
|
||||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
|
||||||
const compositeQuery = {
|
|
||||||
...initialQueryState,
|
|
||||||
queryType: 'builder',
|
|
||||||
builder: {
|
|
||||||
...initialQueryState.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...initialQueryBuilderFormValuesMap.traces,
|
|
||||||
aggregateOperator: TracesAggregatorOperator.NOOP,
|
|
||||||
filters: tracesFilters,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
|
||||||
|
|
||||||
window.open(
|
|
||||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
|
||||||
'_blank',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = (): void => {
|
|
||||||
setSelectedInterval(selectedTime as Time);
|
|
||||||
|
|
||||||
if (selectedTime !== 'custom') {
|
|
||||||
const { maxTime, minTime } = GetMinMax(selectedTime);
|
|
||||||
|
|
||||||
setModalTimeRange({
|
|
||||||
startTime: Math.floor(minTime / 1000000000),
|
|
||||||
endTime: Math.floor(maxTime / 1000000000),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setSelectedView(VIEW_TYPES.METRICS);
|
|
||||||
onClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Drawer
|
|
||||||
width="70%"
|
|
||||||
title={
|
|
||||||
<>
|
|
||||||
<Divider type="vertical" />
|
|
||||||
<Typography.Text className="title">{host?.hostName}</Typography.Text>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
placement="right"
|
|
||||||
onClose={handleClose}
|
|
||||||
open={!!host}
|
|
||||||
style={{
|
|
||||||
overscrollBehavior: 'contain',
|
|
||||||
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
|
|
||||||
}}
|
|
||||||
className="host-detail-drawer"
|
|
||||||
destroyOnClose
|
|
||||||
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
|
|
||||||
>
|
|
||||||
{host && (
|
|
||||||
<>
|
|
||||||
<div className="host-detail-drawer__host">
|
|
||||||
<div className="host-details-grid">
|
|
||||||
<div className="labels-row">
|
|
||||||
<Typography.Text
|
|
||||||
type="secondary"
|
|
||||||
className="host-details-metadata-label"
|
|
||||||
>
|
|
||||||
STATUS
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text
|
|
||||||
type="secondary"
|
|
||||||
className="host-details-metadata-label"
|
|
||||||
>
|
|
||||||
OPERATING SYSTEM
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text
|
|
||||||
type="secondary"
|
|
||||||
className="host-details-metadata-label"
|
|
||||||
>
|
|
||||||
CPU USAGE
|
|
||||||
</Typography.Text>
|
|
||||||
<Typography.Text
|
|
||||||
type="secondary"
|
|
||||||
className="host-details-metadata-label"
|
|
||||||
>
|
|
||||||
MEMORY USAGE
|
|
||||||
</Typography.Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="values-row">
|
|
||||||
<Tag
|
|
||||||
bordered
|
|
||||||
className={`infra-monitoring-tags ${
|
|
||||||
host.active ? 'active' : 'inactive'
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{host.active ? 'ACTIVE' : 'INACTIVE'}
|
|
||||||
</Tag>
|
|
||||||
<Tag className="infra-monitoring-tags" bordered>
|
|
||||||
{host.os}
|
|
||||||
</Tag>
|
|
||||||
<div className="progress-container">
|
|
||||||
<Progress
|
|
||||||
percent={Number((host.cpu * 100).toFixed(1))}
|
|
||||||
size="small"
|
|
||||||
strokeColor={((): string => {
|
|
||||||
const cpuPercent = Number((host.cpu * 100).toFixed(1));
|
|
||||||
if (cpuPercent >= 90) return Color.BG_SAKURA_500;
|
|
||||||
if (cpuPercent >= 60) return Color.BG_AMBER_500;
|
|
||||||
return Color.BG_FOREST_500;
|
|
||||||
})()}
|
|
||||||
className="progress-bar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="progress-container">
|
|
||||||
<Progress
|
|
||||||
percent={Number((host.memory * 100).toFixed(1))}
|
|
||||||
size="small"
|
|
||||||
strokeColor={((): string => {
|
|
||||||
const memoryPercent = Number((host.memory * 100).toFixed(1));
|
|
||||||
if (memoryPercent >= 90) return Color.BG_CHERRY_500;
|
|
||||||
if (memoryPercent >= 60) return Color.BG_AMBER_500;
|
|
||||||
return Color.BG_FOREST_500;
|
|
||||||
})()}
|
|
||||||
className="progress-bar"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="views-tabs-container">
|
|
||||||
<Radio.Group
|
|
||||||
className="views-tabs"
|
|
||||||
onChange={handleTabChange}
|
|
||||||
value={selectedView}
|
|
||||||
>
|
|
||||||
<Radio.Button
|
|
||||||
className={
|
|
||||||
// eslint-disable-next-line sonarjs/no-duplicate-string
|
|
||||||
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
|
|
||||||
}
|
|
||||||
value={VIEW_TYPES.METRICS}
|
|
||||||
>
|
|
||||||
<div className="view-title">
|
|
||||||
<BarChart2 size={14} />
|
|
||||||
Metrics
|
|
||||||
</div>
|
|
||||||
</Radio.Button>
|
|
||||||
<Radio.Button
|
|
||||||
className={
|
|
||||||
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
|
|
||||||
}
|
|
||||||
value={VIEW_TYPES.LOGS}
|
|
||||||
>
|
|
||||||
<div className="view-title">
|
|
||||||
<ScrollText size={14} />
|
|
||||||
Logs
|
|
||||||
</div>
|
|
||||||
</Radio.Button>
|
|
||||||
<Radio.Button
|
|
||||||
className={
|
|
||||||
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
|
|
||||||
}
|
|
||||||
value={VIEW_TYPES.TRACES}
|
|
||||||
>
|
|
||||||
<div className="view-title">
|
|
||||||
<DraftingCompass size={14} />
|
|
||||||
Traces
|
|
||||||
</div>
|
|
||||||
</Radio.Button>
|
|
||||||
<Radio.Button
|
|
||||||
className={
|
|
||||||
selectedView === VIEW_TYPES.CONTAINERS ? 'selected_view tab' : 'tab'
|
|
||||||
}
|
|
||||||
value={VIEW_TYPES.CONTAINERS}
|
|
||||||
>
|
|
||||||
<div className="view-title">
|
|
||||||
<Package2 size={14} />
|
|
||||||
Containers
|
|
||||||
</div>
|
|
||||||
</Radio.Button>
|
|
||||||
<Radio.Button
|
|
||||||
className={
|
|
||||||
selectedView === VIEW_TYPES.PROCESSES ? 'selected_view tab' : 'tab'
|
|
||||||
}
|
|
||||||
value={VIEW_TYPES.PROCESSES}
|
|
||||||
>
|
|
||||||
<div className="view-title">
|
|
||||||
<ChevronsLeftRight size={14} />
|
|
||||||
Processes
|
|
||||||
</div>
|
|
||||||
</Radio.Button>
|
|
||||||
</Radio.Group>
|
|
||||||
|
|
||||||
{(selectedView === VIEW_TYPES.LOGS ||
|
|
||||||
selectedView === VIEW_TYPES.TRACES) && (
|
|
||||||
<Button
|
|
||||||
icon={<Compass size={18} />}
|
|
||||||
className="compass-button"
|
|
||||||
onClick={handleExplorePagesRedirect}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedView === VIEW_TYPES.METRICS && (
|
|
||||||
<Metrics
|
|
||||||
selectedInterval={selectedInterval}
|
|
||||||
hostName={host.hostName}
|
|
||||||
timeRange={modalTimeRange}
|
|
||||||
handleTimeChange={handleTimeChange}
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{selectedView === VIEW_TYPES.LOGS && (
|
|
||||||
<HostMetricLogsDetailedView
|
|
||||||
timeRange={modalTimeRange}
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
handleTimeChange={handleTimeChange}
|
|
||||||
handleChangeLogFilters={handleChangeLogFilters}
|
|
||||||
logFilters={logFilters}
|
|
||||||
selectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{selectedView === VIEW_TYPES.TRACES && (
|
|
||||||
<HostMetricTraces
|
|
||||||
timeRange={modalTimeRange}
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
handleTimeChange={handleTimeChange}
|
|
||||||
handleChangeTracesFilters={handleChangeTracesFilters}
|
|
||||||
tracesFilters={tracesFilters}
|
|
||||||
selectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{selectedView === VIEW_TYPES.CONTAINERS && <Containers />}
|
|
||||||
{selectedView === VIEW_TYPES.PROCESSES && <Processes />}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Drawer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HostMetricsDetails;
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
.host-metrics-logs-container {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.filter-section {
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid var(--bg-slate-400) !important;
|
|
||||||
background-color: var(--bg-ink-300) !important;
|
|
||||||
|
|
||||||
input {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-tag .ant-typography {
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-logs-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-logs {
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
.virtuoso-list {
|
|
||||||
overflow-y: hidden !important;
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
width: 0.3rem;
|
|
||||||
height: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb {
|
|
||||||
background: var(--bg-slate-300);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: var(--bg-slate-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-row {
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.skeleton-container {
|
|
||||||
height: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-logs-list-container {
|
|
||||||
flex: 1;
|
|
||||||
height: calc(100vh - 272px) !important;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
.raw-log-content {
|
|
||||||
width: 100%;
|
|
||||||
text-wrap: inherit;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-logs-list-card {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-loading-skeleton {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
.ant-skeleton-input-sm {
|
|
||||||
height: 18px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-logs-found {
|
|
||||||
height: 50vh;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 24px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.ant-typography {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.filter-section {
|
|
||||||
border-top: 1px solid var(--bg-vanilla-300);
|
|
||||||
border-bottom: 1px solid var(--bg-vanilla-300);
|
|
||||||
|
|
||||||
.ant-select-selector {
|
|
||||||
border-color: var(--bg-vanilla-300) !important;
|
|
||||||
background-color: var(--bg-vanilla-100) !important;
|
|
||||||
color: var(--bg-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
import './HostMetricLogs.styles.scss';
|
|
||||||
|
|
||||||
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
|
|
||||||
import HostMetricsLogs from './HostMetricsLogs';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
handleChangeLogFilters: (value: IBuilderQuery['filters']) => void;
|
|
||||||
logFilters: IBuilderQuery['filters'];
|
|
||||||
selectedInterval: Time;
|
|
||||||
}
|
|
||||||
|
|
||||||
function HostMetricLogsDetailedView({
|
|
||||||
timeRange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
handleTimeChange,
|
|
||||||
handleChangeLogFilters,
|
|
||||||
logFilters,
|
|
||||||
selectedInterval,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const { currentQuery } = useQueryBuilder();
|
|
||||||
const updatedCurrentQuery = useMemo(
|
|
||||||
() => ({
|
|
||||||
...currentQuery,
|
|
||||||
builder: {
|
|
||||||
...currentQuery.builder,
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
...currentQuery.builder.queryData[0],
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
...currentQuery.builder.queryData[0].aggregateAttribute,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[currentQuery],
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="host-metrics-logs-container">
|
|
||||||
<div className="host-metrics-logs-header">
|
|
||||||
<div className="filter-section">
|
|
||||||
{query && (
|
|
||||||
<QueryBuilderSearch
|
|
||||||
query={query}
|
|
||||||
onChange={handleChangeLogFilters}
|
|
||||||
disableNavigationShortcuts
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<HostMetricsLogs
|
|
||||||
timeRange={timeRange}
|
|
||||||
handleChangeLogFilters={handleChangeLogFilters}
|
|
||||||
filters={logFilters}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HostMetricLogsDetailedView;
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
/* eslint-disable no-nested-ternary */
|
|
||||||
import './HostMetricLogs.styles.scss';
|
|
||||||
|
|
||||||
import { Card } from 'antd';
|
|
||||||
import RawLogView from 'components/Logs/RawLogView';
|
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
|
||||||
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
|
|
||||||
import LogsError from 'container/LogsError/LogsError';
|
|
||||||
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
|
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
|
||||||
import { ORDERBY_FILTERS } from 'container/QueryBuilder/filters/OrderByFilter/config';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { isEqual } from 'lodash-es';
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
||||||
import { useQuery } from 'react-query';
|
|
||||||
import { Virtuoso } from 'react-virtuoso';
|
|
||||||
import { ILog } from 'types/api/logs/log';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import {
|
|
||||||
IBuilderQuery,
|
|
||||||
TagFilterItem,
|
|
||||||
} from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
|
|
||||||
import { getHostLogsQueryPayload } from './constants';
|
|
||||||
import NoLogsContainer from './NoLogsContainer';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
handleChangeLogFilters: (filters: IBuilderQuery['filters']) => void;
|
|
||||||
filters: IBuilderQuery['filters'];
|
|
||||||
}
|
|
||||||
|
|
||||||
function HostMetricsLogs({
|
|
||||||
timeRange,
|
|
||||||
handleChangeLogFilters,
|
|
||||||
filters,
|
|
||||||
}: Props): JSX.Element {
|
|
||||||
const [logs, setLogs] = useState<ILog[]>([]);
|
|
||||||
const [hasReachedEndOfLogs, setHasReachedEndOfLogs] = useState(false);
|
|
||||||
const [restFilters, setRestFilters] = useState<TagFilterItem[]>([]);
|
|
||||||
const [resetLogsList, setResetLogsList] = useState<boolean>(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const newRestFilters = filters.items.filter(
|
|
||||||
(item) => item.key?.key !== 'id' && item.key?.key !== 'host.name',
|
|
||||||
);
|
|
||||||
|
|
||||||
const areFiltersSame = isEqual(restFilters, newRestFilters);
|
|
||||||
|
|
||||||
if (!areFiltersSame) {
|
|
||||||
setResetLogsList(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRestFilters(newRestFilters);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [filters]);
|
|
||||||
|
|
||||||
const queryPayload = useMemo(() => {
|
|
||||||
const basePayload = getHostLogsQueryPayload(
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
filters,
|
|
||||||
);
|
|
||||||
|
|
||||||
basePayload.query.builder.queryData[0].pageSize = 100;
|
|
||||||
basePayload.query.builder.queryData[0].orderBy = [
|
|
||||||
{ columnName: 'timestamp', order: ORDERBY_FILTERS.DESC },
|
|
||||||
];
|
|
||||||
|
|
||||||
return basePayload;
|
|
||||||
}, [timeRange.startTime, timeRange.endTime, filters]);
|
|
||||||
|
|
||||||
const [isPaginating, setIsPaginating] = useState(false);
|
|
||||||
|
|
||||||
const { data, isLoading, isFetching, isError } = useQuery({
|
|
||||||
queryKey: [
|
|
||||||
'hostMetricsLogs',
|
|
||||||
timeRange.startTime,
|
|
||||||
timeRange.endTime,
|
|
||||||
filters,
|
|
||||||
],
|
|
||||||
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
|
|
||||||
enabled: !!queryPayload,
|
|
||||||
keepPreviousData: isPaginating,
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (data?.payload?.data?.newResult?.data?.result) {
|
|
||||||
const currentData = data.payload.data.newResult.data.result;
|
|
||||||
|
|
||||||
if (resetLogsList) {
|
|
||||||
const currentLogs: ILog[] =
|
|
||||||
currentData[0].list?.map((item) => ({
|
|
||||||
...item.data,
|
|
||||||
timestamp: item.timestamp,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setLogs(currentLogs);
|
|
||||||
|
|
||||||
setResetLogsList(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentData.length > 0 && currentData[0].list) {
|
|
||||||
const currentLogs: ILog[] =
|
|
||||||
currentData[0].list.map((item) => ({
|
|
||||||
...item.data,
|
|
||||||
timestamp: item.timestamp,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
setLogs((prev) => [...prev, ...currentLogs]);
|
|
||||||
} else {
|
|
||||||
setHasReachedEndOfLogs(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [data, restFilters, isPaginating, resetLogsList]);
|
|
||||||
|
|
||||||
const getItemContent = useCallback(
|
|
||||||
(_: number, logToRender: ILog): JSX.Element => (
|
|
||||||
<RawLogView
|
|
||||||
isReadOnly
|
|
||||||
isTextOverflowEllipsisDisabled
|
|
||||||
key={logToRender.id}
|
|
||||||
data={logToRender}
|
|
||||||
linesPerRow={5}
|
|
||||||
fontSize={FontSize.MEDIUM}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const loadMoreLogs = useCallback(() => {
|
|
||||||
if (!logs.length) return;
|
|
||||||
|
|
||||||
setIsPaginating(true);
|
|
||||||
const lastLog = logs[logs.length - 1];
|
|
||||||
|
|
||||||
const newItems = [
|
|
||||||
...filters.items.filter((item) => item.key?.key !== 'id'),
|
|
||||||
{
|
|
||||||
id: v4(),
|
|
||||||
key: {
|
|
||||||
key: 'id',
|
|
||||||
type: '',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
isColumn: true,
|
|
||||||
},
|
|
||||||
op: '<',
|
|
||||||
value: lastLog.id,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const newFilters = {
|
|
||||||
op: 'AND',
|
|
||||||
items: newItems,
|
|
||||||
} as IBuilderQuery['filters'];
|
|
||||||
|
|
||||||
handleChangeLogFilters(newFilters);
|
|
||||||
}, [logs, filters, handleChangeLogFilters]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsPaginating(false);
|
|
||||||
}, [data]);
|
|
||||||
|
|
||||||
const renderFooter = useCallback(
|
|
||||||
(): JSX.Element | null => (
|
|
||||||
// eslint-disable-next-line react/jsx-no-useless-fragment
|
|
||||||
<>
|
|
||||||
{isFetching ? (
|
|
||||||
<div className="logs-loading-skeleton"> Loading more logs ... </div>
|
|
||||||
) : hasReachedEndOfLogs ? (
|
|
||||||
<div className="logs-loading-skeleton"> *** End *** </div>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[isFetching, hasReachedEndOfLogs],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderContent = useMemo(
|
|
||||||
() => (
|
|
||||||
<Card bordered={false} className="host-metrics-logs-list-card">
|
|
||||||
<OverlayScrollbar isVirtuoso>
|
|
||||||
<Virtuoso
|
|
||||||
className="host-metrics-logs-virtuoso"
|
|
||||||
key="host-metrics-logs-virtuoso"
|
|
||||||
data={logs}
|
|
||||||
endReached={loadMoreLogs}
|
|
||||||
totalCount={logs.length}
|
|
||||||
itemContent={getItemContent}
|
|
||||||
overscan={200}
|
|
||||||
components={{
|
|
||||||
Footer: renderFooter,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</OverlayScrollbar>
|
|
||||||
</Card>
|
|
||||||
),
|
|
||||||
[logs, loadMoreLogs, getItemContent, renderFooter],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="host-metrics-logs">
|
|
||||||
{isLoading && <LogsLoading />}
|
|
||||||
{!isLoading && !isError && logs.length === 0 && <NoLogsContainer />}
|
|
||||||
{isError && !isLoading && <LogsError />}
|
|
||||||
{!isLoading && !isError && logs.length > 0 && (
|
|
||||||
<div className="host-metrics-logs-list-container">{renderContent}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HostMetricsLogs;
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Typography } from 'antd';
|
|
||||||
import { Ghost } from 'lucide-react';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
export default function NoLogsContainer(): React.ReactElement {
|
|
||||||
return (
|
|
||||||
<div className="no-logs-found">
|
|
||||||
<Text type="secondary">
|
|
||||||
<Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this host
|
|
||||||
in the selected time range.
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
|
||||||
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
|
||||||
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
|
|
||||||
import { EQueryType } from 'types/common/dashboard';
|
|
||||||
import { DataSource } from 'types/common/queryBuilder';
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
export const getHostLogsQueryPayload = (
|
|
||||||
start: number,
|
|
||||||
end: number,
|
|
||||||
filters: IBuilderQuery['filters'],
|
|
||||||
): GetQueryResultsProps => ({
|
|
||||||
graphType: PANEL_TYPES.LIST,
|
|
||||||
selectedTime: 'GLOBAL_TIME',
|
|
||||||
query: {
|
|
||||||
clickhouse_sql: [],
|
|
||||||
promql: [],
|
|
||||||
builder: {
|
|
||||||
queryData: [
|
|
||||||
{
|
|
||||||
dataSource: DataSource.LOGS,
|
|
||||||
queryName: 'A',
|
|
||||||
aggregateOperator: 'noop',
|
|
||||||
aggregateAttribute: {
|
|
||||||
id: '------false',
|
|
||||||
dataType: DataTypes.String,
|
|
||||||
key: '',
|
|
||||||
isColumn: false,
|
|
||||||
type: '',
|
|
||||||
isJSON: false,
|
|
||||||
},
|
|
||||||
timeAggregation: 'rate',
|
|
||||||
spaceAggregation: 'sum',
|
|
||||||
functions: [],
|
|
||||||
filters,
|
|
||||||
expression: 'A',
|
|
||||||
disabled: false,
|
|
||||||
stepInterval: 60,
|
|
||||||
having: [],
|
|
||||||
limit: null,
|
|
||||||
orderBy: [
|
|
||||||
{
|
|
||||||
columnName: 'timestamp',
|
|
||||||
order: 'desc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
groupBy: [],
|
|
||||||
legend: '',
|
|
||||||
reduceTo: 'avg',
|
|
||||||
offset: 0,
|
|
||||||
pageSize: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
queryFormulas: [],
|
|
||||||
},
|
|
||||||
id: uuidv4(),
|
|
||||||
queryType: EQueryType.QUERY_BUILDER,
|
|
||||||
},
|
|
||||||
params: {
|
|
||||||
lastLogLineTimestamp: null,
|
|
||||||
},
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
});
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
.empty-container {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-container {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.metrics-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
}
|
|
||||||
|
|
||||||
.host-metrics-card {
|
|
||||||
margin: 8px 0 1rem 0;
|
|
||||||
height: 300px;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
border: 1px solid var(--bg-slate-500);
|
|
||||||
|
|
||||||
.ant-card-body {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chart-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-data-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,142 +0,0 @@
|
|||||||
import './Metrics.styles.scss';
|
|
||||||
|
|
||||||
import { Card, Col, Row, Skeleton, Typography } from 'antd';
|
|
||||||
import cx from 'classnames';
|
|
||||||
import Uplot from 'components/Uplot';
|
|
||||||
import { ENTITY_VERSION_V4 } from 'constants/app';
|
|
||||||
import {
|
|
||||||
getHostQueryPayload,
|
|
||||||
hostWidgetInfo,
|
|
||||||
} from 'container/LogDetailedView/InfraMetrics/constants';
|
|
||||||
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
|
|
||||||
import {
|
|
||||||
CustomTimeType,
|
|
||||||
Time,
|
|
||||||
} from 'container/TopNav/DateTimeSelectionV2/config';
|
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
||||||
import { useResizeObserver } from 'hooks/useDimensions';
|
|
||||||
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
|
|
||||||
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
|
|
||||||
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
|
|
||||||
import { useMemo, useRef } from 'react';
|
|
||||||
import { useQueries, UseQueryResult } from 'react-query';
|
|
||||||
import { SuccessResponse } from 'types/api';
|
|
||||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
|
||||||
|
|
||||||
interface MetricsTabProps {
|
|
||||||
timeRange: {
|
|
||||||
startTime: number;
|
|
||||||
endTime: number;
|
|
||||||
};
|
|
||||||
isModalTimeSelection: boolean;
|
|
||||||
handleTimeChange: (
|
|
||||||
interval: Time | CustomTimeType,
|
|
||||||
dateTimeRange?: [number, number],
|
|
||||||
) => void;
|
|
||||||
selectedInterval: Time;
|
|
||||||
|
|
||||||
hostName: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Metrics({
|
|
||||||
selectedInterval,
|
|
||||||
hostName,
|
|
||||||
timeRange,
|
|
||||||
handleTimeChange,
|
|
||||||
isModalTimeSelection,
|
|
||||||
}: MetricsTabProps): JSX.Element {
|
|
||||||
const queryPayloads = useMemo(
|
|
||||||
() => getHostQueryPayload(hostName, timeRange.startTime, timeRange.endTime),
|
|
||||||
[hostName, timeRange.startTime, timeRange.endTime],
|
|
||||||
);
|
|
||||||
|
|
||||||
const queries = useQueries(
|
|
||||||
queryPayloads.map((payload) => ({
|
|
||||||
queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'],
|
|
||||||
queryFn: (): Promise<SuccessResponse<MetricRangePayloadProps>> =>
|
|
||||||
GetMetricQueryRange(payload, ENTITY_VERSION_V4),
|
|
||||||
enabled: !!payload,
|
|
||||||
})),
|
|
||||||
);
|
|
||||||
|
|
||||||
const isDarkMode = useIsDarkMode();
|
|
||||||
const graphRef = useRef<HTMLDivElement>(null);
|
|
||||||
const dimensions = useResizeObserver(graphRef);
|
|
||||||
|
|
||||||
const chartData = useMemo(
|
|
||||||
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
|
|
||||||
[queries],
|
|
||||||
);
|
|
||||||
|
|
||||||
const options = useMemo(
|
|
||||||
() =>
|
|
||||||
queries.map(({ data }, idx) =>
|
|
||||||
getUPlotChartOptions({
|
|
||||||
apiResponse: data?.payload,
|
|
||||||
isDarkMode,
|
|
||||||
dimensions,
|
|
||||||
yAxisUnit: hostWidgetInfo[idx].yAxisUnit,
|
|
||||||
softMax: null,
|
|
||||||
softMin: null,
|
|
||||||
minTimeScale: timeRange.startTime,
|
|
||||||
maxTimeScale: timeRange.endTime,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
[queries, isDarkMode, dimensions, timeRange.startTime, timeRange.endTime],
|
|
||||||
);
|
|
||||||
|
|
||||||
const renderCardContent = (
|
|
||||||
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
|
|
||||||
idx: number,
|
|
||||||
): JSX.Element => {
|
|
||||||
if (query.isLoading) {
|
|
||||||
return <Skeleton />;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.error) {
|
|
||||||
const errorMessage =
|
|
||||||
(query.error as Error)?.message || 'Something went wrong';
|
|
||||||
return <div>{errorMessage}</div>;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cx('chart-container', {
|
|
||||||
'no-data-container':
|
|
||||||
!query.isLoading && !query?.data?.payload?.data?.result?.length,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
<Uplot options={options[idx]} data={chartData[idx]} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="metrics-header">
|
|
||||||
<div className="metrics-datetime-section">
|
|
||||||
<DateTimeSelectionV2
|
|
||||||
showAutoRefresh={false}
|
|
||||||
showRefreshText={false}
|
|
||||||
hideShareModal
|
|
||||||
onTimeChange={handleTimeChange}
|
|
||||||
defaultRelativeTime="5m"
|
|
||||||
isModalTimeSelection={isModalTimeSelection}
|
|
||||||
modalSelectedInterval={selectedInterval}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Row gutter={24} className="host-metrics-container">
|
|
||||||
{queries.map((query, idx) => (
|
|
||||||
<Col span={12} key={hostWidgetInfo[idx].title}>
|
|
||||||
<Typography.Text>{hostWidgetInfo[idx].title}</Typography.Text>
|
|
||||||
<Card bordered className="host-metrics-card" ref={graphRef}>
|
|
||||||
{renderCardContent(query, idx)}
|
|
||||||
</Card>
|
|
||||||
</Col>
|
|
||||||
))}
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Metrics;
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
.host-processes {
|
|
||||||
gap: 24px;
|
|
||||||
height: 60vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
.infra-container-card-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dev-status-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-card {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-card-text {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
color: var(--text-vanilla-400);
|
|
||||||
line-height: 20px;
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
width: 400px;
|
|
||||||
font-family: 'Inter';
|
|
||||||
margin-top: 12px;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-working-msg {
|
|
||||||
display: flex;
|
|
||||||
width: 400px;
|
|
||||||
padding: 12px;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: rgba(171, 189, 255, 0.04);
|
|
||||||
|
|
||||||
.ant-space {
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.infra-container-contact-support-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
margin: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.lightMode {
|
|
||||||
.infra-container-card-text {
|
|
||||||
color: var(--text-ink-200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import './Processes.styles.scss';
|
|
||||||
|
|
||||||
import { Space, Typography } from 'antd';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import WaitlistFragment from '../WaitlistFragment/WaitlistFragment';
|
|
||||||
|
|
||||||
const { Text } = Typography;
|
|
||||||
|
|
||||||
function Processes(): JSX.Element {
|
|
||||||
const { t } = useTranslation(['infraMonitoring']);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Space direction="vertical" className="host-processes" size={24}>
|
|
||||||
<div className="infra-container-card-container">
|
|
||||||
<div className="dev-status-container">
|
|
||||||
<div className="infra-container-card">
|
|
||||||
<img
|
|
||||||
src="/Icons/infraContainers.svg"
|
|
||||||
alt="infra-container"
|
|
||||||
width={32}
|
|
||||||
height={32}
|
|
||||||
/>
|
|
||||||
<Text className="infra-container-card-text">
|
|
||||||
{t('processes_visualization_message')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="infra-container-working-msg">
|
|
||||||
<Space>
|
|
||||||
<img src="/Icons/broom.svg" alt="broom" width={24} height={24} />
|
|
||||||
<Text className="infra-container-card-text">{t('working_message')}</Text>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<WaitlistFragment entityType="processes" />
|
|
||||||
</div>
|
|
||||||
</Space>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Processes;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
.wait-list-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.wait-list-text {
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
|
|
||||||
.join-waitlist-btn {
|
|
||||||
width: 160px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background: var(--slate-500);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
import './WaitListFragment.styles.scss';
|
|
||||||
|
|
||||||
import { Color } from '@signozhq/design-tokens';
|
|
||||||
import { Button, Typography } from 'antd';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
|
||||||
import { CheckCircle2, HandPlatter } from 'lucide-react';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import { AppState } from 'store/reducers';
|
|
||||||
import AppReducer from 'types/reducer/app';
|
|
||||||
|
|
||||||
export default function WaitlistFragment({
|
|
||||||
entityType,
|
|
||||||
}: {
|
|
||||||
entityType: string;
|
|
||||||
}): JSX.Element {
|
|
||||||
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
|
|
||||||
const { t } = useTranslation(['infraMonitoring']);
|
|
||||||
const { notifications } = useNotifications();
|
|
||||||
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [isSuccess, setIsSuccess] = useState(false);
|
|
||||||
|
|
||||||
const handleJoinWaitlist = (): void => {
|
|
||||||
if (!user || !user.email) return;
|
|
||||||
|
|
||||||
setIsSubmitting(true);
|
|
||||||
|
|
||||||
logEvent('Infra Monitoring: Get Early Access Clicked', {
|
|
||||||
entity_type: entityType,
|
|
||||||
userEmail: user.email,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
notifications.success({
|
|
||||||
message: t('waitlist_success_message'),
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsSubmitting(false);
|
|
||||||
setIsSuccess(true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsSuccess(false);
|
|
||||||
}, 4000);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error logging event:', error);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="wait-list-container">
|
|
||||||
<Typography.Text className="wait-list-text">
|
|
||||||
{t('waitlist_message')}
|
|
||||||
</Typography.Text>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
className="periscope-btn join-waitlist-btn"
|
|
||||||
type="default"
|
|
||||||
loading={isSubmitting}
|
|
||||||
icon={
|
|
||||||
isSuccess ? (
|
|
||||||
<CheckCircle2 size={16} color={Color.BG_FOREST_500} />
|
|
||||||
) : (
|
|
||||||
<HandPlatter size={16} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onClick={handleJoinWaitlist}
|
|
||||||
>
|
|
||||||
Get early access
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
export enum VIEWS {
|
|
||||||
METRICS = 'metrics',
|
|
||||||
LOGS = 'logs',
|
|
||||||
TRACES = 'traces',
|
|
||||||
CONTAINERS = 'containers',
|
|
||||||
PROCESSES = 'processes',
|
|
||||||
}
|
|
||||||
|
|
||||||
export const VIEW_TYPES = {
|
|
||||||
METRICS: VIEWS.METRICS,
|
|
||||||
LOGS: VIEWS.LOGS,
|
|
||||||
TRACES: VIEWS.TRACES,
|
|
||||||
CONTAINERS: VIEWS.CONTAINERS,
|
|
||||||
PROCESSES: VIEWS.PROCESSES,
|
|
||||||
};
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import HostMetricsDetails from './HostMetricsDetails';
|
|
||||||
|
|
||||||
export default HostMetricsDetails;
|
|
||||||
@@ -8,13 +8,13 @@ import LogDetail from 'components/LogDetail';
|
|||||||
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
import { VIEW_TYPES } from 'components/LogDetail/constants';
|
||||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
import { FontSize } from 'container/OptionsMenu/types';
|
import { FontSize } from 'container/OptionsMenu/types';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
// utils
|
// utils
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
// interfaces
|
// interfaces
|
||||||
import { IField } from 'types/api/logs/fields';
|
import { IField } from 'types/api/logs/fields';
|
||||||
@@ -174,20 +174,12 @@ function ListLogView({
|
|||||||
[selectedFields],
|
[selectedFields],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
|
||||||
|
|
||||||
const timestampValue = useMemo(
|
const timestampValue = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof flattenLogData.timestamp === 'string'
|
typeof flattenLogData.timestamp === 'string'
|
||||||
? formatTimezoneAdjustedTimestamp(
|
? dayjs(flattenLogData.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||||
flattenLogData.timestamp,
|
: dayjs(flattenLogData.timestamp / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS'),
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
[flattenLogData.timestamp],
|
||||||
)
|
|
||||||
: formatTimezoneAdjustedTimestamp(
|
|
||||||
flattenLogData.timestamp / 1e6,
|
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
|
||||||
),
|
|
||||||
[flattenLogData.timestamp, formatTimezoneAdjustedTimestamp],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const logType = getLogIndicatorType(logData);
|
const logType = getLogIndicatorType(logData);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import LogDetail from 'components/LogDetail';
|
|||||||
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
import { VIEW_TYPES, VIEWS } from 'components/LogDetail/constants';
|
||||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
import LogsExplorerContext from 'container/LogsExplorerContext';
|
import LogsExplorerContext from 'container/LogsExplorerContext';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||||
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
||||||
@@ -13,7 +14,6 @@ import { useCopyLogLink } from 'hooks/logs/useCopyLogLink';
|
|||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { isEmpty, isNumber, isUndefined } from 'lodash-es';
|
import { isEmpty, isNumber, isUndefined } from 'lodash-es';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import {
|
import {
|
||||||
KeyboardEvent,
|
KeyboardEvent,
|
||||||
MouseEvent,
|
MouseEvent,
|
||||||
@@ -89,24 +89,16 @@ function RawLogView({
|
|||||||
attributesText += ' | ';
|
attributesText += ' | ';
|
||||||
}
|
}
|
||||||
|
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
|
||||||
|
|
||||||
const text = useMemo(() => {
|
const text = useMemo(() => {
|
||||||
const date =
|
const date =
|
||||||
typeof data.timestamp === 'string'
|
typeof data.timestamp === 'string'
|
||||||
? formatTimezoneAdjustedTimestamp(data.timestamp, 'YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(data.timestamp)
|
||||||
: formatTimezoneAdjustedTimestamp(
|
: dayjs(data.timestamp / 1e6);
|
||||||
data.timestamp / 1e6,
|
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
|
||||||
);
|
|
||||||
|
|
||||||
return `${date} | ${attributesText} ${data.body}`;
|
return `${date.format('YYYY-MM-DD HH:mm:ss.SSS')} | ${attributesText} ${
|
||||||
}, [
|
data.body
|
||||||
data.timestamp,
|
}`;
|
||||||
data.body,
|
}, [data.timestamp, data.body, attributesText]);
|
||||||
attributesText,
|
|
||||||
formatTimezoneAdjustedTimestamp,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleClickExpand = useCallback(() => {
|
const handleClickExpand = useCallback(() => {
|
||||||
if (activeContextLog || isReadOnly) return;
|
if (activeContextLog || isReadOnly) return;
|
||||||
@@ -181,7 +173,6 @@ function RawLogView({
|
|||||||
<LogStateIndicator type={logType} fontSize={fontSize} />
|
<LogStateIndicator type={logType} fontSize={fontSize} />
|
||||||
|
|
||||||
<RawLogContent
|
<RawLogContent
|
||||||
className="raw-log-content"
|
|
||||||
$isReadOnly={isReadOnly}
|
$isReadOnly={isReadOnly}
|
||||||
$isActiveLog={isActiveLog}
|
$isActiveLog={isActiveLog}
|
||||||
$isDarkMode={isDarkMode}
|
$isDarkMode={isDarkMode}
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { Typography } from 'antd';
|
|||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { unescapeString } from 'container/LogDetailedView/utils';
|
import { unescapeString } from 'container/LogDetailedView/utils';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import dompurify from 'dompurify';
|
import dompurify from 'dompurify';
|
||||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||||
import { FlatLogData } from 'lib/logs/flatLogData';
|
import { FlatLogData } from 'lib/logs/flatLogData';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
import { FORBID_DOM_PURIFY_TAGS } from 'utils/app';
|
||||||
|
|
||||||
@@ -44,8 +44,6 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
logs,
|
logs,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
|
||||||
|
|
||||||
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
const columns: ColumnsType<Record<string, unknown>> = useMemo(() => {
|
||||||
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
const fieldColumns: ColumnsType<Record<string, unknown>> = fields
|
||||||
.filter((e) => e.name !== 'id')
|
.filter((e) => e.name !== 'id')
|
||||||
@@ -83,11 +81,8 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
render: (field, item): ColumnTypeRender<Record<string, unknown>> => {
|
||||||
const date =
|
const date =
|
||||||
typeof field === 'string'
|
typeof field === 'string'
|
||||||
? formatTimezoneAdjustedTimestamp(field, 'YYYY-MM-DD HH:mm:ss.SSS')
|
? dayjs(field).format('YYYY-MM-DD HH:mm:ss.SSS')
|
||||||
: formatTimezoneAdjustedTimestamp(
|
: dayjs(field / 1e6).format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||||
field / 1e6,
|
|
||||||
'YYYY-MM-DD HH:mm:ss.SSS',
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
children: (
|
children: (
|
||||||
<div className="table-timestamp">
|
<div className="table-timestamp">
|
||||||
@@ -130,15 +125,7 @@ export const useTableView = (props: UseTableViewProps): UseTableViewResult => {
|
|||||||
},
|
},
|
||||||
...(appendTo === 'end' ? fieldColumns : []),
|
...(appendTo === 'end' ? fieldColumns : []),
|
||||||
];
|
];
|
||||||
}, [
|
}, [fields, isListViewPanel, appendTo, isDarkMode, linesPerRow, fontSize]);
|
||||||
fields,
|
|
||||||
isListViewPanel,
|
|
||||||
appendTo,
|
|
||||||
isDarkMode,
|
|
||||||
linesPerRow,
|
|
||||||
fontSize,
|
|
||||||
formatTimezoneAdjustedTimestamp,
|
|
||||||
]);
|
|
||||||
|
|
||||||
return { columns, dataSource: flattenLogData };
|
return { columns, dataSource: flattenLogData };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
margin: 6px 0;
|
margin: 6px 0;
|
||||||
width: 240px;
|
width: 160px;
|
||||||
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
.back-btn {
|
.back-btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -32,16 +32,14 @@
|
|||||||
.icon {
|
.icon {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
color: var(--bg-slate-50);
|
color: var(--bg-vanilla-400);
|
||||||
font-family: Inter;
|
font-family: Inter;
|
||||||
font-size: 11px;
|
font-size: 13px;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
line-height: 20px; /* 142.857% */
|
line-height: 20px; /* 142.857% */
|
||||||
letter-spacing: 0.14px;
|
letter-spacing: 0.14px;
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,75 +252,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-new-column-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.add-new-column-header {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 8px;
|
|
||||||
gap: 8px;
|
|
||||||
|
|
||||||
.back-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
gap: 4px;
|
|
||||||
align-items: center;
|
|
||||||
color: var(--bg-slate-50);
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 18px;
|
|
||||||
letter-spacing: 0.88px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new-column-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
min-height: 240px;
|
|
||||||
max-height: 400px;
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-format-new-options {
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
.column-name {
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: 1px;
|
|
||||||
color: var(--bg-vanilla-400, #c0c1c3);
|
|
||||||
font-family: Inter;
|
|
||||||
font-size: 13px;
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px; /* 142.857% */
|
|
||||||
letter-spacing: -0.07px;
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: var(--bg-ink-200);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
|
||||||
height: 1rem;
|
|
||||||
width: 0.2rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-item-content-container {
|
.selected-item-content-container {
|
||||||
.add-new-column-header {
|
.add-new-column-header {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
@@ -385,22 +314,6 @@
|
|||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&.default-column {
|
|
||||||
color: var(--bg-vanilla-400, #c0c1c3);
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-columns-selected {
|
|
||||||
color: var(--bg-slate-100);
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.add-new-column-btn {
|
|
||||||
color: var(--bg-vanilla-400, #c0c1c3);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -515,30 +428,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.add-new-column-container {
|
|
||||||
.add-new-column-header {
|
|
||||||
.title {
|
|
||||||
color: var(--bg-ink-100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-new-column-content {
|
|
||||||
.column-format-new-options {
|
|
||||||
.column-name {
|
|
||||||
color: var(--bg-ink-400);
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: var(--bg-vanilla-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-container {
|
|
||||||
color: var(--bg-ink-400);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-size-container {
|
.font-size-container {
|
||||||
.title {
|
.title {
|
||||||
color: var(--bg-ink-100);
|
color: var(--bg-ink-100);
|
||||||
|
|||||||
@@ -3,14 +3,13 @@
|
|||||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||||
import './LogsFormatOptionsMenu.styles.scss';
|
import './LogsFormatOptionsMenu.styles.scss';
|
||||||
|
|
||||||
import { Button, Input, InputNumber, Tooltip, Typography } from 'antd';
|
import { Button, Divider, Input, InputNumber, Tooltip, Typography } from 'antd';
|
||||||
import { DefaultOptionType } from 'antd/es/select';
|
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { LogViewMode } from 'container/LogsTable';
|
import { LogViewMode } from 'container/LogsTable';
|
||||||
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
import { FontSize, OptionsMenuConfig } from 'container/OptionsMenu/types';
|
||||||
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
import useDebouncedFn from 'hooks/useDebouncedFunction';
|
||||||
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
|
import { Check, ChevronLeft, ChevronRight, Minus, Plus, X } from 'lucide-react';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface LogsFormatOptionsMenuProps {
|
interface LogsFormatOptionsMenuProps {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -36,13 +35,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [showAddNewColumnContainer, setShowAddNewColumnContainer] = useState(
|
const [addNewColumn, setAddNewColumn] = useState(false);
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
|
||||||
const listRef = useRef<HTMLDivElement>(null);
|
|
||||||
const initialMouseEnterRef = useRef<boolean>(false);
|
|
||||||
|
|
||||||
const onChange = useCallback(
|
const onChange = useCallback(
|
||||||
(key: LogViewMode) => {
|
(key: LogViewMode) => {
|
||||||
@@ -56,7 +49,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
const handleMenuItemClick = (key: LogViewMode): void => {
|
const handleMenuItemClick = (key: LogViewMode): void => {
|
||||||
setSelectedItem(key);
|
setSelectedItem(key);
|
||||||
onChange(key);
|
onChange(key);
|
||||||
setShowAddNewColumnContainer(false);
|
setAddNewColumn(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const incrementMaxLinesPerRow = (): void => {
|
const incrementMaxLinesPerRow = (): void => {
|
||||||
@@ -82,8 +75,7 @@ export default function LogsFormatOptionsMenu({
|
|||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const handleToggleAddNewColumn = (): void => {
|
const handleToggleAddNewColumn = (): void => {
|
||||||
addColumn?.onSearch?.('');
|
setAddNewColumn(!addNewColumn);
|
||||||
setShowAddNewColumnContainer(!showAddNewColumnContainer);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
const handleLinesPerRowChange = (maxLinesPerRow: number | null): void => {
|
||||||
@@ -108,106 +100,9 @@ export default function LogsFormatOptionsMenu({
|
|||||||
}
|
}
|
||||||
}, [fontSizeValue]);
|
}, [fontSizeValue]);
|
||||||
|
|
||||||
function handleColumnSelection(
|
|
||||||
currentIndex: number,
|
|
||||||
optionsData: DefaultOptionType[],
|
|
||||||
): void {
|
|
||||||
const currentItem = optionsData[currentIndex];
|
|
||||||
const itemLength = optionsData.length;
|
|
||||||
if (addColumn && addColumn?.onSelect) {
|
|
||||||
addColumn?.onSelect(selectedValue, {
|
|
||||||
label: currentItem.label,
|
|
||||||
disabled: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// if the last element is selected then select the previous one
|
|
||||||
if (currentIndex === itemLength - 1) {
|
|
||||||
// there should be more than 1 element in the list
|
|
||||||
if (currentIndex - 1 >= 0) {
|
|
||||||
const prevValue = optionsData[currentIndex - 1]?.value || null;
|
|
||||||
setSelectedValue(prevValue as string | null);
|
|
||||||
} else {
|
|
||||||
// if there is only one element then just select and do nothing
|
|
||||||
setSelectedValue(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// selecting any random element from the list except the last one
|
|
||||||
const nextIndex = currentIndex + 1;
|
|
||||||
|
|
||||||
const nextValue = optionsData[nextIndex]?.value || null;
|
|
||||||
|
|
||||||
setSelectedValue(nextValue as string | null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = (e: KeyboardEvent): void => {
|
|
||||||
if (!selectedValue) return;
|
|
||||||
|
|
||||||
const optionsData = addColumn?.options || [];
|
|
||||||
|
|
||||||
const currentIndex = optionsData.findIndex(
|
|
||||||
(item) => item?.value === selectedValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
const itemLength = optionsData.length;
|
|
||||||
|
|
||||||
switch (e.key) {
|
|
||||||
case 'ArrowUp': {
|
|
||||||
const newValue = optionsData[Math.max(0, currentIndex - 1)]?.value;
|
|
||||||
|
|
||||||
setSelectedValue(newValue as string | null);
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'ArrowDown': {
|
|
||||||
const newValue =
|
|
||||||
optionsData[Math.min(itemLength - 1, currentIndex + 1)]?.value;
|
|
||||||
|
|
||||||
setSelectedValue(newValue as string | null);
|
|
||||||
e.preventDefault();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'Enter':
|
|
||||||
e.preventDefault();
|
|
||||||
handleColumnSelection(currentIndex, optionsData);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Scroll the selected item into view
|
|
||||||
const listNode = listRef.current;
|
|
||||||
if (listNode && selectedValue) {
|
|
||||||
const optionsData = addColumn?.options || [];
|
|
||||||
const currentIndex = optionsData.findIndex(
|
|
||||||
(item) => item?.value === selectedValue,
|
|
||||||
);
|
|
||||||
const itemNode = listNode.children[currentIndex] as HTMLElement;
|
|
||||||
if (itemNode) {
|
|
||||||
itemNode.scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'nearest',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [selectedValue]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
|
||||||
return (): void => {
|
|
||||||
window.removeEventListener('keydown', handleKeyDown);
|
|
||||||
};
|
|
||||||
}, [selectedValue]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cx(
|
className={cx('nested-menu-container', addNewColumn ? 'active' : '')}
|
||||||
'nested-menu-container',
|
|
||||||
showAddNewColumnContainer ? 'active' : '',
|
|
||||||
)}
|
|
||||||
onClick={(event): void => {
|
onClick={(event): void => {
|
||||||
// this is to restrict click events to propogate to parent
|
// this is to restrict click events to propogate to parent
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@@ -263,72 +158,8 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : (
|
||||||
|
<>
|
||||||
{showAddNewColumnContainer && (
|
|
||||||
<div className="add-new-column-container">
|
|
||||||
<div className="add-new-column-header">
|
|
||||||
<div className="title">
|
|
||||||
<div className="periscope-btn ghost" onClick={handleToggleAddNewColumn}>
|
|
||||||
<ChevronLeft
|
|
||||||
size={14}
|
|
||||||
className="back-icon"
|
|
||||||
onClick={handleToggleAddNewColumn}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
Add New Column
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Input
|
|
||||||
tabIndex={0}
|
|
||||||
type="text"
|
|
||||||
autoFocus
|
|
||||||
onFocus={addColumn?.onFocus}
|
|
||||||
onChange={handleSearchValueChange}
|
|
||||||
placeholder="Search..."
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="add-new-column-content">
|
|
||||||
{addColumn?.isFetching && (
|
|
||||||
<div className="loading-container"> Loading ... </div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="column-format-new-options" ref={listRef}>
|
|
||||||
{addColumn?.options?.map(({ label, value }, index) => (
|
|
||||||
<div
|
|
||||||
className={cx('column-name', value === selectedValue && 'selected')}
|
|
||||||
key={value}
|
|
||||||
onMouseEnter={(): void => {
|
|
||||||
if (!initialMouseEnterRef.current) {
|
|
||||||
setSelectedValue(value as string | null);
|
|
||||||
}
|
|
||||||
|
|
||||||
initialMouseEnterRef.current = true;
|
|
||||||
}}
|
|
||||||
onMouseMove={(): void => {
|
|
||||||
// this is added to handle the mouse move explicit event and not the re-rendered on mouse enter event
|
|
||||||
setSelectedValue(value as string | null);
|
|
||||||
}}
|
|
||||||
onClick={(eve): void => {
|
|
||||||
eve.stopPropagation();
|
|
||||||
handleColumnSelection(index, addColumn?.options || []);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="name">
|
|
||||||
<Tooltip placement="left" title={label}>
|
|
||||||
{label}
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{!isFontSizeOptionsOpen && !showAddNewColumnContainer && (
|
|
||||||
<div>
|
|
||||||
<div className="font-size-container">
|
<div className="font-size-container">
|
||||||
<div className="title">Font Size</div>
|
<div className="title">Font Size</div>
|
||||||
<Button
|
<Button
|
||||||
@@ -399,10 +230,29 @@ export default function LogsFormatOptionsMenu({
|
|||||||
</>
|
</>
|
||||||
|
|
||||||
<div className="selected-item-content-container active">
|
<div className="selected-item-content-container active">
|
||||||
{!showAddNewColumnContainer && <div className="horizontal-line" />}
|
{!addNewColumn && <div className="horizontal-line" />}
|
||||||
|
|
||||||
|
{addNewColumn && (
|
||||||
|
<div className="add-new-column-header">
|
||||||
|
<div className="title">
|
||||||
|
{' '}
|
||||||
|
columns
|
||||||
|
<X size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
tabIndex={0}
|
||||||
|
type="text"
|
||||||
|
autoFocus
|
||||||
|
onFocus={addColumn?.onFocus}
|
||||||
|
onChange={handleSearchValueChange}
|
||||||
|
placeholder="Search..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="item-content">
|
<div className="item-content">
|
||||||
{!showAddNewColumnContainer && (
|
{!addNewColumn && (
|
||||||
<div className="title">
|
<div className="title">
|
||||||
columns
|
columns
|
||||||
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
<Plus size={14} onClick={handleToggleAddNewColumn} />{' '}
|
||||||
@@ -424,17 +274,48 @@ export default function LogsFormatOptionsMenu({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{addColumn && addColumn?.value?.length === 0 && (
|
|
||||||
<div className="column-name no-columns-selected">
|
|
||||||
No columns selected
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{addColumn?.isFetching && (
|
||||||
|
<div className="loading-container"> Loading ... </div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{addNewColumn &&
|
||||||
|
addColumn &&
|
||||||
|
addColumn.value.length > 0 &&
|
||||||
|
addColumn.options &&
|
||||||
|
addColumn?.options?.length > 0 && (
|
||||||
|
<Divider className="column-divider" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{addNewColumn && (
|
||||||
|
<div className="column-format-new-options">
|
||||||
|
{addColumn?.options?.map(({ label, value }) => (
|
||||||
|
<div
|
||||||
|
className="column-name"
|
||||||
|
key={value}
|
||||||
|
onClick={(eve): void => {
|
||||||
|
eve.stopPropagation();
|
||||||
|
|
||||||
|
if (addColumn && addColumn?.onSelect) {
|
||||||
|
addColumn?.onSelect(value, { label, disabled: false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="name">
|
||||||
|
<Tooltip placement="left" title={label}>
|
||||||
|
{label}
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
.left-action {
|
.left-action {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -396,22 +396,23 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="checkbox-filter">
|
<div className="checkbox-filter">
|
||||||
<section
|
<section className="filter-header-checkbox">
|
||||||
className="filter-header-checkbox"
|
|
||||||
onClick={(): void => {
|
|
||||||
if (isOpen) {
|
|
||||||
setIsOpen(false);
|
|
||||||
setVisibleItemsCount(10);
|
|
||||||
} else {
|
|
||||||
setIsOpen(true);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<section className="left-action">
|
<section className="left-action">
|
||||||
{isOpen ? (
|
{isOpen ? (
|
||||||
<ChevronDown size={13} cursor="pointer" />
|
<ChevronDown
|
||||||
|
size={13}
|
||||||
|
cursor="pointer"
|
||||||
|
onClick={(): void => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setVisibleItemsCount(10);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ChevronRight size={13} cursor="pointer" />
|
<ChevronRight
|
||||||
|
size={13}
|
||||||
|
onClick={(): void => setIsOpen(true)}
|
||||||
|
cursor="pointer"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<Typography.Text className="title">{filter.title}</Typography.Text>
|
<Typography.Text className="title">{filter.title}</Typography.Text>
|
||||||
</section>
|
</section>
|
||||||
@@ -419,11 +420,7 @@ export default function CheckboxFilter(props: ICheckboxProps): JSX.Element {
|
|||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="clear-all"
|
className="clear-all"
|
||||||
onClick={(e): void => {
|
onClick={handleClearFilterAttribute}
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
handleClearFilterAttribute();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Clear All
|
Clear All
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
import { useTimezone } from 'providers/Timezone';
|
import convertDateToAmAndPm from 'lib/convertDateToAmAndPm';
|
||||||
|
import getFormattedDate from 'lib/getFormatedDate';
|
||||||
|
|
||||||
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
|
function Time({ CreatedOrUpdateTime }: DateProps): JSX.Element {
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
|
||||||
const time = new Date(CreatedOrUpdateTime);
|
const time = new Date(CreatedOrUpdateTime);
|
||||||
const timeString = formatTimezoneAdjustedTimestamp(
|
const date = getFormattedDate(time);
|
||||||
time,
|
const timeString = `${date} ${convertDateToAmAndPm(time)}`;
|
||||||
'MM/DD/YYYY hh:mm:ss A (UTC Z)',
|
|
||||||
);
|
|
||||||
return <Typography>{timeString}</Typography>;
|
return <Typography>{timeString}</Typography>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ function RouteTab({
|
|||||||
}: RouteTabProps & TabsProps): JSX.Element {
|
}: RouteTabProps & TabsProps): JSX.Element {
|
||||||
const onChange = (activeRoute: string): void => {
|
const onChange = (activeRoute: string): void => {
|
||||||
if (onChangeHandler) {
|
if (onChangeHandler) {
|
||||||
onChangeHandler(activeRoute);
|
onChangeHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedRoute = routes.find((e) => e.key === activeRoute);
|
const selectedRoute = routes.find((e) => e.key === activeRoute);
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export type TabRoutes = {
|
|||||||
export interface RouteTabProps {
|
export interface RouteTabProps {
|
||||||
routes: TabRoutes[];
|
routes: TabRoutes[];
|
||||||
activeKey: TabsProps['activeKey'];
|
activeKey: TabsProps['activeKey'];
|
||||||
onChangeHandler?: (key: string) => void;
|
onChangeHandler?: VoidFunction;
|
||||||
history: History<unknown>;
|
history: History<unknown>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,4 @@ export enum LOCALSTORAGE {
|
|||||||
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
|
||||||
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
|
LAST_USED_SAVED_VIEWS = 'LAST_USED_SAVED_VIEWS',
|
||||||
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
|
SHOW_LOGS_QUICK_FILTERS = 'SHOW_LOGS_QUICK_FILTERS',
|
||||||
PREFERRED_TIMEZONE = 'PREFERRED_TIMEZONE',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,5 @@ export const REACT_QUERY_KEY = {
|
|||||||
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
GET_ALL_ALLERTS: 'GET_ALL_ALLERTS',
|
||||||
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
REMOVE_ALERT_RULE: 'REMOVE_ALERT_RULE',
|
||||||
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
DUPLICATE_ALERT_RULE: 'DUPLICATE_ALERT_RULE',
|
||||||
GET_HOST_LIST: 'GET_HOST_LIST',
|
|
||||||
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
UPDATE_ALERT_RULE: 'UPDATE_ALERT_RULE',
|
||||||
GET_ACTIVE_LICENSE_V3: 'GET_ACTIVE_LICENSE_V3',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,12 +55,10 @@ const ROUTES = {
|
|||||||
LOGS_SAVE_VIEWS: '/logs/saved-views',
|
LOGS_SAVE_VIEWS: '/logs/saved-views',
|
||||||
TRACES_SAVE_VIEWS: '/traces/saved-views',
|
TRACES_SAVE_VIEWS: '/traces/saved-views',
|
||||||
WORKSPACE_LOCKED: '/workspace-locked',
|
WORKSPACE_LOCKED: '/workspace-locked',
|
||||||
WORKSPACE_SUSPENDED: '/workspace-suspended',
|
|
||||||
SHORTCUTS: '/shortcuts',
|
SHORTCUTS: '/shortcuts',
|
||||||
INTEGRATIONS: '/integrations',
|
INTEGRATIONS: '/integrations',
|
||||||
MESSAGING_QUEUES: '/messaging-queues',
|
MESSAGING_QUEUES: '/messaging-queues',
|
||||||
MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail',
|
MESSAGING_QUEUES_DETAIL: '/messaging-queues/detail',
|
||||||
INFRASTRUCTURE_MONITORING_HOSTS: '/infrastructure-monitoring/hosts',
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default ROUTES;
|
export default ROUTES;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
export const TimezonePickerShortcuts = {
|
|
||||||
CloseTimezonePicker: 'escape',
|
|
||||||
};
|
|
||||||
@@ -1,16 +1,10 @@
|
|||||||
import { Color } from '@signozhq/design-tokens';
|
import { Color } from '@signozhq/design-tokens';
|
||||||
import { Progress, Table } from 'antd';
|
import { Progress, Table } from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import logEvent from 'api/common/logEvent';
|
|
||||||
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
|
import { ConditionalAlertPopover } from 'container/AlertHistory/AlertPopover/AlertPopover';
|
||||||
import AlertLabels from 'pages/AlertDetails/AlertHeader/AlertLabels/AlertLabels';
|
import AlertLabels from 'pages/AlertDetails/AlertHeader/AlertLabels/AlertLabels';
|
||||||
import PaginationInfoText from 'periscope/components/PaginationInfoText/PaginationInfoText';
|
import PaginationInfoText from 'periscope/components/PaginationInfoText/PaginationInfoText';
|
||||||
import { HTMLAttributes } from 'react';
|
import { AlertRuleStats, AlertRuleTopContributors } from 'types/api/alerts/def';
|
||||||
import {
|
|
||||||
AlertRuleStats,
|
|
||||||
AlertRuleTimelineTableResponse,
|
|
||||||
AlertRuleTopContributors,
|
|
||||||
} from 'types/api/alerts/def';
|
|
||||||
|
|
||||||
function TopContributorsRows({
|
function TopContributorsRows({
|
||||||
topContributors,
|
topContributors,
|
||||||
@@ -76,21 +70,10 @@ function TopContributorsRows({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleRowClick = (
|
|
||||||
record: AlertRuleTopContributors,
|
|
||||||
): HTMLAttributes<AlertRuleTimelineTableResponse> => ({
|
|
||||||
onClick: (): void => {
|
|
||||||
logEvent('Alert history: Top contributors row: Clicked', {
|
|
||||||
labels: record.labels,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Table
|
||||||
rowClassName="contributors-row"
|
rowClassName="contributors-row"
|
||||||
rowKey={(row): string => `top-contributor-${row.fingerprint}`}
|
rowKey={(row): string => `top-contributor-${row.fingerprint}`}
|
||||||
onRow={handleRowClick}
|
|
||||||
columns={columns}
|
columns={columns}
|
||||||
showHeader={false}
|
showHeader={false}
|
||||||
dataSource={topContributors}
|
dataSource={topContributors}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user