diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4008dd426a..573be5f290 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,7 +2,7 @@ # Owners are automatically requested for review for PRs that changes code # that they own. -/frontend/ @palashgdev @YounixM +/frontend/ @YounixM /frontend/src/container/MetricsApplication @srikanthccv /frontend/src/container/NewWidget/RightContainer/types.ts @srikanthccv /deploy/ @prashant-shahi diff --git a/README.de-de.md b/README.de-de.md index 5c61097a15..1fdbcbda15 100644 --- a/README.de-de.md +++ b/README.de-de.md @@ -11,7 +11,6 @@ tweet

-

DokumentationReadme auf Englisch • @@ -40,12 +39,13 @@ SigNoz hilft Entwicklern, Anwendungen zu überwachen und Probleme in ihren berei 👉 Einfache Einrichtung von Benachrichtigungen mit dem selbst erstellbaren Abfrage-Builder. ## + ### Anwendung Metriken ![application_metrics](https://user-images.githubusercontent.com/83692067/226637410-900dbc5e-6705-4b11-a10c-bd0faeb2a92f.png) - ### Verteiltes Tracing + distributed_tracing_2 2 distributed_tracing_1 @@ -62,22 +62,18 @@ SigNoz hilft Entwicklern, Anwendungen zu überwachen und Probleme in ihren berei ![exceptions_light](https://user-images.githubusercontent.com/83692067/226637967-4188d024-3ac9-4799-be95-f5ea9c45436f.png) - ### Alarme alerts_management -

- ## Werde Teil unserer Slack Community Sag Hi zu uns auf [Slack](https://signoz.io/slack) 👋

- ## Funktionen: - Einheitliche Benutzeroberfläche für Metriken, Traces und Logs. Keine Notwendigkeit, zwischen Prometheus und Jaeger zu wechseln, um Probleme zu debuggen oder ein separates Log-Tool wie Elastic neben Ihrer Metriken- und Traces-Stack zu verwenden. @@ -93,7 +89,6 @@ Sag Hi zu uns auf [Slack](https://signoz.io/slack) 👋

- ## Wieso SigNoz? Als Entwickler fanden wir es anstrengend, uns für jede kleine Funktion, die wir haben wollten, auf Closed Source SaaS Anbieter verlassen zu müssen. Closed Source Anbieter überraschen ihre Kunden zum Monatsende oft mit hohen Rechnungen, die keine Transparenz bzgl. der Kostenaufteilung bieten. @@ -116,12 +111,10 @@ Wir unterstützen [OpenTelemetry](https://opentelemetry.io) als Bibliothek, mit - Elixir - Rust - Hier findest du die vollständige Liste von unterstützten Programmiersprachen - https://opentelemetry.io/docs/

- ## Erste Schritte mit SigNoz ### Bereitstellung mit Docker @@ -138,7 +131,6 @@ Bitte folge den [hier](https://signoz.io/docs/deployment/helm_chart) aufgelistet

- ## Vergleiche mit bekannten Tools ### SigNoz vs Prometheus @@ -179,7 +171,6 @@ Wir haben Benchmarks veröffentlicht, die Loki mit SigNoz vergleichen. Schauen S

- ## Zum Projekt beitragen Wir ❤️ Beiträge zum Projekt, egal ob große oder kleine. Bitte lies dir zuerst die [CONTRIBUTING.md](CONTRIBUTING.md), durch, bevor du anfängst, Beiträge zu SigNoz zu machen. @@ -197,6 +188,8 @@ Du bist dir nicht sicher, wie du anfangen sollst? Schreib uns einfach auf dem #c #### Frontend - [Palash Gupta](https://github.com/palashgdev) +- [Yunus M](https://github.com/YounixM) +- [Rajat Dabade](https://github.com/Rajat-Dabade) #### DevOps @@ -204,16 +197,12 @@ Du bist dir nicht sicher, wie du anfangen sollst? Schreib uns einfach auf dem #c

- ## Dokumentation Du findest unsere Dokumentation unter https://signoz.io/docs/. Falls etwas unverständlich ist oder fehlt, öffne gerne ein Github Issue mit dem Label `documentation` oder schreib uns über den Community Slack Channel. - -

- ## Gemeinschaft Werde Teil der [slack community](https://signoz.io/slack) um mehr über verteilte Einzelschritt-Fehlersuche, Messung von Systemzuständen oder SigNoz zu erfahren und sich mit anderen Nutzern und Mitwirkenden in Verbindung zu setzen. diff --git a/README.zh-cn.md b/README.zh-cn.md index 32b6328fcb..445474f6ba 100644 --- a/README.zh-cn.md +++ b/README.zh-cn.md @@ -19,7 +19,7 @@ Twitter

-## +## SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力: @@ -67,7 +67,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可 ## 加入我们 Slack 社区 -来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋 +来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋

@@ -83,7 +83,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可 - 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据 -- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据 +- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据 - 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志 @@ -101,7 +101,7 @@ SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可 我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务。 -作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。 +作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。 ### 支持的编程语言: @@ -153,9 +153,9 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric 而且, SigNoz 相较于 Jaeger 拥有更对的高级功能: -- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。 +- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。 -- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。 +- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。

 

@@ -185,7 +185,7 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric 我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。 -如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。 +如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。 ### 项目维护人员 @@ -199,6 +199,8 @@ Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metric #### 前端 - [Palash Gupta](https://github.com/palashgdev) +- [Yunus M](https://github.com/YounixM) +- [Rajat Dabade](https://github.com/Rajat-Dabade) #### 运维开发 diff --git a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml index 9193dcc97e..71421a0196 100644 --- a/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker-swarm/clickhouse-setup/docker-compose.yaml @@ -146,11 +146,11 @@ services: condition: on-failure query-service: - image: signoz/query-service:0.36.2 + image: signoz/query-service:0.37.0 command: [ "-config=/root/config/prometheus.yml", - "--prefer-delta=true" + # "--prefer-delta=true" ] # ports: # - "6060:6060" # pprof port @@ -186,7 +186,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:0.36.2 + image: signoz/frontend:0.37.0 deploy: restart_policy: condition: on-failure @@ -199,7 +199,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector: - image: signoz/signoz-otel-collector:0.88.6 + image: signoz/signoz-otel-collector:0.88.8 command: [ "--config=/etc/otel-collector-config.yaml", @@ -237,7 +237,7 @@ services: - query-service otel-collector-migrator: - image: signoz/signoz-schema-migrator:0.88.6 + image: signoz/signoz-schema-migrator:0.88.8 deploy: restart_policy: condition: on-failure @@ -249,25 +249,6 @@ services: # - clickhouse-2 # - clickhouse-3 - otel-collector-metrics: - image: signoz/signoz-otel-collector:0.88.6 - command: - [ - "--config=/etc/otel-collector-metrics-config.yaml", - "--feature-gates=-pkg.translator.prometheus.NormalizeName" - ] - volumes: - - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - # ports: - # - "1777:1777" # pprof extension - # - "8888:8888" # OtelCollector internal metrics - # - "13133:13133" # Health check extension - # - "55679:55679" # zPages extension - deploy: - restart_policy: - condition: on-failure - <<: *db-depend - logspout: image: "gliderlabs/logspout:v3.2.14" volumes: diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml index 29409919a7..424d717b09 100644 --- a/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker-swarm/clickhouse-setup/otel-collector-config.yaml @@ -15,13 +15,9 @@ receivers: # please remove names from below if you want to collect logs from them - type: filter id: signoz_logs_filter - expr: 'attributes.container_name matches "^signoz_(logspout|frontend|alertmanager|query-service|otel-collector|otel-collector-metrics|clickhouse|zookeeper)"' + expr: 'attributes.container_name matches "^signoz_(logspout|frontend|alertmanager|query-service|otel-collector|clickhouse|zookeeper)"' opencensus: endpoint: 0.0.0.0:55678 - otlp/spanmetrics: - protocols: - grpc: - endpoint: localhost:12345 otlp: protocols: grpc: @@ -69,8 +65,8 @@ processors: # Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels. detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure. timeout: 2s - signozspanmetrics/prometheus: - metrics_exporter: prometheus + signozspanmetrics/cumulative: + metrics_exporter: clickhousemetricswrite latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] dimensions_cache_size: 100000 dimensions: @@ -97,6 +93,20 @@ processors: # num_workers: 4 # queue_size: 100 # retry_on_failure: true + signozspanmetrics/delta: + metrics_exporter: clickhousemetricswrite + latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + # This is added to ensure the uniqueness of the timeseries + # Otherwise, identical timeseries produced by multiple replicas of + # collectors result in incorrect APM metrics + - name: signoz.collector.id exporters: clickhousetraces: @@ -109,8 +119,6 @@ exporters: enabled: true clickhousemetricswrite/prometheus: endpoint: tcp://clickhouse:9000/?database=signoz_metrics - prometheus: - endpoint: 0.0.0.0:8889 # logging: {} clickhouselogsexporter: dsn: tcp://clickhouse:9000/ @@ -140,7 +148,7 @@ service: pipelines: traces: receivers: [jaeger, otlp] - processors: [signozspanmetrics/prometheus, batch] + processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch] exporters: [clickhousetraces] metrics: receivers: [otlp] @@ -154,9 +162,6 @@ service: receivers: [prometheus] processors: [batch] exporters: [clickhousemetricswrite/prometheus] - metrics/spanmetrics: - receivers: [otlp/spanmetrics] - exporters: [prometheus] logs: receivers: [otlp, tcplog/docker] processors: [batch] diff --git a/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml deleted file mode 100644 index 099caa737b..0000000000 --- a/deploy/docker-swarm/clickhouse-setup/otel-collector-metrics-config.yaml +++ /dev/null @@ -1,64 +0,0 @@ -receivers: - prometheus: - config: - scrape_configs: - # otel-collector-metrics internal metrics - - job_name: otel-collector-metrics - scrape_interval: 60s - static_configs: - - targets: - - localhost:8888 - labels: - job_name: otel-collector-metrics - # SigNoz span metrics - - job_name: signozspanmetrics-collector - scrape_interval: 60s - dns_sd_configs: - - names: - - tasks.otel-collector - type: A - port: 8889 - -processors: - batch: - send_batch_size: 10000 - send_batch_max_size: 11000 - timeout: 10s - # memory_limiter: - # # 80% of maximum memory up to 2G - # limit_mib: 1500 - # # 25% of limit up to 2G - # spike_limit_mib: 512 - # check_interval: 5s - # - # # 50% of the maximum memory - # limit_percentage: 50 - # # 20% of max memory usage spike expected - # spike_limit_percentage: 20 - # queued_retry: - # num_workers: 4 - # queue_size: 100 - # retry_on_failure: true - -exporters: - clickhousemetricswrite: - endpoint: tcp://clickhouse:9000/?database=signoz_metrics - -extensions: - health_check: - endpoint: 0.0.0.0:13133 - zpages: - endpoint: 0.0.0.0:55679 - pprof: - endpoint: 0.0.0.0:1777 - -service: - telemetry: - metrics: - address: 0.0.0.0:8888 - extensions: [health_check, zpages, pprof] - pipelines: - metrics: - receivers: [prometheus] - processors: [batch] - exporters: [clickhousemetricswrite] diff --git a/deploy/docker/clickhouse-setup/docker-compose-core.yaml b/deploy/docker/clickhouse-setup/docker-compose-core.yaml index 81969766cc..866029e73d 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-core.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-core.yaml @@ -66,7 +66,7 @@ services: - --storage.path=/data otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -81,7 +81,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` otel-collector: container_name: signoz-otel-collector - image: signoz/signoz-otel-collector:0.88.6 + image: signoz/signoz-otel-collector:0.88.8 command: [ "--config=/etc/otel-collector-config.yaml", @@ -116,28 +116,6 @@ services: query-service: condition: service_healthy - otel-collector-metrics: - container_name: signoz-otel-collector-metrics - image: signoz/signoz-otel-collector:0.88.6 - command: - [ - "--config=/etc/otel-collector-metrics-config.yaml", - "--feature-gates=-pkg.translator.prometheus.NormalizeName" - ] - volumes: - - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - # ports: - # - "1777:1777" # pprof extension - # - "8888:8888" # OtelCollector internal metrics - # - "13133:13133" # Health check extension - # - "55679:55679" # zPages extension - restart: on-failure - depends_on: - clickhouse: - condition: service_healthy - otel-collector-migrator: - condition: service_completed_successfully - logspout: image: "gliderlabs/logspout:v3.2.14" container_name: signoz-logspout diff --git a/deploy/docker/clickhouse-setup/docker-compose-local.yaml b/deploy/docker/clickhouse-setup/docker-compose-local.yaml index a92c3dbcd9..248c7bf9f6 100644 --- a/deploy/docker/clickhouse-setup/docker-compose-local.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose-local.yaml @@ -25,7 +25,7 @@ services: command: [ "-config=/root/config/prometheus.yml", - "--prefer-delta=true" + # "--prefer-delta=true" ] ports: - "6060:6060" diff --git a/deploy/docker/clickhouse-setup/docker-compose.yaml b/deploy/docker/clickhouse-setup/docker-compose.yaml index bad6e8ba74..ef41cd3ae8 100644 --- a/deploy/docker/clickhouse-setup/docker-compose.yaml +++ b/deploy/docker/clickhouse-setup/docker-compose.yaml @@ -164,12 +164,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` query-service: - image: signoz/query-service:${DOCKER_TAG:-0.36.2} + image: signoz/query-service:${DOCKER_TAG:-0.37.0} container_name: signoz-query-service command: [ "-config=/root/config/prometheus.yml", - "--prefer-delta=true" + # "--prefer-delta=true" ] # ports: # - "6060:6060" # pprof port @@ -203,7 +203,7 @@ services: <<: *db-depend frontend: - image: signoz/frontend:${DOCKER_TAG:-0.36.2} + image: signoz/frontend:${DOCKER_TAG:-0.37.0} container_name: signoz-frontend restart: on-failure depends_on: @@ -215,7 +215,7 @@ services: - ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf otel-collector-migrator: - image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.6} + image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.8} container_name: otel-migrator command: - "--dsn=tcp://clickhouse:9000" @@ -229,7 +229,7 @@ services: otel-collector: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6} + image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.8} container_name: signoz-otel-collector command: [ @@ -268,24 +268,6 @@ services: query-service: condition: service_healthy - otel-collector-metrics: - image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.6} - container_name: signoz-otel-collector-metrics - command: - [ - "--config=/etc/otel-collector-metrics-config.yaml", - "--feature-gates=-pkg.translator.prometheus.NormalizeName" - ] - volumes: - - ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml - # ports: - # - "1777:1777" # pprof extension - # - "8888:8888" # OtelCollector internal metrics - # - "13133:13133" # Health check extension - # - "55679:55679" # zPages extension - restart: on-failure - <<: *db-depend - logspout: image: "gliderlabs/logspout:v3.2.14" container_name: signoz-logspout diff --git a/deploy/docker/clickhouse-setup/otel-collector-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-config.yaml index 204dcd9511..f3d6900e6c 100644 --- a/deploy/docker/clickhouse-setup/otel-collector-config.yaml +++ b/deploy/docker/clickhouse-setup/otel-collector-config.yaml @@ -15,13 +15,9 @@ receivers: # please remove names from below if you want to collect logs from them - type: filter id: signoz_logs_filter - expr: 'attributes.container_name matches "^signoz-(logspout|frontend|alertmanager|query-service|otel-collector|otel-collector-metrics|clickhouse|zookeeper)"' + expr: 'attributes.container_name matches "^signoz-(logspout|frontend|alertmanager|query-service|otel-collector|clickhouse|zookeeper)"' opencensus: endpoint: 0.0.0.0:55678 - otlp/spanmetrics: - protocols: - grpc: - endpoint: localhost:12345 otlp: protocols: grpc: @@ -66,8 +62,9 @@ processors: send_batch_size: 10000 send_batch_max_size: 11000 timeout: 10s - signozspanmetrics/prometheus: - metrics_exporter: prometheus + signozspanmetrics/cumulative: + metrics_exporter: clickhousemetricswrite + metrics_flush_interval: 60s latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] dimensions_cache_size: 100000 dimensions: @@ -98,6 +95,21 @@ processors: # Using OTEL_RESOURCE_ATTRIBUTES envvar, env detector adds custom labels. detectors: [env, system] # include ec2 for AWS, gcp for GCP and azure for Azure. timeout: 2s + signozspanmetrics/delta: + metrics_exporter: clickhousemetricswrite + metrics_flush_interval: 60s + latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ] + dimensions_cache_size: 100000 + aggregation_temporality: AGGREGATION_TEMPORALITY_DELTA + dimensions: + - name: service.namespace + default: default + - name: deployment.environment + default: default + # This is added to ensure the uniqueness of the timeseries + # Otherwise, identical timeseries produced by multiple replicas of + # collectors result in incorrect APM metrics + - name: signoz.collector.id extensions: health_check: @@ -118,8 +130,6 @@ exporters: enabled: true clickhousemetricswrite/prometheus: endpoint: tcp://clickhouse:9000/?database=signoz_metrics - prometheus: - endpoint: 0.0.0.0:8889 # logging: {} clickhouselogsexporter: @@ -145,7 +155,7 @@ service: pipelines: traces: receivers: [jaeger, otlp] - processors: [signozspanmetrics/prometheus, batch] + processors: [signozspanmetrics/cumulative, signozspanmetrics/delta, batch] exporters: [clickhousetraces] metrics: receivers: [otlp] @@ -159,9 +169,6 @@ service: receivers: [prometheus] processors: [batch] exporters: [clickhousemetricswrite/prometheus] - metrics/spanmetrics: - receivers: [otlp/spanmetrics] - exporters: [prometheus] logs: receivers: [otlp, tcplog/docker] processors: [batch] diff --git a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml b/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml deleted file mode 100644 index 7543d1f6f6..0000000000 --- a/deploy/docker/clickhouse-setup/otel-collector-metrics-config.yaml +++ /dev/null @@ -1,69 +0,0 @@ -receivers: - otlp: - protocols: - grpc: - http: - prometheus: - config: - scrape_configs: - # otel-collector-metrics internal metrics - - job_name: otel-collector-metrics - scrape_interval: 60s - static_configs: - - targets: - - localhost:8888 - labels: - job_name: otel-collector-metrics - # SigNoz span metrics - - job_name: signozspanmetrics-collector - scrape_interval: 60s - static_configs: - - targets: - - otel-collector:8889 - -processors: - batch: - send_batch_size: 10000 - send_batch_max_size: 11000 - timeout: 10s - # memory_limiter: - # # 80% of maximum memory up to 2G - # limit_mib: 1500 - # # 25% of limit up to 2G - # spike_limit_mib: 512 - # check_interval: 5s - # - # # 50% of the maximum memory - # limit_percentage: 50 - # # 20% of max memory usage spike expected - # spike_limit_percentage: 20 - # queued_retry: - # num_workers: 4 - # queue_size: 100 - # retry_on_failure: true - -extensions: - health_check: - endpoint: 0.0.0.0:13133 - zpages: - endpoint: 0.0.0.0:55679 - pprof: - endpoint: 0.0.0.0:1777 - -exporters: - clickhousemetricswrite: - endpoint: tcp://clickhouse:9000/?database=signoz_metrics - -service: - telemetry: - metrics: - address: 0.0.0.0:8888 - extensions: - - health_check - - zpages - - pprof - pipelines: - metrics: - receivers: [prometheus] - processors: [batch] - exporters: [clickhousemetricswrite] diff --git a/ee/query-service/app/server.go b/ee/query-service/app/server.go index 699894e691..a5c7c1db22 100644 --- a/ee/query-service/app/server.go +++ b/ee/query-service/app/server.go @@ -331,6 +331,7 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, e apiHandler.RegisterMetricsRoutes(r, am) apiHandler.RegisterLogsRoutes(r, am) apiHandler.RegisterQueryRangeV3Routes(r, am) + apiHandler.RegisterQueryRangeV4Routes(r, am) c := cors.New(cors.Options{ AllowedOrigins: []string{"*"}, diff --git a/frontend/package.json b/frontend/package.json index f0edc5c959..4a57943602 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "@mdx-js/loader": "2.3.0", "@mdx-js/react": "2.3.0", "@monaco-editor/react": "^4.3.1", + "@signozhq/design-tokens": "0.0.6", "@uiw/react-md-editor": "3.23.5", "@xstate/react": "^3.0.0", "ansi-to-html": "0.7.2", diff --git a/frontend/public/Logos/signoz-brand-logo.svg b/frontend/public/Logos/signoz-brand-logo.svg new file mode 100644 index 0000000000..aaa8a77669 --- /dev/null +++ b/frontend/public/Logos/signoz-brand-logo.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/frontend/public/locales/en/titles.json b/frontend/public/locales/en/titles.json index 24b0f45269..71ec805100 100644 --- a/frontend/public/locales/en/titles.json +++ b/frontend/public/locales/en/titles.json @@ -31,6 +31,7 @@ "NOT_FOUND": "SigNoz | Page Not Found", "LOGS": "SigNoz | Logs", "LOGS_EXPLORER": "SigNoz | Logs Explorer", + "OLD_LOGS_EXPLORER": "SigNoz | Old Logs Explorer", "LIVE_LOGS": "SigNoz | Live Logs", "LOGS_PIPELINES": "SigNoz | Logs Pipelines", "HOME_PAGE": "Open source Observability Platform | SigNoz", diff --git a/frontend/src/AppRoutes/pageComponents.ts b/frontend/src/AppRoutes/pageComponents.ts index 638c019506..aca7de9730 100644 --- a/frontend/src/AppRoutes/pageComponents.ts +++ b/frontend/src/AppRoutes/pageComponents.ts @@ -112,17 +112,25 @@ export const MySettings = Loadable( ); export const Logs = Loadable( - () => import(/* webpackChunkName: "Logs" */ 'pages/Logs'), + () => import(/* webpackChunkName: "Logs" */ 'pages/LogsModulePage'), ); export const LogsExplorer = Loadable( - () => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'), + () => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsModulePage'), +); + +export const OldLogsExplorer = Loadable( + () => import(/* webpackChunkName: "Logs Explorer" */ 'pages/Logs'), ); export const LiveLogs = Loadable( () => import(/* webpackChunkName: "Live Logs" */ 'pages/LiveLogs'), ); +export const PipelinePage = Loadable( + () => import(/* webpackChunkName: "Pipelines" */ 'pages/LogsModulePage'), +); + export const Login = Loadable( () => import(/* webpackChunkName: "Login" */ 'pages/Login'), ); @@ -151,10 +159,6 @@ export const LogsIndexToFields = Loadable( import(/* webpackChunkName: "LogsIndexToFields Page" */ 'pages/LogsSettings'), ); -export const PipelinePage = Loadable( - () => import(/* webpackChunkName: "Pipelines" */ 'pages/Pipelines'), -); - export const BillingPage = Loadable( () => import(/* webpackChunkName: "BillingPage" */ 'pages/Billing'), ); diff --git a/frontend/src/AppRoutes/routes.ts b/frontend/src/AppRoutes/routes.ts index 2f4142c809..6fa3accde0 100644 --- a/frontend/src/AppRoutes/routes.ts +++ b/frontend/src/AppRoutes/routes.ts @@ -23,6 +23,7 @@ import { LogsIndexToFields, MySettings, NewDashboardPage, + OldLogsExplorer, Onboarding, OrganizationSettings, PasswordReset, @@ -246,6 +247,13 @@ const routes: AppRoutes[] = [ key: 'LOGS_EXPLORER', isPrivate: true, }, + { + path: ROUTES.OLD_LOGS_EXPLORER, + exact: true, + component: OldLogsExplorer, + key: 'OLD_LOGS_EXPLORER', + isPrivate: true, + }, { path: ROUTES.LIVE_LOGS, exact: true, diff --git a/frontend/src/api/metrics/ApDex/getMetricMeta.ts b/frontend/src/api/metrics/ApDex/getMetricMeta.ts index 36466e1e69..e3045730a7 100644 --- a/frontend/src/api/metrics/ApDex/getMetricMeta.ts +++ b/frontend/src/api/metrics/ApDex/getMetricMeta.ts @@ -4,5 +4,6 @@ import { MetricMetaProps } from 'types/api/metrics/getApDex'; export const getMetricMeta = ( metricName: string, + servicename: string, ): Promise> => - axios.get(`/metric_meta?metricName=${metricName}`); + axios.get(`/metric_meta?metricName=${metricName}&serviceName=${servicename}`); diff --git a/frontend/src/api/utils.ts b/frontend/src/api/utils.ts index 140e793e35..bd81719eee 100644 --- a/frontend/src/api/utils.ts +++ b/frontend/src/api/utils.ts @@ -66,7 +66,11 @@ export const Logout = (): void => { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - window.Intercom('shutdown'); + if (window && window.Intercom) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.Intercom('shutdown'); + } history.push(ROUTES.LOGIN); }; diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss new file mode 100644 index 0000000000..3304232d65 --- /dev/null +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.styles.scss @@ -0,0 +1,90 @@ +.custom-time-picker { + display: flex; + flex-direction: column; +} + +.time-options-container { + .time-options-item { + margin: 2px 0; + padding: 8px; + border-radius: 2px; + + &.active { + background-color: rgba($color: #000000, $alpha: 0.2); + + &:hover { + cursor: pointer; + background-color: rgba($color: #000000, $alpha: 0.3); + } + } + + &:hover { + cursor: pointer; + background-color: rgba($color: #000000, $alpha: 0.3); + } + } +} + +.time-selection-dropdown-content { + min-width: 172px; + width: 100%; +} + +.timeSelection-input { + display: flex; + gap: 8px; + align-items: center; + padding: 4px 8px; + padding-left: 0px !important; + + input::placeholder { + color: white; + } + + input:focus::placeholder { + color: rgba($color: #ffffff, $alpha: 0.4); + } +} + +.valid-format-error { + margin-top: 4px; + color: var(--bg-cherry-400) !important; + font-size: 13px !important; + font-weight: 400 !important; +} + +.lightMode { + .time-options-container { + .time-options-item { + &.active { + background-color: rgba($color: #ffffff, $alpha: 0.2); + + &:hover { + cursor: pointer; + background-color: rgba($color: #ffffff, $alpha: 0.3); + } + } + + &:hover { + cursor: pointer; + background-color: rgba($color: #ffffff, $alpha: 0.3); + } + } + } + + .timeSelection-input { + display: flex; + gap: 8px; + align-items: center; + padding: 4px 8px; + padding-left: 0px !important; + + input::placeholder { + color: var(---bg-ink-300); + } + + input:focus::placeholder { + color: rgba($color: #000000, $alpha: 0.4); + } + } +} diff --git a/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx new file mode 100644 index 0000000000..abefa8fd6f --- /dev/null +++ b/frontend/src/components/CustomTimePicker/CustomTimePicker.tsx @@ -0,0 +1,240 @@ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +import './CustomTimePicker.styles.scss'; + +import { Input, Popover, Tooltip, Typography } from 'antd'; +import cx from 'classnames'; +import { Options } from 'container/TopNav/DateTimeSelection/config'; +import dayjs from 'dayjs'; +import debounce from 'lodash-es/debounce'; +import { CheckCircle, ChevronDown, Clock } from 'lucide-react'; +import { ChangeEvent, useEffect, useState } from 'react'; +import { popupContainer } from 'utils/selectPopupContainer'; + +const maxAllowedMinTimeInMonths = 6; + +interface CustomTimePickerProps { + onSelect: (value: string) => void; + onError: (value: boolean) => void; + items: any[]; + selectedValue: string; + selectedTime: string; + onValidCustomDateChange: ([t1, t2]: any[]) => void; +} + +function CustomTimePicker({ + onSelect, + onError, + items, + selectedValue, + selectedTime, + onValidCustomDateChange, +}: CustomTimePickerProps): JSX.Element { + const [open, setOpen] = useState(false); + const [ + selectedTimePlaceholderValue, + setSelectedTimePlaceholderValue, + ] = useState('Select / Enter Time Range'); + + const [inputValue, setInputValue] = useState(''); + const [inputStatus, setInputStatus] = useState<'' | 'error' | 'success'>(''); + const [inputErrorMessage, setInputErrorMessage] = useState( + null, + ); + const [isInputFocused, setIsInputFocused] = useState(false); + + const getSelectedTimeRangeLabel = ( + selectedTime: string, + selectedTimeValue: string, + ): string => { + if (selectedTime === 'custom') { + return selectedTimeValue; + } + + for (let index = 0; index < Options.length; index++) { + if (Options[index].value === selectedTime) { + return Options[index].label; + } + } + + return ''; + }; + + useEffect(() => { + const value = getSelectedTimeRangeLabel(selectedTime, selectedValue); + + setSelectedTimePlaceholderValue(value); + }, [selectedTime, selectedValue]); + + const hide = (): void => { + setOpen(false); + }; + + const handleOpenChange = (newOpen: boolean): void => { + setOpen(newOpen); + }; + + const debouncedHandleInputChange = debounce((inputValue): void => { + const isValidFormat = /^(\d+)([mhdw])$/.test(inputValue); + if (isValidFormat) { + setInputStatus('success'); + onError(false); + setInputErrorMessage(null); + + const match = inputValue.match(/^(\d+)([mhdw])$/); + + const value = parseInt(match[1], 10); + const unit = match[2]; + + const currentTime = dayjs(); + const maxAllowedMinTime = currentTime.subtract( + maxAllowedMinTimeInMonths, + 'month', + ); + let minTime = null; + + switch (unit) { + case 'm': + minTime = currentTime.subtract(value, 'minute'); + break; + + case 'h': + minTime = currentTime.subtract(value, 'hour'); + break; + case 'd': + minTime = currentTime.subtract(value, 'day'); + break; + case 'w': + minTime = currentTime.subtract(value, 'week'); + break; + default: + break; + } + + if (minTime && minTime < maxAllowedMinTime) { + setInputStatus('error'); + onError(true); + setInputErrorMessage('Please enter time less than 6 months'); + } else { + onValidCustomDateChange([minTime, currentTime]); + } + } else { + setInputStatus('error'); + onError(true); + setInputErrorMessage(null); + } + }, 300); + + const handleInputChange = (event: ChangeEvent): void => { + const inputValue = event.target.value; + + if (inputValue.length > 0) { + setOpen(false); + } else { + setOpen(true); + } + + setInputValue(inputValue); + + // Call the debounced function with the input value + debouncedHandleInputChange(inputValue); + }; + + const content = ( +
+
+ {items.map(({ value, label }) => ( +
{ + onSelect(value); + setSelectedTimePlaceholderValue(label); + setInputStatus(''); + onError(false); + setInputErrorMessage(null); + setInputValue(''); + hide(); + }} + key={value} + className={cx( + 'time-options-item', + selectedValue === value ? 'active' : '', + )} + > + {label} +
+ ))} +
+
+ ); + + const handleFocus = (): void => { + setIsInputFocused(true); + }; + + const handleBlur = (): void => { + setIsInputFocused(false); + }; + + return ( +
+ + + ) : ( + + + + ) + } + suffix={ + { + setOpen(!open); + }} + /> + } + /> + + + {inputStatus === 'error' && inputErrorMessage && ( + + {inputErrorMessage} + + )} +
+ ); +} + +export default CustomTimePicker; diff --git a/frontend/src/components/Logs/RawLogView/styles.ts b/frontend/src/components/Logs/RawLogView/styles.ts index 4944d05f74..a3df1c3dca 100644 --- a/frontend/src/components/Logs/RawLogView/styles.ts +++ b/frontend/src/components/Logs/RawLogView/styles.ts @@ -48,8 +48,9 @@ export const RawLogContent = styled.div` line-clamp: ${linesPerRow}; -webkit-box-orient: vertical;`}; - font-size: 1rem; - line-height: 2rem; + font-size: 12px; + line-height: 24px; + padding: 4px; cursor: ${({ $isActiveLog, $isReadOnly }): string => $isActiveLog || $isReadOnly ? 'initial' : 'pointer'}; diff --git a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx index 55af931d5c..c0d77c967b 100644 --- a/frontend/src/components/ResizeTable/DynamicColumnTable.tsx +++ b/frontend/src/components/ResizeTable/DynamicColumnTable.tsx @@ -43,7 +43,7 @@ function DynamicColumnTable({ : undefined, ); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [columns]); + }, [columns, dynamicColumns]); const onToggleHandler = (index: number) => ( checked: boolean, diff --git a/frontend/src/components/WelcomeLeftContainer/styles.ts b/frontend/src/components/WelcomeLeftContainer/styles.ts index 70428a7f1d..be312edac1 100644 --- a/frontend/src/components/WelcomeLeftContainer/styles.ts +++ b/frontend/src/components/WelcomeLeftContainer/styles.ts @@ -13,6 +13,7 @@ export const Container = styled.div` &&& { display: flex; justify-content: center; + gap: 16px; align-items: center; min-height: 100vh; diff --git a/frontend/src/constants/global.ts b/frontend/src/constants/global.ts index d2a455ea57..42fb29720b 100644 --- a/frontend/src/constants/global.ts +++ b/frontend/src/constants/global.ts @@ -1,2 +1,4 @@ const MAX_RPS_LIMIT = 100; export { MAX_RPS_LIMIT }; + +export const LEGEND = 'legend'; diff --git a/frontend/src/constants/routes.ts b/frontend/src/constants/routes.ts index 208e83e525..39456318a7 100644 --- a/frontend/src/constants/routes.ts +++ b/frontend/src/constants/routes.ts @@ -6,7 +6,6 @@ const ROUTES = { TRACE: '/trace', TRACE_DETAIL: '/trace/:id', TRACES_EXPLORER: '/traces-explorer', - SETTINGS: '/settings', GET_STARTED: '/get-started', USAGE_EXPLORER: '/usage-explorer', APPLICATION: '/services', @@ -23,15 +22,18 @@ const ROUTES = { ERROR_DETAIL: '/error-detail', VERSION: '/status', MY_SETTINGS: '/my-settings', + SETTINGS: '/settings', ORG_SETTINGS: '/settings/org-settings', INGESTION_SETTINGS: '/settings/ingestion-settings', SOMETHING_WENT_WRONG: '/something-went-wrong', UN_AUTHORIZED: '/un-authorized', NOT_FOUND: '/not-found', - LOGS: '/logs', - LOGS_EXPLORER: '/logs-explorer', - LIVE_LOGS: '/logs-explorer/live', - LOGS_PIPELINES: '/pipelines', + LOGS_BASE: '/logs', + LOGS: '/logs/logs-explorer', + OLD_LOGS_EXPLORER: '/logs/old-logs-explorer', + LOGS_EXPLORER: '/logs/logs-explorer', + LIVE_LOGS: '/logs/logs-explorer/live', + LOGS_PIPELINES: '/logs/pipelines', HOME_PAGE: '/', PASSWORD_RESET: '/password-reset', LIST_LICENSES: '/licenses', diff --git a/frontend/src/constants/theme.ts b/frontend/src/constants/theme.ts index 757926c0fe..427a13efe8 100644 --- a/frontend/src/constants/theme.ts +++ b/frontend/src/constants/theme.ts @@ -1,5 +1,6 @@ const themeColors = { chartcolors: { + robin: '#3F5ECC', dodgerBlue: '#2F80ED', mediumOrchid: '#BB6BD9', seaBuckthorn: '#F2994A', diff --git a/frontend/src/container/AllAlertChannels/styles.ts b/frontend/src/container/AllAlertChannels/styles.ts index 209860b867..454e48aeaf 100644 --- a/frontend/src/container/AllAlertChannels/styles.ts +++ b/frontend/src/container/AllAlertChannels/styles.ts @@ -15,6 +15,7 @@ export const ButtonContainer = styled.div` align-items: center; margin-top: 1rem; margin-bottom: 1rem; + padding-right: 1rem; } `; diff --git a/frontend/src/container/AllError/index.tsx b/frontend/src/container/AllError/index.tsx index 4bc7d199e1..e8c13d88cd 100644 --- a/frontend/src/container/AllError/index.tsx +++ b/frontend/src/container/AllError/index.tsx @@ -48,6 +48,15 @@ import { urlKey, } from './utils'; +type QueryParams = { + order: string; + offset: number; + orderParam: string; + pageSize: number; + exceptionType?: string; + serviceName?: string; +}; + function AllErrors(): JSX.Element { const { maxTime, minTime, loading } = useSelector( (state) => state.globalTime, @@ -162,16 +171,23 @@ function AllErrors(): JSX.Element { filterKey, filterValue || '', ); - history.replace( - `${pathname}?${createQueryParams({ - order: updatedOrder, - offset: getUpdatedOffset, - orderParam: getUpdatedParams, - pageSize: getUpdatedPageSize, - exceptionType: exceptionFilterValue, - serviceName: serviceFilterValue, - })}`, - ); + + const queryParams: QueryParams = { + order: updatedOrder, + offset: getUpdatedOffset, + orderParam: getUpdatedParams, + pageSize: getUpdatedPageSize, + }; + + if (exceptionFilterValue && exceptionFilterValue !== 'undefined') { + queryParams.exceptionType = exceptionFilterValue; + } + + if (serviceFilterValue && serviceFilterValue !== 'undefined') { + queryParams.serviceName = serviceFilterValue; + } + + history.replace(`${pathname}?${createQueryParams(queryParams)}`); confirm(); }, [ @@ -198,8 +214,10 @@ function AllErrors(): JSX.Element { - setSelectedKeys(e.target.value ? [e.target.value] : []) + onChange={ + (e): void => setSelectedKeys(e.target.value ? [e.target.value] : []) + + // Need to fix this logic, when the value in empty, it's setting undefined string as value } allowClear defaultValue={getDefaultFilterValue( diff --git a/frontend/src/container/AppLayout/AppLayout.styles.scss b/frontend/src/container/AppLayout/AppLayout.styles.scss new file mode 100644 index 0000000000..b62cab0a0d --- /dev/null +++ b/frontend/src/container/AppLayout/AppLayout.styles.scss @@ -0,0 +1,53 @@ +@import '@signozhq/design-tokens'; + +.app-layout { + height: 100%; + width: 100%; + + .app-content { + width: 100%; + overflow: auto; + } +} + +.isDarkMode { + .app-layout { + .app-content { + background: #0b0c0e; + } + } +} + +.isLightMode { + .app-layout { + .app-content { + background: #ffffff; + } + } +} + +.trial-expiry-banner { + padding: 8px; + background-color: #f25733; + color: white; + text-align: center; +} + +.upgrade-link { + padding: 0px; + padding-right: 4px; + display: inline !important; + color: white; + text-decoration: underline; + text-decoration-color: white; + text-decoration-thickness: 2px; + text-underline-offset: 2px; + + &:hover { + color: white; + text-decoration: underline; + text-decoration-color: white; + text-decoration-thickness: 2px; + text-underline-offset: 2px; + } +} diff --git a/frontend/src/container/AppLayout/index.tsx b/frontend/src/container/AppLayout/index.tsx index 15f71fc692..28b48223b4 100644 --- a/frontend/src/container/AppLayout/index.tsx +++ b/frontend/src/container/AppLayout/index.tsx @@ -1,13 +1,22 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable jsx-a11y/anchor-is-valid */ +import './AppLayout.styles.scss'; + +import { Flex } from 'antd'; import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs'; import getUserLatestVersion from 'api/user/getLatestVersion'; import getUserVersion from 'api/user/getVersion'; +import cx from 'classnames'; import ROUTES from 'constants/routes'; -import Header from 'container/Header'; import SideNav from 'container/SideNav'; import TopNav from 'container/TopNav'; +import { useIsDarkMode } from 'hooks/useDarkMode'; +import useLicense from 'hooks/useLicense'; import { useNotifications } from 'hooks/useNotifications'; +import history from 'lib/history'; import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback'; -import { ReactNode, useEffect, useMemo, useRef } from 'react'; +import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { ErrorBoundary } from 'react-error-boundary'; import { Helmet } from 'react-helmet-async'; import { useTranslation } from 'react-i18next'; @@ -25,15 +34,20 @@ import { UPDATE_LATEST_VERSION_ERROR, } from 'types/actions/app'; import AppReducer from 'types/reducer/app'; +import { getFormattedDate, getRemainingDays } from 'utils/timeUtils'; import { ChildrenContainer, Layout, LayoutContent } from './styles'; import { getRouteKey } from './utils'; function AppLayout(props: AppLayoutProps): JSX.Element { - const { isLoggedIn, user } = useSelector( + const { isLoggedIn, user, role } = useSelector( (state) => state.app, ); + const isDarkMode = useIsDarkMode(); + + const { data: licenseData, isFetching } = useLicense(); + const { pathname } = useLocation(); const { t } = useTranslation(['titles']); @@ -196,25 +210,68 @@ function AppLayout(props: AppLayoutProps): JSX.Element { const renderFullScreen = pathname === ROUTES.GET_STARTED || pathname === ROUTES.WORKSPACE_LOCKED; + const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false); + + useEffect(() => { + if ( + !isFetching && + licenseData?.payload?.onTrial && + !licenseData?.payload?.trialConvertedToSubscription && + !licenseData?.payload?.workSpaceBlock && + getRemainingDays(licenseData?.payload.trialEnd) < 7 + ) { + setShowTrialExpiryBanner(true); + } + }, [licenseData, isFetching]); + + const handleUpgrade = (): void => { + if (role === 'ADMIN') { + history.push(ROUTES.BILLING); + } + }; + return ( - + {pageTitle} - {isToDisplayLayout &&
} - - {isToDisplayLayout && !renderFullScreen && } + {showTrialExpiryBanner && ( +
+ You are in free trial period. Your free trial will end on{' '} + + {getFormattedDate(licenseData?.payload?.trialEnd || Date.now())}. + + {role === 'ADMIN' ? ( + + {' '} + Please{' '} + + upgrade + + to continue using SigNoz features. + + ) : ( + 'Please contact your administrator for upgrading to a paid plan.' + )} +
+ )} - - - - {isToDisplayLayout && !renderFullScreen && } - {children} - - - -
+ + {isToDisplayLayout && !renderFullScreen && ( + + )} +
+ + + + {isToDisplayLayout && !renderFullScreen && } + {children} + + + +
+
); } diff --git a/frontend/src/container/AppLayout/styles.ts b/frontend/src/container/AppLayout/styles.ts index 8bed914d66..f266087895 100644 --- a/frontend/src/container/AppLayout/styles.ts +++ b/frontend/src/container/AppLayout/styles.ts @@ -13,6 +13,7 @@ export const Layout = styled(LayoutComponent)` export const LayoutContent = styled(LayoutComponent.Content)` overflow-y: auto; + height: 100%; `; export const ChildrenContainer = styled.div` diff --git a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx index 7e22e3a11c..73b1f4715e 100644 --- a/frontend/src/container/FormAlertRules/ChartPreview/index.tsx +++ b/frontend/src/container/FormAlertRules/ChartPreview/index.tsx @@ -150,6 +150,8 @@ function ChartPreview({ thresholdUnit: alertDef?.condition.targetUnit, }, ], + softMax: null, + softMin: null, }), [ yAxisUnit, diff --git a/frontend/src/container/FullViewHeader/FullViewHeader.styles.scss b/frontend/src/container/FullViewHeader/FullViewHeader.styles.scss new file mode 100644 index 0000000000..98f2897902 --- /dev/null +++ b/frontend/src/container/FullViewHeader/FullViewHeader.styles.scss @@ -0,0 +1,37 @@ +.full-view-header-container { + display: flex; + justify-content: flex-end; + align-items: center; + padding: 24px 0; + + .brand-logo { + display: flex; + justify-content: center; + align-items: center; + gap: 16px; + cursor: pointer; + + img { + height: 32px; + width: 32px; + } + + .brand-logo-name { + font-family: 'Work Sans', sans-serif; + font-size: 24px; + font-style: normal; + font-weight: 500; + line-height: 18px; + + color: #fff; + } + } +} + +.lightMode { + .brand-logo { + .brand-logo-name { + color: black; + } + } +} diff --git a/frontend/src/container/FullViewHeader/FullViewHeader.tsx b/frontend/src/container/FullViewHeader/FullViewHeader.tsx new file mode 100644 index 0000000000..8fa19b8ee4 --- /dev/null +++ b/frontend/src/container/FullViewHeader/FullViewHeader.tsx @@ -0,0 +1,28 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +import './FullViewHeader.styles.scss'; + +import history from 'lib/history'; + +export default function FullViewHeader({ + overrideRoute, +}: { + overrideRoute?: string; +}): React.ReactElement { + const handleLogoClick = (): void => { + history.push(overrideRoute || '/'); + }; + return ( +
+
+ SigNoz + +
SigNoz
+
+
+ ); +} + +FullViewHeader.defaultProps = { + overrideRoute: '/', +}; diff --git a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx index db42625f1d..4ee4c54e93 100644 --- a/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/FullView/index.tsx @@ -132,6 +132,8 @@ function FullView({ thresholds: widget.thresholds, minTimeScale, maxTimeScale, + softMax: widget.softMax === undefined ? null : widget.softMax, + softMin: widget.softMin === undefined ? null : widget.softMin, }); setChartOptions(newChartOptions); diff --git a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx index 4fdbc05be7..34288a0b19 100644 --- a/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/WidgetGraphComponent.tsx @@ -46,6 +46,7 @@ function WidgetGraphComponent({ data, options, onDragSelect, + graphVisibility, }: WidgetGraphComponentProps): JSX.Element { const [deleteModal, setDeleteModal] = useState(false); const [hovered, setHovered] = useState(false); @@ -83,6 +84,10 @@ function WidgetGraphComponent({ setGraphsVisibilityStates(localStoredVisibilityStates); }, [localStoredVisibilityStates]); + graphVisibility?.forEach((state, index) => { + lineChartRef.current?.toggleGraph(index, state); + }); + const { setLayouts, selectedDashboard, setSelectedDashboard } = useDashboard(); const featureResponse = useSelector( diff --git a/frontend/src/container/GridCardLayout/GridCard/index.tsx b/frontend/src/container/GridCardLayout/GridCard/index.tsx index 32454bb28f..c1c1f99c93 100644 --- a/frontend/src/container/GridCardLayout/GridCard/index.tsx +++ b/frontend/src/container/GridCardLayout/GridCard/index.tsx @@ -1,10 +1,15 @@ +import { QueryParams } from 'constants/query'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange'; import { useStepInterval } from 'hooks/queryBuilder/useStepInterval'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useResizeObserver } from 'hooks/useDimensions'; import { useIntersectionObserver } from 'hooks/useIntersectionObserver'; +import useUrlQuery from 'hooks/useUrlQuery'; import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables'; +import GetMinMax from 'lib/getMinMax'; +import getTimeString from 'lib/getTimeString'; +import history from 'lib/history'; import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions'; import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData'; import isEmpty from 'lodash-es/isEmpty'; @@ -12,6 +17,7 @@ import _noop from 'lodash-es/noop'; import { useDashboard } from 'providers/Dashboard/Dashboard'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; +import { useLocation } from 'react-router-dom'; import { UpdateTimeInterval } from 'store/actions'; import { AppState } from 'store/reducers'; import { GlobalReducer } from 'types/reducer/globalTime'; @@ -37,6 +43,12 @@ function GridCardGraph({ const { toScrollWidgetId, setToScrollWidgetId } = useDashboard(); const [minTimeScale, setMinTimeScale] = useState(); const [maxTimeScale, setMaxTimeScale] = useState(); + const urlQuery = useUrlQuery(); + const location = useLocation(); + const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector< + AppState, + GlobalReducer + >((state) => state.globalTime); const onDragSelect = useCallback( (start: number, end: number): void => { @@ -46,10 +58,44 @@ function GridCardGraph({ if (startTimestamp !== endTimestamp) { dispatch(UpdateTimeInterval('custom', [startTimestamp, endTimestamp])); } + + const { maxTime, minTime } = GetMinMax('custom', [ + startTimestamp, + endTimestamp, + ]); + + urlQuery.set(QueryParams.startTime, minTime.toString()); + urlQuery.set(QueryParams.endTime, maxTime.toString()); + const generatedUrl = `${location.pathname}?${urlQuery.toString()}`; + history.push(generatedUrl); }, - [dispatch], + [dispatch, location.pathname, urlQuery], ); + const handleBackNavigation = (): void => { + const searchParams = new URLSearchParams(window.location.search); + const startTime = searchParams.get(QueryParams.startTime); + const endTime = searchParams.get(QueryParams.endTime); + + if (startTime && endTime && startTime !== endTime) { + dispatch( + UpdateTimeInterval('custom', [ + parseInt(getTimeString(startTime), 10), + parseInt(getTimeString(endTime), 10), + ]), + ); + } + }; + + useEffect(() => { + window.addEventListener('popstate', handleBackNavigation); + + return (): void => { + window.removeEventListener('popstate', handleBackNavigation); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const graphRef = useRef(null); const isVisible = useIntersectionObserver(graphRef, undefined, true); @@ -70,11 +116,6 @@ function GridCardGraph({ const isEmptyWidget = widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget); - const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector< - AppState, - GlobalReducer - >((state) => state.globalTime); - const queryResponse = useGetQueryRange( { selectedTime: widget?.timePreferance, @@ -122,6 +163,17 @@ function GridCardGraph({ ? headerMenuList.filter((menu) => menu !== MenuItemKeys.CreateAlerts) : headerMenuList; + const [graphVisibility, setGraphVisibility] = useState( + Array(queryResponse.data?.payload?.data.result.length || 0).fill(true), + ); + + useEffect(() => { + setGraphVisibility([ + true, + ...Array(queryResponse.data?.payload?.data.result.length).fill(true), + ]); + }, [queryResponse.data?.payload?.data.result.length]); + const options = useMemo( () => getUPlotChartOptions({ @@ -135,11 +187,17 @@ function GridCardGraph({ thresholds: widget.thresholds, minTimeScale, maxTimeScale, + softMax: widget.softMax === undefined ? null : widget.softMax, + softMin: widget.softMin === undefined ? null : widget.softMin, + graphsVisibilityStates: graphVisibility, + setGraphsVisibilityStates: setGraphVisibility, }), [ widget?.id, widget?.yAxisUnit, widget.thresholds, + widget.softMax, + widget.softMin, queryResponse.data?.payload, containerDimensions, isDarkMode, @@ -147,6 +205,8 @@ function GridCardGraph({ onClickHandler, minTimeScale, maxTimeScale, + graphVisibility, + setGraphVisibility, ], ); @@ -167,6 +227,7 @@ function GridCardGraph({ threshold={threshold} headerMenuList={menuList} onClickHandler={onClickHandler} + graphVisibility={graphVisibility} /> )} diff --git a/frontend/src/container/GridCardLayout/GridCard/types.ts b/frontend/src/container/GridCardLayout/GridCard/types.ts index 3674817291..2298b2b070 100644 --- a/frontend/src/container/GridCardLayout/GridCard/types.ts +++ b/frontend/src/container/GridCardLayout/GridCard/types.ts @@ -28,6 +28,7 @@ export interface WidgetGraphComponentProps extends UplotProps { threshold?: ReactNode; headerMenuList: MenuItemKeys[]; isWarning: boolean; + graphVisibility?: boolean[]; } export interface GridCardGraphProps { diff --git a/frontend/src/container/GridCardLayout/GridCardLayout.tsx b/frontend/src/container/GridCardLayout/GridCardLayout.tsx index 3ee11b4008..ed93df3a07 100644 --- a/frontend/src/container/GridCardLayout/GridCardLayout.tsx +++ b/frontend/src/container/GridCardLayout/GridCardLayout.tsx @@ -1,6 +1,6 @@ import './GridCardLayout.styles.scss'; -import { PlusOutlined, SaveFilled } from '@ant-design/icons'; +import { PlusOutlined } from '@ant-design/icons'; import { SOMETHING_WENT_WRONG } from 'constants/api'; import { PANEL_TYPES } from 'constants/queryBuilder'; import { themeColors } from 'constants/theme'; @@ -8,9 +8,12 @@ import { useUpdateDashboard } from 'hooks/dashboard/useUpdateDashboard'; import useComponentPermission from 'hooks/useComponentPermission'; import { useIsDarkMode } from 'hooks/useDarkMode'; import { useNotifications } from 'hooks/useNotifications'; +import isEqual from 'lodash-es/isEqual'; import { FullscreenIcon } from 'lucide-react'; import { useDashboard } from 'providers/Dashboard/Dashboard'; +import { useEffect, useState } from 'react'; import { FullScreen, useFullScreenHandle } from 'react-full-screen'; +import { Layout } from 'react-grid-layout'; import { useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { AppState } from 'store/reducers'; @@ -29,6 +32,7 @@ import { ReactGridLayout, } from './styles'; import { GraphLayoutProps } from './types'; +import { removeUndefinedValuesFromLayout } from './utils'; function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { const { @@ -51,6 +55,8 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { const isDarkMode = useIsDarkMode(); + const [dashboardLayout, setDashboardLayout] = useState(layouts); + const updateDashboardMutation = useUpdateDashboard(); const { notifications } = useNotifications(); @@ -78,7 +84,7 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { ...selectedDashboard, data: { ...selectedDashboard.data, - layout: layouts.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET), + layout: dashboardLayout.filter((e) => e.i !== PANEL_TYPES.EMPTY_WIDGET), }, uuid: selectedDashboard.uuid, }; @@ -90,9 +96,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { setLayouts(updatedDashboard.payload.data.layout); setSelectedDashboard(updatedDashboard.payload); } - notifications.success({ - message: t('dashboard:layout_saved_successfully'), - }); featureResponse.refetch(); }, @@ -108,6 +111,32 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { ? [...ViewMenuAction, ...EditMenuAction] : [...ViewMenuAction]; + const handleLayoutChange = (layout: Layout[]): void => { + const filterLayout = removeUndefinedValuesFromLayout(layout); + const filterDashboardLayout = removeUndefinedValuesFromLayout( + dashboardLayout, + ); + if (!isEqual(filterLayout, filterDashboardLayout)) { + setDashboardLayout(layout); + } + }; + + useEffect(() => { + if ( + dashboardLayout && + Array.isArray(dashboardLayout) && + dashboardLayout.length > 0 && + !isEqual(layouts, dashboardLayout) && + !isDashboardLocked && + saveLayoutPermission && + !updateDashboardMutation.isLoading + ) { + onSaveHandler(); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [dashboardLayout]); + return ( <> @@ -120,17 +149,6 @@ function GraphLayout({ onAddPanelHandler }: GraphLayoutProps): JSX.Element { {t('dashboard:full_view')} - {!isDashboardLocked && saveLayoutPermission && ( - - )} - {!isDashboardLocked && addPanelPermission && ( - - + ); } diff --git a/frontend/src/container/MySettings/UserInfo/UserInfo.styles.scss b/frontend/src/container/MySettings/UserInfo/UserInfo.styles.scss new file mode 100644 index 0000000000..d1cfae649c --- /dev/null +++ b/frontend/src/container/MySettings/UserInfo/UserInfo.styles.scss @@ -0,0 +1,7 @@ +.userInfo-label { + min-width: 150px; +} + +.userInfo-value { + min-width: 20rem; +} diff --git a/frontend/src/container/MySettings/UpdateName/index.tsx b/frontend/src/container/MySettings/UserInfo/index.tsx similarity index 58% rename from frontend/src/container/MySettings/UpdateName/index.tsx rename to frontend/src/container/MySettings/UserInfo/index.tsx index 6da15a237a..23187a9bf4 100644 --- a/frontend/src/container/MySettings/UpdateName/index.tsx +++ b/frontend/src/container/MySettings/UserInfo/index.tsx @@ -1,6 +1,10 @@ -import { Button, Space, Typography } from 'antd'; +import '../MySettings.styles.scss'; +import './UserInfo.styles.scss'; + +import { Button, Card, Flex, Input, Space, Typography } from 'antd'; import editUser from 'api/user/editUser'; import { useNotifications } from 'hooks/useNotifications'; +import { PencilIcon, UserSquare } from 'lucide-react'; import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; @@ -12,7 +16,7 @@ import AppReducer from 'types/reducer/app'; import { NameInput } from '../styles'; -function UpdateName(): JSX.Element { +function UserInfo(): JSX.Element { const { user, role, org, userFlags } = useSelector( (state) => state.app, ); @@ -72,28 +76,51 @@ function UpdateName(): JSX.Element { }; return ( -
+ - Name - { - setChangedName(event.target.value); - }} - value={changedName} - disabled={loading} - /> - + + {' '} + + User Details + + + + + + Name + { + setChangedName(event.target.value); + }} + value={changedName} + disabled={loading} + /> + + + + + + + Email + + + + + Role + + -
+ ); } -export default UpdateName; +export default UserInfo; diff --git a/frontend/src/container/MySettings/index.tsx b/frontend/src/container/MySettings/index.tsx index f0938e6440..e3945c4d12 100644 --- a/frontend/src/container/MySettings/index.tsx +++ b/frontend/src/container/MySettings/index.tsx @@ -1,16 +1,28 @@ -import { Space, Typography } from 'antd'; -import { useTranslation } from 'react-i18next'; +import './MySettings.styles.scss'; + +import { Button, Space } from 'antd'; +import { Logout } from 'api/utils'; +import { LogOut } from 'lucide-react'; import Password from './Password'; -import UpdateName from './UpdateName'; +import UserInfo from './UserInfo'; function MySettings(): JSX.Element { - const { t } = useTranslation(['routes']); return ( - - {t('my_settings')} - + + + + + ); } diff --git a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx index 1a111cf4fe..d355edfd1a 100644 --- a/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx +++ b/frontend/src/container/NewDashboard/ComponentsSlider/index.tsx @@ -63,6 +63,8 @@ function DashboardGraphSlider(): JSX.Element { panelTypes: name, query: initialQueriesMap.metrics, timePreferance: 'GLOBAL_TIME', + softMax: null, + softMin: null, }, ], }, diff --git a/frontend/src/container/NewExplorerCTA/config.ts b/frontend/src/container/NewExplorerCTA/config.ts index b2feeff572..886f044e57 100644 --- a/frontend/src/container/NewExplorerCTA/config.ts +++ b/frontend/src/container/NewExplorerCTA/config.ts @@ -7,5 +7,5 @@ export const RIBBON_STYLES = { export const buttonText = { [ROUTES.LOGS_EXPLORER]: 'Switch to Old Logs Explorer', [ROUTES.TRACE]: 'Try new Traces Explorer', - [ROUTES.LOGS]: 'Switch to New Logs Explorer', + [ROUTES.OLD_LOGS_EXPLORER]: 'Switch to New Logs Explorer', }; diff --git a/frontend/src/container/NewExplorerCTA/index.tsx b/frontend/src/container/NewExplorerCTA/index.tsx index 6b1a5b577c..5b6d4532e2 100644 --- a/frontend/src/container/NewExplorerCTA/index.tsx +++ b/frontend/src/container/NewExplorerCTA/index.tsx @@ -14,16 +14,16 @@ function NewExplorerCTA(): JSX.Element | null { () => location.pathname === ROUTES.LOGS_EXPLORER || location.pathname === ROUTES.TRACE || - location.pathname === ROUTES.LOGS, + location.pathname === ROUTES.OLD_LOGS_EXPLORER, [location.pathname], ); const onClickHandler = useCallback((): void => { if (location.pathname === ROUTES.LOGS_EXPLORER) { - history.push(ROUTES.LOGS); + history.push(ROUTES.OLD_LOGS_EXPLORER); } else if (location.pathname === ROUTES.TRACE) { history.push(ROUTES.TRACES_EXPLORER); - } else if (location.pathname === ROUTES.LOGS) { + } else if (location.pathname === ROUTES.OLD_LOGS_EXPLORER) { history.push(ROUTES.LOGS_EXPLORER); } }, [location.pathname]); @@ -36,6 +36,7 @@ function NewExplorerCTA(): JSX.Element | null { danger data-testid="newExplorerCTA" type="primary" + size="small" > {buttonText[location.pathname]} diff --git a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx index e878c61b4d..b56b53694a 100644 --- a/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx +++ b/frontend/src/container/NewWidget/LeftContainer/QuerySection/QueryBuilder/QueryHeader.tsx @@ -30,11 +30,10 @@ function QueryHeader({ const [collapse, setCollapse] = useState(false); return ( - +