Compare commits

..

21 Commits

Author SHA1 Message Date
Shaheer Kochai
1b48eb81c6 feat: add timezone support to the graphs throughout the app (#6520)
...
2024-12-03 13:56:48 +04:30
ahmadshaheer
504811546e chore: added null check for timezone.value in updateTimezone 2024-12-02 19:40:27 +04:30
ahmadshaheer
e012f10395 chore: overall improvements 2024-11-20 18:42:41 +04:30
ahmadshaheer
676e32ea09 chore: store timezone string in local storage instead of object 2024-11-20 18:20:48 +04:30
ahmadshaheer
28045772b8 fix: get active timezone in timepicker hint 2024-11-20 09:23:52 +04:30
ahmadshaheer
b1120c7d16 fix: if timezone is undefined, fallback to browser time zone 2024-11-20 09:19:55 +04:30
ahmadshaheer
55c9205aad feat: timezone setting functionality in timepicker, timezone picker, and timezone preferences 2024-11-20 09:19:55 +04:30
ahmadshaheer
5b4f423f9f feat: create a context for timezone and handle the timezone configuration 2024-11-20 09:19:16 +04:30
ahmadshaheer
cc376ce6a8 chore: improve timezoneUtils by adding a function to get browser timezone 2024-11-20 09:19:16 +04:30
ahmadshaheer
20e00c597a fix: display the timezone in timepicker hint 'You are at' 2024-11-20 09:18:43 +04:30
ahmadshaheer
ff7da5c05b fix: fix the issue of timezone breaking for browser and utc timezones 2024-11-19 18:42:20 +04:30
ahmadshaheer
14ccadaeb5 fix: don't focus on time picker when timezone is clicked 2024-11-19 13:55:42 +04:30
ahmadshaheer
984f3829dd chore: fix the typo 2024-11-19 13:37:18 +04:30
ahmadshaheer
31a9ead2fc feat: display timezone in timepicker input 2024-11-19 13:34:15 +04:30
ahmadshaheer
65ce8eaf14 chore: change timezone item from div to button 2024-11-19 13:33:14 +04:30
ahmadshaheer
daec491c79 chore: improve timezone utils 2024-11-19 13:29:33 +04:30
ahmadshaheer
49e29567f4 feat: timezone preferences UI 2024-11-19 10:02:00 +04:30
ahmadshaheer
8edd5fe7d6 fix: overall improvement + add searchIndex to timezone 2024-11-18 18:49:22 +04:30
ahmadshaheer
178a3153dd chore: add the selected timezone as url param and close timezone picker on select 2024-11-18 18:38:45 +04:30
ahmadshaheer
e7f1b27a5b feat: add support for esc keypress to close the timezone picker 2024-11-18 18:27:38 +04:30
ahmadshaheer
dbf0f236be feat: time picker hint and timezone picker UI with basic functionality + helper to get timezones 2024-11-18 17:47:48 +04:30
567 changed files with 3456 additions and 12233 deletions

View File

@@ -3,6 +3,7 @@ name: build-pipeline
on: on:
pull_request: pull_request:
branches: branches:
- develop
- main - main
- release/v* - release/v*

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- develop
tags: tags:
- v* - v*

View File

@@ -3,6 +3,7 @@ on:
pull_request: pull_request:
branches: branches:
- main - main
- develop
paths: paths:
- 'frontend/**' - 'frontend/**'
defaults: defaults:

View File

@@ -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:

View File

@@ -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}

View File

@@ -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
``` ```

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -110,7 +110,6 @@ 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:

View File

@@ -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",

View File

@@ -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"

View File

@@ -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:
[ [

View File

@@ -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:
[ [

View File

@@ -119,7 +119,6 @@ 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:

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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"`

View File

@@ -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

View File

@@ -78,7 +78,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 +109,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{}

View File

@@ -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,31 @@ func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*M
repo: &repo, repo: &repo,
} }
if err := m.start(features...); err != nil { if useLicensesV3 {
// get active license from the db
active, err := m.repo.GetActiveLicense(context.Background())
if err != nil {
return m, err
}
// if we have an active license then need to fetch the complete details
if active != nil {
// fetch the new license structure from control plane
licenseV3, apiError := validate.ValidateLicenseV3(active.Key)
if apiError != nil {
return m, apiError
}
// insert the licenseV3 in sqlite db
apiError = m.repo.InsertLicenseV3(context.Background(), licenseV3)
// if the license already exists move ahead.
if apiError != nil && apiError.Typ != model.ErrorConflict {
return m, apiError
}
}
}
if err := m.start(useLicensesV3, features...); err != nil {
return m, err return m, err
} }
LM = m LM = m
@@ -75,8 +99,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 +116,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 +171,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 +253,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 +307,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 +422,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 {

View File

@@ -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

View File

@@ -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},
}
}

View File

@@ -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"
) )

View File

@@ -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(),
) )

View File

@@ -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

View File

@@ -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"
}, },
@@ -186,6 +186,7 @@
"@types/webpack-dev-server": "^4.7.2", "@types/webpack-dev-server": "^4.7.2",
"@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0", "@typescript-eslint/parser": "^4.33.0",
"@vvo/tzdb": "6.149.0",
"autoprefixer": "10.4.19", "autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0", "babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0", "compression-webpack-plugin": "9.0.0",
@@ -241,7 +242,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"
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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 youve 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"
}

View File

@@ -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"
}

View File

@@ -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"
} }

View File

@@ -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 bits enough ⎯ were getting your {{dataSource}}!"
} }

View File

@@ -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 youve 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"
}

View File

@@ -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"
}

View File

@@ -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"
} }

View File

@@ -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,7 +20,6 @@ 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 { isCloudUser } from 'utils/app';
@@ -51,8 +49,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(
@@ -253,33 +249,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]);

View File

@@ -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>
); );
} }

View File

@@ -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'
),
);

View File

@@ -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 = {

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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);
}
};

View File

@@ -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;

View File

@@ -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,

View File

@@ -40,7 +40,7 @@
&.custom-time { &.custom-time {
input:not(:focus) { input:not(:focus) {
min-width: 280px; min-width: 240px;
} }
} }
@@ -120,7 +120,7 @@
} }
} }
.date-time-popover__footer { .date-time-popover-footer {
border-top: 1px solid var(--bg-ink-200); border-top: 1px solid var(--bg-ink-200);
padding: 8px 14px; padding: 8px 14px;
.timezone-container { .timezone-container {
@@ -136,14 +136,10 @@
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
gap: 6px; gap: 6px;
.timezone { .timezone {
display: flex;
align-items: center;
gap: 4px;
border-radius: 2px;
background: rgba(171, 189, 255, 0.04);
cursor: pointer; cursor: pointer;
padding: 0px 4px; padding: 0;
color: var(--bg-vanilla-100); color: var(--bg-vanilla-100);
background-color: transparent;
border: none; border: none;
} }
} }
@@ -162,26 +158,3 @@
letter-spacing: -0.06px; letter-spacing: -0.06px;
cursor: pointer; 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%);
}
}

View File

@@ -89,9 +89,9 @@ function CustomTimePicker({
const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW); const [activeView, setActiveView] = useState<ViewType>(DEFAULT_VIEW);
const { timezone, browserTimezone } = useTimezone(); const { timezone, browserTimezone } = useTimezone();
const activeTimezoneOffset = timezone.offset; const activeTimezoneOffset = timezone?.offset;
const isTimezoneOverridden = useMemo( const isTimezoneOverridden = useMemo(
() => timezone.offset !== browserTimezone.offset, () => timezone?.offset !== browserTimezone.offset,
[timezone, browserTimezone], [timezone, browserTimezone],
); );
@@ -100,28 +100,16 @@ function CustomTimePicker({
if (activeView !== newView) { if (activeView !== newView) {
setActiveView(newView); setActiveView(newView);
} }
setOpen(true); setOpen(!open);
}, },
[activeView, setOpen], [activeView, open, 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 +144,7 @@ function CustomTimePicker({
useEffect(() => { useEffect(() => {
const value = getSelectedTimeRangeLabel(selectedTime, selectedValue); const value = getSelectedTimeRangeLabel(selectedTime, selectedValue);
setSelectedTimePlaceholderValue(value); setSelectedTimePlaceholderValue(value);
}, [selectedTime, selectedValue]); }, [selectedTime, selectedValue]);
@@ -281,7 +270,6 @@ function CustomTimePicker({
const handleFocus = (): void => { const handleFocus = (): void => {
setIsInputFocused(true); setIsInputFocused(true);
setActiveView('datetime');
}; };
const handleBlur = (): void => { const handleBlur = (): void => {
@@ -320,8 +308,6 @@ function CustomTimePicker({
selectedTime={selectedTime} selectedTime={selectedTime}
activeView={activeView} activeView={activeView}
setActiveView={setActiveView} setActiveView={setActiveView}
setIsOpenedFromFooter={setIsOpenedFromFooter}
isOpenedFromFooter={isOpenedFromFooter}
/> />
) : ( ) : (
content content
@@ -365,7 +351,6 @@ function CustomTimePicker({
onClick={(e): void => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleViewChange('timezone'); handleViewChange('timezone');
setIsOpenedFromFooter(false);
}} }}
> >
<span>{activeTimezoneOffset}</span> <span>{activeTimezoneOffset}</span>

View File

@@ -10,7 +10,7 @@ import {
Option, Option,
RelativeDurationSuggestionOptions, RelativeDurationSuggestionOptions,
} from 'container/TopNav/DateTimeSelectionV2/config'; } from 'container/TopNav/DateTimeSelectionV2/config';
import { Clock, PenLine } from 'lucide-react'; import { Clock } from 'lucide-react';
import { useTimezone } from 'providers/Timezone'; 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';
@@ -32,8 +32,6 @@ interface CustomTimePickerPopoverContentProps {
selectedTime: string; selectedTime: string;
activeView: 'datetime' | 'timezone'; activeView: 'datetime' | 'timezone';
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>; setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
isOpenedFromFooter: boolean;
setIsOpenedFromFooter: Dispatch<SetStateAction<boolean>>;
} }
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
@@ -48,8 +46,6 @@ function CustomTimePickerPopoverContent({
selectedTime, selectedTime,
activeView, activeView,
setActiveView, setActiveView,
isOpenedFromFooter,
setIsOpenedFromFooter,
}: CustomTimePickerPopoverContentProps): JSX.Element { }: CustomTimePickerPopoverContentProps): JSX.Element {
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -57,7 +53,7 @@ function CustomTimePickerPopoverContent({
pathname, pathname,
]); ]);
const { timezone } = useTimezone(); const { timezone } = useTimezone();
const activeTimezoneOffset = timezone.offset; const activeTimezoneOffset = timezone?.offset;
function getTimeChips(options: Option[]): JSX.Element { function getTimeChips(options: Option[]): JSX.Element {
return ( return (
@@ -78,25 +74,8 @@ function CustomTimePickerPopoverContent({
); );
} }
const handleTimezoneHintClick = (): void => { return activeView === 'datetime' ? (
setActiveView('timezone'); <div>
setIsOpenedFromFooter(true);
};
if (activeView === 'timezone') {
return (
<div className="date-time-popover">
<TimezonePicker
setActiveView={setActiveView}
setIsOpen={setIsOpen}
isOpenedFromFooter={isOpenedFromFooter}
/>
</div>
);
}
return (
<>
<div className="date-time-popover"> <div className="date-time-popover">
<div className="date-time-options"> <div className="date-time-options">
{isLogsExplorerPage && ( {isLogsExplorerPage && (
@@ -146,31 +125,24 @@ function CustomTimePickerPopoverContent({
</div> </div>
</div> </div>
<div className="date-time-popover__footer"> <div className="date-time-popover-footer">
<div className="timezone-container"> <div className="timezone-container">
<Clock <Clock color={Color.BG_VANILLA_400} height={12} width={12} />
color={Color.BG_VANILLA_400} <span className="timezone-text">You are at</span>
className="timezone-container__clock-icon"
height={12}
width={12}
/>
<span className="timezone__icon">Current timezone</span>
<div></div>
<button <button
type="button" type="button"
className="timezone" className="timezone"
onClick={handleTimezoneHintClick} onClick={(): void => setActiveView('timezone')}
> >
<span>{activeTimezoneOffset}</span> {activeTimezoneOffset}
<PenLine
color={Color.BG_VANILLA_100}
className="timezone__icon"
size={10}
/>
</button> </button>
</div> </div>
</div> </div>
</> </div>
) : (
<div className="date-time-popover">
<TimezonePicker setActiveView={setActiveView} setIsOpen={setIsOpen} />
</div>
); );
} }

View File

@@ -3,7 +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 { useTimezone } from 'providers/Timezone';
import { Dispatch, SetStateAction } from 'react'; import { Dispatch, SetStateAction } from 'react';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
@@ -32,10 +32,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());
}; };
@@ -61,7 +58,6 @@ function RangePickerModal(props: RangePickerModalProps): JSX.Element {
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' && {

View File

@@ -1,15 +1,8 @@
// Variables // Variables
$font-family: 'Inter'; $font-family: 'Inter';
$border-color: var(--bg-slate-400);
$item-spacing: 8px; $item-spacing: 8px;
:root {
--border-color: var(--bg-slate-400);
}
.lightMode {
--border-color: var(--bg-vanilla-400);
}
// Mixins // Mixins
@mixin text-style-base { @mixin text-style-base {
font-family: $font-family; font-family: $font-family;
@@ -31,7 +24,7 @@ $item-spacing: 8px;
@include flex-center; @include flex-center;
justify-content: space-between; justify-content: space-between;
padding: 12px 14px; padding: 12px 14px;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid $border-color;
} }
&__input-container { &__input-container {
@@ -51,9 +44,6 @@ $item-spacing: 8px;
line-height: 20px; line-height: 20px;
letter-spacing: -0.07px; letter-spacing: -0.07px;
padding: 0; padding: 0;
&.ant-input:focus {
box-shadow: none;
}
&::placeholder { &::placeholder {
color: var(--bg-vanilla-400); color: var(--bg-vanilla-400);
@@ -104,7 +94,7 @@ $item-spacing: 8px;
bottom: -2px; bottom: -2px;
left: -$item-spacing; left: -$item-spacing;
right: -$item-spacing; right: -$item-spacing;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid $border-color;
} }
} }
} }
@@ -133,34 +123,3 @@ $item-spacing: 8px;
width: 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);
}
}
}
}

View File

@@ -1,7 +1,6 @@
import './TimezonePicker.styles.scss'; import './TimezonePicker.styles.scss';
import { Color } from '@signozhq/design-tokens'; import { Color } from '@signozhq/design-tokens';
import { Input } from 'antd';
import cx from 'classnames'; import cx from 'classnames';
import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts'; import { TimezonePickerShortcuts } from 'constants/shortcuts/TimezonePickerShortcuts';
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys'; import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
@@ -20,9 +19,6 @@ import { Timezone, TIMEZONE_DATA } from './timezoneUtils';
interface SearchBarProps { interface SearchBarProps {
value: string; value: string;
onChange: (value: string) => void; onChange: (value: string) => void;
setIsOpen: Dispatch<SetStateAction<boolean>>;
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
isOpenedFromFooter: boolean;
} }
interface TimezoneItemProps { interface TimezoneItemProps {
@@ -33,44 +29,17 @@ interface TimezoneItemProps {
const ICON_SIZE = 14; const ICON_SIZE = 14;
function SearchBar({ function SearchBar({ value, onChange }: SearchBarProps): JSX.Element {
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 ( return (
<div className="timezone-picker__search"> <div className="timezone-picker__search">
<div className="timezone-picker__input-container"> <div className="timezone-picker__input-container">
<Search <Search color={Color.BG_VANILLA_400} height={ICON_SIZE} width={ICON_SIZE} />
color={Color.BG_VANILLA_400} <input
className="search-icon"
height={ICON_SIZE}
width={ICON_SIZE}
/>
<Input
type="text" type="text"
className="timezone-picker__input" className="timezone-picker__input"
placeholder="Search timezones..." placeholder="Search timezones..."
value={value} value={value}
onChange={(e): void => onChange(e.target.value)} onChange={(e): void => onChange(e.target.value)}
onKeyDown={handleKeyDown}
tabIndex={0}
autoFocus
/> />
</div> </div>
<kbd className="timezone-picker__esc-key">esc</kbd> <kbd className="timezone-picker__esc-key">esc</kbd>
@@ -96,7 +65,6 @@ function TimezoneItem({
<div className="timezone-name-wrapper__selected-icon"> <div className="timezone-name-wrapper__selected-icon">
{isSelected && ( {isSelected && (
<Check <Check
className="check-icon"
color={Color.BG_VANILLA_100} color={Color.BG_VANILLA_100}
height={ICON_SIZE} height={ICON_SIZE}
width={ICON_SIZE} width={ICON_SIZE}
@@ -118,19 +86,16 @@ TimezoneItem.defaultProps = {
interface TimezonePickerProps { interface TimezonePickerProps {
setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>; setActiveView: Dispatch<SetStateAction<'datetime' | 'timezone'>>;
setIsOpen: Dispatch<SetStateAction<boolean>>; setIsOpen: Dispatch<SetStateAction<boolean>>;
isOpenedFromFooter: boolean;
} }
function TimezonePicker({ function TimezonePicker({
setActiveView, setActiveView,
setIsOpen, setIsOpen,
isOpenedFromFooter,
}: TimezonePickerProps): JSX.Element { }: TimezonePickerProps): JSX.Element {
console.log({ isOpenedFromFooter });
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const { timezone, updateTimezone } = useTimezone(); const { timezone, updateTimezone } = useTimezone();
const [selectedTimezone, setSelectedTimezone] = useState<string>( const [selectedTimezone, setSelectedTimezone] = useState<string>(
timezone.name ?? TIMEZONE_DATA[0].name, timezone?.name ?? TIMEZONE_DATA[0].name,
); );
const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => { const getFilteredTimezones = useCallback((searchTerm: string): Timezone[] => {
@@ -144,12 +109,8 @@ function TimezonePicker({
}, []); }, []);
const handleCloseTimezonePicker = useCallback(() => { const handleCloseTimezonePicker = useCallback(() => {
if (isOpenedFromFooter) { setActiveView('datetime');
setActiveView('datetime'); }, [setActiveView]);
} else {
setIsOpen(false);
}
}, [isOpenedFromFooter, setActiveView, setIsOpen]);
const handleTimezoneSelect = useCallback( const handleTimezoneSelect = useCallback(
(timezone: Timezone) => { (timezone: Timezone) => {
@@ -177,13 +138,7 @@ function TimezonePicker({
return ( return (
<div className="timezone-picker"> <div className="timezone-picker">
<SearchBar <SearchBar value={searchTerm} onChange={setSearchTerm} />
value={searchTerm}
onChange={setSearchTerm}
setIsOpen={setIsOpen}
setActiveView={setActiveView}
isOpenedFromFooter={isOpenedFromFooter}
/>
<div className="timezone-picker__list"> <div className="timezone-picker__list">
{getFilteredTimezones(searchTerm).map((timezone) => ( {getFilteredTimezones(searchTerm).map((timezone) => (
<TimezoneItem <TimezoneItem

View File

@@ -1,3 +1,4 @@
import { getTimeZones } from '@vvo/tzdb';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import timezone from 'dayjs/plugin/timezone'; import timezone from 'dayjs/plugin/timezone';
import utc from 'dayjs/plugin/utc'; import utc from 'dayjs/plugin/utc';
@@ -13,6 +14,7 @@ export interface Timezone {
hasDivider?: boolean; hasDivider?: boolean;
} }
// Constants
const TIMEZONE_TYPES = { const TIMEZONE_TYPES = {
BROWSER: 'BROWSER', BROWSER: 'BROWSER',
UTC: 'UTC', UTC: 'UTC',
@@ -21,7 +23,7 @@ const TIMEZONE_TYPES = {
type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES]; type TimezoneType = typeof TIMEZONE_TYPES[keyof typeof TIMEZONE_TYPES];
export const UTC_TIMEZONE: Timezone = { const UTC_TIMEZONE: Timezone = {
name: 'Coordinated Universal Time — UTC, GMT', name: 'Coordinated Universal Time — UTC, GMT',
value: 'UTC', value: 'UTC',
offset: 'UTC', offset: 'UTC',
@@ -29,12 +31,14 @@ export const UTC_TIMEZONE: Timezone = {
hasDivider: true, hasDivider: true,
}; };
const normalizeTimezoneName = (timezone: string): string => { // Helper functions
// https://github.com/tc39/proposal-temporal/issues/1076 const isValidTimezone = (tzName: string): boolean => {
if (timezone === 'Asia/Calcutta') { try {
return 'Asia/Kolkata'; dayjs.tz(dayjs(), tzName);
return true;
} catch {
return false;
} }
return timezone;
}; };
const formatOffset = (offsetMinutes: number): string => { const formatOffset = (offsetMinutes: number): string => {
@@ -92,36 +96,26 @@ const getOffsetByTimezone = (timezone: string): number => {
export const getBrowserTimezone = (): Timezone => { export const getBrowserTimezone = (): Timezone => {
const browserTz = dayjs.tz.guess(); const browserTz = dayjs.tz.guess();
const normalizedTz = normalizeTimezoneName(browserTz); const browserOffset = getOffsetByTimezone(browserTz);
const browserOffset = getOffsetByTimezone(normalizedTz); return createTimezoneEntry(browserTz, browserOffset, TIMEZONE_TYPES.BROWSER);
return createTimezoneEntry(
normalizedTz,
browserOffset,
TIMEZONE_TYPES.BROWSER,
);
}; };
const filterAndSortTimezones = ( const filterAndSortTimezones = (
allTimezones: string[], allTimezones: ReturnType<typeof getTimeZones>,
browserTzName?: string, browserTzName?: string,
includeEtcTimezones = false,
): Timezone[] => ): Timezone[] =>
allTimezones allTimezones
.filter((tz) => { .filter(
const isNotBrowserTz = tz !== browserTzName; (tz) =>
const isNotEtcTz = includeEtcTimezones || !tz.startsWith('Etc/'); !tz.name.startsWith('Etc/') &&
return isNotBrowserTz && isNotEtcTz; isValidTimezone(tz.name) &&
}) tz.name !== browserTzName,
.sort((a, b) => a.localeCompare(b)) )
.map((tz) => { .sort((a, b) => a.name.localeCompare(b.name))
const normalizedTz = normalizeTimezoneName(tz); .map((tz) => createTimezoneEntry(tz.name, tz.rawOffsetInMinutes));
const offset = getOffsetByTimezone(normalizedTz);
return createTimezoneEntry(normalizedTz, offset);
});
const generateTimezoneData = (includeEtcTimezones = false): Timezone[] => { const generateTimezoneData = (): Timezone[] => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any const allTimezones = getTimeZones();
const allTimezones = (Intl as any).supportedValuesOf('timeZone');
const timezones: Timezone[] = []; const timezones: Timezone[] = [];
// Add browser timezone // Add browser timezone
@@ -131,13 +125,8 @@ const generateTimezoneData = (includeEtcTimezones = false): Timezone[] => {
// Add UTC timezone with divider // Add UTC timezone with divider
timezones.push(UTC_TIMEZONE); timezones.push(UTC_TIMEZONE);
timezones.push( // Add remaining timezones
...filterAndSortTimezones( timezones.push(...filterAndSortTimezones(allTimezones, browserTzObject.value));
allTimezones,
browserTzObject.value,
includeEtcTimezones,
),
);
return timezones; return timezones;
}; };
@@ -146,6 +135,7 @@ export const getTimezoneObjectByTimezoneString = (
timezone: string, timezone: string,
): Timezone => { ): Timezone => {
const utcOffset = getOffsetByTimezone(timezone); const utcOffset = getOffsetByTimezone(timezone);
return createTimezoneEntry(timezone, utcOffset); return createTimezoneEntry(timezone, utcOffset);
}; };

View File

@@ -132,7 +132,7 @@ const Graph = forwardRef<ToggleGraphProps | undefined, GraphProps>(
useEffect(() => { useEffect(() => {
_adapters._date.override({ _adapters._date.override({
format(time: number | Date, fmt: string) { format(time: number | Date, fmt: string) {
const dayjsTime = dayjs(time).tz(timezone.value); const dayjsTime = dayjs(time).tz(timezone?.value);
const format = formatMap[fmt as keyof typeof formatMap]; const format = formatMap[fmt as keyof typeof formatMap];
if (!format) { if (!format) {
console.warn(`Missing datetime format for ${fmt}`); console.warn(`Missing datetime format for ${fmt}`);

View File

@@ -99,7 +99,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.tz(timezone?.value).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 || '';

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -1,7 +0,0 @@
import { HostData } from 'api/infraMonitoring/getHostLists';
export type HostDetailProps = {
host: HostData | null;
isModalTimeSelection: boolean;
onClose: () => void;
};

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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,
},
],
},
});

View File

@@ -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;
};

View File

@@ -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);
}
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>
);
}

View File

@@ -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,
});

View File

@@ -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%);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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>
);
}

View File

@@ -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,
};

View File

@@ -1,3 +0,0 @@
import HostMetricsDetails from './HostMetricsDetails';
export default HostMetricsDetails;

View File

@@ -181,7 +181,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}

View File

@@ -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);

View File

@@ -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>
); );

View File

@@ -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;

View File

@@ -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>

View File

@@ -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);

View File

@@ -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>;
} }

View File

@@ -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',
}; };

View File

@@ -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;

View File

@@ -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}

View File

@@ -120,7 +120,7 @@ function HorizontalTimelineGraph({
: [], : [],
tzDate: (timestamp: number): Date => tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value), uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
}), }),
[ [
width, width,
@@ -128,7 +128,7 @@ function HorizontalTimelineGraph({
transformedData.length, transformedData.length,
urlQuery, urlQuery,
dispatch, dispatch,
timezone.value, timezone?.value,
], ],
); );
return <Uplot data={transformedData} options={options} />; return <Uplot data={transformedData} options={options} />;

View File

@@ -1,16 +1,14 @@
import './Table.styles.scss'; import './Table.styles.scss';
import { Table } from 'antd'; import { Table } from 'antd';
import logEvent from 'api/common/logEvent';
import { initialFilters } from 'constants/queryBuilder'; import { initialFilters } from 'constants/queryBuilder';
import { import {
useGetAlertRuleDetailsTimelineTable, useGetAlertRuleDetailsTimelineTable,
useTimelineTable, useTimelineTable,
} from 'pages/AlertDetails/hooks'; } from 'pages/AlertDetails/hooks';
import { useTimezone } from 'providers/Timezone'; import { useTimezone } from 'providers/Timezone';
import { HTMLAttributes, useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { AlertRuleTimelineTableResponse } from 'types/api/alerts/def';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData'; import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { timelineTableColumns } from './useTimelineTable'; import { timelineTableColumns } from './useTimelineTable';
@@ -48,17 +46,6 @@ function TimelineTable(): JSX.Element {
return <div>{t('something_went_wrong')}</div>; return <div>{t('something_went_wrong')}</div>;
} }
const handleRowClick = (
record: AlertRuleTimelineTableResponse,
): HTMLAttributes<AlertRuleTimelineTableResponse> => ({
onClick: (): void => {
logEvent('Alert history: Timeline table row: Clicked', {
ruleId: record.ruleID,
labels: record.labels,
});
},
});
return ( return (
<div className="timeline-table"> <div className="timeline-table">
<Table <Table
@@ -69,7 +56,6 @@ function TimelineTable(): JSX.Element {
setFilters, setFilters,
formatTimezoneAdjustedTimestamp, formatTimezoneAdjustedTimestamp,
})} })}
onRow={handleRowClick}
dataSource={timelineData} dataSource={timelineData}
pagination={paginationConfig} pagination={paginationConfig}
size="middle" size="middle"

View File

@@ -154,7 +154,7 @@ function AnomalyAlertEvaluationView({
const options = { const options = {
width: dimensions.width, width: dimensions.width,
height: dimensions.height - 36, height: dimensions.height - 36,
plugins: [bandsPlugin, tooltipPlugin(isDarkMode, timezone.value)], plugins: [bandsPlugin, tooltipPlugin(isDarkMode, timezone?.value)],
focus: { focus: {
alpha: 0.3, alpha: 0.3,
}, },
@@ -260,7 +260,7 @@ function AnomalyAlertEvaluationView({
}, },
axes: getAxes(isDarkMode, yAxisUnit), axes: getAxes(isDarkMode, yAxisUnit),
tzDate: (timestamp: number): Date => tzDate: (timestamp: number): Date =>
uPlot.tzDate(new Date(timestamp * 1e3), timezone.value), uPlot.tzDate(new Date(timestamp * 1e3), timezone?.value),
}; };
const handleSearch = (searchText: string): void => { const handleSearch = (searchText: string): void => {

View File

@@ -108,13 +108,6 @@
text-align: center; text-align: center;
} }
.payment-failed-banner {
padding: 8px;
background-color: var(--bg-sakura-500);
color: white;
text-align: center;
}
.upgrade-link { .upgrade-link {
padding: 0px; padding: 0px;
padding-right: 4px; padding-right: 4px;

View File

@@ -5,30 +5,25 @@ import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react'; import * as Sentry from '@sentry/react';
import { Flex } from 'antd'; import { Flex } from 'antd';
import manageCreditCardApi from 'api/billing/manage';
import getUserLatestVersion from 'api/user/getLatestVersion'; import getUserLatestVersion from 'api/user/getLatestVersion';
import getUserVersion from 'api/user/getVersion'; import getUserVersion from 'api/user/getVersion';
import cx from 'classnames'; import cx from 'classnames';
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway'; import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar'; import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { SOMETHING_WENT_WRONG } from 'constants/api';
import { FeatureKeys } from 'constants/features'; import { FeatureKeys } from 'constants/features';
import ROUTES from 'constants/routes'; import ROUTES from 'constants/routes';
import SideNav from 'container/SideNav'; import SideNav from 'container/SideNav';
import TopNav from 'container/TopNav'; import TopNav from 'container/TopNav';
import dayjs from 'dayjs';
import { useIsDarkMode } from 'hooks/useDarkMode'; import { useIsDarkMode } from 'hooks/useDarkMode';
import useFeatureFlags from 'hooks/useFeatureFlag'; import useFeatureFlags from 'hooks/useFeatureFlag';
import useLicense from 'hooks/useLicense'; 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 { isNull } from 'lodash-es';
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import { useAppContext } from 'providers/App/App';
import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async'; import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMutation, useQueries } from 'react-query'; import { useQueries } from 'react-query';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { Dispatch } from 'redux'; import { Dispatch } from 'redux';
@@ -40,16 +35,9 @@ import {
UPDATE_LATEST_VERSION, UPDATE_LATEST_VERSION,
UPDATE_LATEST_VERSION_ERROR, UPDATE_LATEST_VERSION_ERROR,
} from 'types/actions/app'; } from 'types/actions/app';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
import { LicenseEvent } from 'types/api/licensesV3/getActive';
import AppReducer from 'types/reducer/app'; import AppReducer from 'types/reducer/app';
import { isCloudUser } from 'utils/app'; import { isCloudUser } from 'utils/app';
import { import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
getFormattedDate,
getFormattedDateWithMinutes,
getRemainingDays,
} from 'utils/timeUtils';
import { ChildrenContainer, Layout, LayoutContent } from './styles'; import { ChildrenContainer, Layout, LayoutContent } from './styles';
import { getRouteKey } from './utils'; import { getRouteKey } from './utils';
@@ -60,42 +48,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
(state) => state.app, (state) => state.app,
); );
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
const { notifications } = useNotifications(); const { notifications } = useNotifications();
const [
showPaymentFailedWarning,
setShowPaymentFailedWarning,
] = useState<boolean>(false);
const handleBillingOnSuccess = (
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
): void => {
if (data?.payload?.redirectURL) {
const newTab = document.createElement('a');
newTab.href = data.payload.redirectURL;
newTab.target = '_blank';
newTab.rel = 'noopener noreferrer';
newTab.click();
}
};
const handleBillingOnError = (): void => {
notifications.error({
message: SOMETHING_WENT_WRONG,
});
};
const {
mutate: manageCreditCard,
isLoading: isLoadingManageBilling,
} = useMutation(manageCreditCardApi, {
onSuccess: (data) => {
handleBillingOnSuccess(data);
},
onError: handleBillingOnError,
});
const isDarkMode = useIsDarkMode(); const isDarkMode = useIsDarkMode();
const { data: licenseData, isFetching } = useLicense(); const { data: licenseData, isFetching } = useLicense();
@@ -258,16 +212,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
} }
}, [licenseData, isFetching]); }, [licenseData, isFetching]);
useEffect(() => {
if (
!isFetchingActiveLicenseV3 &&
!isNull(activeLicenseV3) &&
activeLicenseV3?.event_queue?.event === LicenseEvent.FAILED_PAYMENT
) {
setShowPaymentFailedWarning(true);
}
}, [activeLicenseV3, isFetchingActiveLicenseV3]);
useEffect(() => { useEffect(() => {
// after logging out hide the trial expiry banner // after logging out hide the trial expiry banner
if (!isLoggedIn) { if (!isLoggedIn) {
@@ -281,14 +225,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
} }
}; };
const handleFailedPayment = (): void => {
manageCreditCard({
licenseKey: activeLicenseV3?.key || '',
successURL: window.location.href,
cancelURL: window.location.href,
});
};
const isLogsView = (): boolean => const isLogsView = (): boolean =>
routeKey === 'LOGS' || routeKey === 'LOGS' ||
routeKey === 'LOGS_EXPLORER' || routeKey === 'LOGS_EXPLORER' ||
@@ -304,8 +240,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD'; const isDashboardListView = (): boolean => routeKey === 'ALL_DASHBOARD';
const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY'; const isAlertHistory = (): boolean => routeKey === 'ALERT_HISTORY';
const isAlertOverview = (): boolean => routeKey === 'ALERT_OVERVIEW'; const isAlertOverview = (): boolean => routeKey === 'ALERT_OVERVIEW';
const isInfraMonitoringHosts = (): boolean =>
routeKey === 'INFRASTRUCTURE_MONITORING_HOSTS';
const isPathMatch = (regex: RegExp): boolean => regex.test(pathname); const isPathMatch = (regex: RegExp): boolean => regex.test(pathname);
const isDashboardView = (): boolean => const isDashboardView = (): boolean =>
@@ -333,7 +267,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
<title>{pageTitle}</title> <title>{pageTitle}</title>
</Helmet> </Helmet>
{showTrialExpiryBanner && !showPaymentFailedWarning && ( {showTrialExpiryBanner && (
<div className="trial-expiry-banner"> <div className="trial-expiry-banner">
You are in free trial period. Your free trial will end on{' '} You are in free trial period. Your free trial will end on{' '}
<span> <span>
@@ -353,36 +287,6 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
)} )}
</div> </div>
)} )}
{!showTrialExpiryBanner && showPaymentFailedWarning && (
<div className="payment-failed-banner">
Your bill payment has failed. Your workspace will get suspended on{' '}
<span>
{getFormattedDateWithMinutes(
dayjs(activeLicenseV3?.event_queue?.scheduled_at).unix() || Date.now(),
)}
.
</span>
{role === 'ADMIN' ? (
<span>
{' '}
Please{' '}
<a
className="upgrade-link"
onClick={(): void => {
if (!isLoadingManageBilling) {
handleFailedPayment();
}
}}
>
pay the bill
</a>
to continue using SigNoz features.
</span>
) : (
' Please contact your administrator to pay the bill.'
)}
</div>
)}
<Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}> <Flex className={cx('app-layout', isDarkMode ? 'darkMode' : 'lightMode')}>
{isToDisplayLayout && !renderFullScreen && ( {isToDisplayLayout && !renderFullScreen && (
@@ -402,8 +306,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
isDashboardListView() || isDashboardListView() ||
isAlertHistory() || isAlertHistory() ||
isAlertOverview() || isAlertOverview() ||
isMessagingQueues() || isMessagingQueues()
isInfraMonitoringHosts()
? 0 ? 0
: '0 1rem', : '0 1rem',

View File

@@ -17,7 +17,6 @@ function Controls({
handleNavigateNext, handleNavigateNext,
handleCountItemsPerPageChange, handleCountItemsPerPageChange,
isLogPanel = false, isLogPanel = false,
showSizeChanger = true,
}: ControlsProps): JSX.Element | null { }: ControlsProps): JSX.Element | null {
const isNextAndPreviousDisabled = useMemo( const isNextAndPreviousDisabled = useMemo(
() => isLoading || countPerPage < 0 || totalCount === 0, () => isLoading || countPerPage < 0 || totalCount === 0,
@@ -53,23 +52,20 @@ function Controls({
> >
Next <RightOutlined /> Next <RightOutlined />
</Button> </Button>
<Select<Pagination['limit']>
{showSizeChanger && ( style={defaultSelectStyle}
<Select<Pagination['limit']> loading={isLoading}
style={defaultSelectStyle} value={countPerPage}
loading={isLoading} onChange={handleCountItemsPerPageChange}
value={countPerPage} getPopupContainer={popupContainer}
onChange={handleCountItemsPerPageChange} >
getPopupContainer={popupContainer} {perPageOptions.map((count) => (
> <Select.Option
{perPageOptions.map((count) => ( key={count}
<Select.Option value={count}
key={count} >{`${count} / page`}</Select.Option>
value={count} ))}
>{`${count} / page`}</Select.Option> </Select>
))}
</Select>
)}
</Container> </Container>
); );
} }
@@ -78,7 +74,6 @@ Controls.defaultProps = {
offset: 0, offset: 0,
perPageOptions: DEFAULT_PER_PAGE_OPTIONS, perPageOptions: DEFAULT_PER_PAGE_OPTIONS,
isLogPanel: false, isLogPanel: false,
showSizeChanger: true,
}; };
export interface ControlsProps { export interface ControlsProps {
@@ -91,7 +86,6 @@ export interface ControlsProps {
handleNavigateNext: () => void; handleNavigateNext: () => void;
handleCountItemsPerPageChange: (value: Pagination['limit']) => void; handleCountItemsPerPageChange: (value: Pagination['limit']) => void;
isLogPanel?: boolean; isLogPanel?: boolean;
showSizeChanger?: boolean;
} }
export default memo(Controls); export default memo(Controls);

View File

@@ -117,7 +117,7 @@ export const logAlertDefaults: AlertDef = {
chQueries: { chQueries: {
A: { A: {
name: 'A', name: 'A',
query: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.distributed_logs_v2 \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nAND ts_bucket_start BETWEEN {{.start_timestamp}} - 1800 AND {{.end_timestamp}} \nGROUP BY interval;\n\n-- Please check docs here https://signoz.io/docs/userguide/logs_clickhouse_queries/\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n-- \t{{.start_timestamp}}\n-- \t{{.end_timestamp}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`, query: `select \ntoStartOfInterval(fromUnixTimestamp64Nano(timestamp), INTERVAL 30 MINUTE) AS interval, \ntoFloat64(count()) as value \nFROM signoz_logs.distributed_logs_v2 \nWHERE timestamp BETWEEN {{.start_timestamp_nano}} AND {{.end_timestamp_nano}} \nGROUP BY interval;\n\n-- available variables:\n-- \t{{.start_timestamp_nano}}\n-- \t{{.end_timestamp_nano}}\n\n-- required columns (or alias):\n-- \tvalue\n-- \tinterval`,
legend: '', legend: '',
disabled: false, disabled: false,
}, },
@@ -149,7 +149,7 @@ export const traceAlertDefaults: AlertDef = {
chQueries: { chQueries: {
A: { A: {
name: 'A', name: 'A',
query: `SELECT \n\ttoStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval, \n\tresource_string_service$$name AS \`service.name\`, \n\ttoFloat64(avg(duration_nano)) AS value \nFROM signoz_traces.distributed_signoz_index_v3 \nWHERE resource_string_service$$name !='' \nAND timestamp BETWEEN {{.start_datetime}} AND {{.end_datetime}} \nAND ts_bucket_start BETWEEN {{.start_timestamp}} - 1800 AND {{.end_timestamp}} \nGROUP BY (\`service.name\`, interval);\n\n-- Please check docs here https://signoz.io/docs/userguide/writing-clickhouse-traces-query/\n\n-- available variables:\n-- \t{{.start_datetime}}\n-- \t{{.end_datetime}}\n-- \t{{.start_timestamp}}\n-- \t{{.end_timestamp}}\n\n-- required column alias:\n-- \tvalue\n-- \tinterval`, query: `SELECT \n\ttoStartOfInterval(timestamp, INTERVAL 1 MINUTE) AS interval, \n\tstringTagMap['peer.service'] AS op_name, \n\ttoFloat64(avg(durationNano)) AS value \nFROM signoz_traces.distributed_signoz_index_v2 \nWHERE stringTagMap['peer.service']!='' \nAND timestamp BETWEEN {{.start_datetime}} AND {{.end_datetime}} \nGROUP BY (op_name, interval);\n\n-- available variables:\n-- \t{{.start_datetime}}\n-- \t{{.end_datetime}}\n\n-- required column alias:\n-- \tvalue\n-- \tinterval`,
legend: '', legend: '',
disabled: false, disabled: false,
}, },

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