mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-10 02:29:03 +08:00
Inital Commit
This commit is contained in:
parent
940788afb5
commit
1bc2fc038b
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
frontend/node_modules
|
||||
frontend/.pnp
|
||||
*.pnp.js
|
||||
|
||||
# testing
|
||||
frontend/coverage
|
||||
|
||||
# production
|
||||
frontend/build
|
||||
frontend/.vscode
|
||||
frontend/.yarnclean
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
frontend/npm-debug.log*
|
||||
frontend/yarn-debug.log*
|
||||
frontend/yarn-error.log*
|
||||
|
||||
**/.vscode
|
||||
*.tgz
|
0
CHANGELOG.md
Normal file
0
CHANGELOG.md
Normal file
0
CONTRIBUTING.md
Normal file
0
CONTRIBUTING.md
Normal file
0
deploy/docker/README.md
Normal file
0
deploy/docker/README.md
Normal file
61
deploy/docker/docker-compose.yaml
Normal file
61
deploy/docker/docker-compose.yaml
Normal file
@ -0,0 +1,61 @@
|
||||
version: "3"
|
||||
services:
|
||||
# Zookeeper
|
||||
zookeeper:
|
||||
image: 'bitnami/zookeeper:3'
|
||||
ports:
|
||||
- '2181:2181'
|
||||
environment:
|
||||
- ALLOW_ANONYMOUS_LOGIN=yes
|
||||
|
||||
# Kafka
|
||||
kafka:
|
||||
image: 'bitnami/kafka:2'
|
||||
ports:
|
||||
- '9092:9092'
|
||||
environment:
|
||||
- KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181
|
||||
- ALLOW_PLAINTEXT_LISTENER=yes
|
||||
- KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT
|
||||
- KAFKA_CFG_LISTENERS=PLAINTEXT://:29092,PLAINTEXT_HOST://:9092
|
||||
- KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:29092,PLAINTEXT_HOST://localhost:9092
|
||||
depends_on:
|
||||
- zookeeper
|
||||
|
||||
# Divolte container
|
||||
divolte:
|
||||
image: divolte/divolte-collector
|
||||
container_name: divolte
|
||||
environment:
|
||||
- DIVOLTE_KAFKA_BROKER_LIST=kafka:29092
|
||||
volumes:
|
||||
- ./conf/divolte/:/opt/divolte/divolte-collector/conf/
|
||||
ports:
|
||||
- 8290:8290
|
||||
depends_on:
|
||||
- kafka
|
||||
|
||||
# Druid container
|
||||
druid:
|
||||
image: fokkodriesprong/docker-druid
|
||||
container_name: druid
|
||||
ports:
|
||||
- 8081:8081
|
||||
- 8082:8082
|
||||
- 8888:8888
|
||||
depends_on:
|
||||
- kafka
|
||||
|
||||
# Superset container
|
||||
superset:
|
||||
image: amancevice/superset:0.18.5
|
||||
container_name: superset
|
||||
ports:
|
||||
- 8088:8088
|
||||
|
||||
# Superset container
|
||||
app:
|
||||
build: app/
|
||||
container_name: app
|
||||
ports:
|
||||
- 8090:8090
|
0
deploy/kubernetes/README.md
Normal file
0
deploy/kubernetes/README.md
Normal file
0
deploy/kubernetes/config.yaml
Normal file
0
deploy/kubernetes/config.yaml
Normal file
7
deploy/kubernetes/jobs/retention/retention-config.yaml
Normal file
7
deploy/kubernetes/jobs/retention/retention-config.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: retention-config
|
||||
data:
|
||||
retention-spec.json: |
|
||||
[{"period":"P3D","includeFuture":true,"tieredReplicants":{"_default_tier":1},"type":"loadByPeriod"},{"type":"dropForever"}]
|
29
deploy/kubernetes/jobs/retention/retention.yaml
Normal file
29
deploy/kubernetes/jobs/retention/retention.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: set-retention
|
||||
annotations:
|
||||
"helm.sh/hook": post-install,post-upgrade
|
||||
spec:
|
||||
# ttlSecondsAfterFinished: 100
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: set-retention
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
volumeMounts:
|
||||
- name: retention-config-volume
|
||||
mountPath: /app/retention-spec.json
|
||||
subPath: retention-spec.json
|
||||
args:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/retention-spec.json http://signoz-druid-router:8888/druid/coordinator/v1/rules/flattened_spans"
|
||||
|
||||
volumes:
|
||||
- name: retention-config-volume
|
||||
configMap:
|
||||
name: retention-config
|
||||
|
||||
restartPolicy: Never
|
||||
backoffLimit: 4
|
59
deploy/kubernetes/jobs/supervisor/supervisor-config.yaml
Normal file
59
deploy/kubernetes/jobs/supervisor/supervisor-config.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: supervisor-config
|
||||
data:
|
||||
supervisor-spec.json: |
|
||||
{
|
||||
"type": "kafka",
|
||||
"dataSchema": {
|
||||
"dataSource": "flattened_spans",
|
||||
"parser": {
|
||||
"type": "string",
|
||||
"parseSpec": {
|
||||
"format": "json",
|
||||
"timestampSpec": {
|
||||
"column": "StartTimeUnixNano",
|
||||
"format": "nano"
|
||||
},
|
||||
"dimensionsSpec": {
|
||||
"dimensions": [
|
||||
"TraceId",
|
||||
"SpanId",
|
||||
"ParentSpanId",
|
||||
"Name",
|
||||
"ServiceName",
|
||||
"References",
|
||||
"Tags",
|
||||
"TagsKeys",
|
||||
"TagsValues",
|
||||
{ "name": "DurationNano", "type": "Long" },
|
||||
{ "name": "Kind", "type": "int" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"metricsSpec" : [
|
||||
{ "type": "quantilesDoublesSketch", "name": "QuantileDuration", "fieldName": "DurationNano" }
|
||||
],
|
||||
"granularitySpec": {
|
||||
"type": "uniform",
|
||||
"segmentGranularity": "DAY",
|
||||
"queryGranularity": "NONE",
|
||||
"rollup": false
|
||||
}
|
||||
},
|
||||
"tuningConfig": {
|
||||
"type": "kafka",
|
||||
"reportParseExceptions": true
|
||||
},
|
||||
"ioConfig": {
|
||||
"topic": "flattened_spans",
|
||||
"replicas": 1,
|
||||
"taskDuration": "PT20M",
|
||||
"completionTimeout": "PT30M",
|
||||
"consumerProperties": {
|
||||
"bootstrap.servers": "signoz-kafka:9092"
|
||||
}
|
||||
}
|
||||
}
|
27
deploy/kubernetes/jobs/supervisor/supervisor.yaml
Normal file
27
deploy/kubernetes/jobs/supervisor/supervisor.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: create-supervisor
|
||||
annotations:
|
||||
"helm.sh/hook": post-install,post-upgrade
|
||||
spec:
|
||||
# ttlSecondsAfterFinished: 100
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: create-supervisor
|
||||
image: theithollow/hollowapp-blog:curl
|
||||
volumeMounts:
|
||||
- name: supervisor-config-volume
|
||||
mountPath: /app/supervisor-spec.json
|
||||
subPath: supervisor-spec.json
|
||||
args:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- "curl -X POST -H 'Content-Type: application/json' -d @/app/supervisor-spec.json http://signoz-druid-router:8888/druid/indexer/v1/supervisor"
|
||||
volumes:
|
||||
- name: supervisor-config-volume
|
||||
configMap:
|
||||
name: supervisor-config
|
||||
restartPolicy: Never
|
||||
backoffLimit: 4
|
43
deploy/kubernetes/otel-collector/config.yaml
Normal file
43
deploy/kubernetes/otel-collector/config.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: otel-collector-conf
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector-conf
|
||||
data:
|
||||
otel-collector-config: |
|
||||
receivers:
|
||||
jaeger:
|
||||
protocols:
|
||||
grpc:
|
||||
thrift_http:
|
||||
processors:
|
||||
batch:
|
||||
memory_limiter:
|
||||
# Same as --mem-ballast-size-mib CLI argument
|
||||
ballast_size_mib: 683
|
||||
# 80% of maximum memory up to 2G
|
||||
limit_mib: 1500
|
||||
# 25% of limit up to 2G
|
||||
spike_limit_mib: 512
|
||||
check_interval: 5s
|
||||
queued_retry:
|
||||
num_workers: 4
|
||||
queue_size: 100
|
||||
retry_on_failure: true
|
||||
extensions:
|
||||
health_check: {}
|
||||
zpages: {}
|
||||
exporters:
|
||||
kafka:
|
||||
brokers:
|
||||
- signoz-kafka:9092
|
||||
protocol_version: 2.0.0
|
||||
service:
|
||||
extensions: [health_check, zpages]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [jaeger]
|
||||
processors: [memory_limiter, batch, queued_retry]
|
||||
exporters: [kafka]
|
70
deploy/kubernetes/otel-collector/deployment.yaml
Normal file
70
deploy/kubernetes/otel-collector/deployment.yaml
Normal file
@ -0,0 +1,70 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: otel-collector
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
minReadySeconds: 5
|
||||
progressDeadlineSeconds: 120
|
||||
replicas: 1 #TODO - adjust this to your own requirements
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
containers:
|
||||
- command:
|
||||
- "/otelcol"
|
||||
- "--config=/conf/otel-collector-config.yaml"
|
||||
# Memory Ballast size should be max 1/3 to 1/2 of memory.
|
||||
- "--mem-ballast-size-mib=683"
|
||||
image: otel/opentelemetry-collector-dev:latest
|
||||
name: otel-collector
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 400Mi
|
||||
ports:
|
||||
- containerPort: 55679 # Default endpoint for ZPages.
|
||||
- containerPort: 55680 # Default endpoint for OpenTelemetry receiver.
|
||||
- containerPort: 14250 # Default endpoint for Jaeger HTTP receiver.
|
||||
- containerPort: 14268 # Default endpoint for Jaeger HTTP receiver.
|
||||
- containerPort: 9411 # Default endpoint for Zipkin receiver.
|
||||
- containerPort: 8888 # Default endpoint for querying metrics.
|
||||
volumeMounts:
|
||||
- name: otel-collector-config-vol
|
||||
mountPath: /conf
|
||||
# - name: otel-collector-secrets
|
||||
# mountPath: /secrets
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133 # Health Check extension default port.
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 13133 # Health Check extension default port.
|
||||
volumes:
|
||||
- configMap:
|
||||
name: otel-collector-conf
|
||||
items:
|
||||
- key: otel-collector-config
|
||||
path: otel-collector-config.yaml
|
||||
name: otel-collector-config-vol
|
||||
# - secret:
|
||||
# name: otel-collector-secrets
|
||||
# items:
|
||||
# - key: cert.pem
|
||||
# path: cert.pem
|
||||
# - key: key.pem
|
||||
# path: key.pem
|
23
deploy/kubernetes/otel-collector/service.yaml
Normal file
23
deploy/kubernetes/otel-collector/service.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: otel-collector
|
||||
labels:
|
||||
app: opentelemetry
|
||||
component: otel-collector
|
||||
spec:
|
||||
ports:
|
||||
- name: otlp # Default endpoint for OpenTelemetry receiver.
|
||||
port: 55680
|
||||
protocol: TCP
|
||||
targetPort: 55680
|
||||
- name: jaeger-grpc # Default endpoing for Jaeger gRPC receiver
|
||||
port: 14250
|
||||
- name: jaeger-thrift-http # Default endpoint for Jaeger HTTP receiver.
|
||||
port: 14268
|
||||
- name: zipkin # Default endpoint for Zipkin receiver.
|
||||
port: 9411
|
||||
- name: metrics # Default endpoint for querying metrics.
|
||||
port: 8888
|
||||
selector:
|
||||
component: otel-collector
|
21
deploy/kubernetes/platform/Chart.lock
Normal file
21
deploy/kubernetes/platform/Chart.lock
Normal file
@ -0,0 +1,21 @@
|
||||
dependencies:
|
||||
- name: zookeeper
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 6.0.0
|
||||
- name: kafka
|
||||
repository: https://charts.bitnami.com/bitnami
|
||||
version: 12.0.0
|
||||
- name: druid
|
||||
repository: https://charts.helm.sh/incubator
|
||||
version: 0.2.18
|
||||
- name: flattener-processor
|
||||
repository: file://./signoz-charts/flattener-processor
|
||||
version: 0.1.1
|
||||
- name: query-service
|
||||
repository: file://./signoz-charts/query-service
|
||||
version: 0.1.1
|
||||
- name: frontend
|
||||
repository: file://./signoz-charts/frontend
|
||||
version: 0.1.4
|
||||
digest: sha256:f29f2d0a5e58617075b0e1fd513bf7699138e20dfb94f1cddcfc8ff6a9bcb371
|
||||
generated: "2021-01-02T01:14:15.321064+05:30"
|
43
deploy/kubernetes/platform/Chart.yaml
Normal file
43
deploy/kubernetes/platform/Chart.yaml
Normal file
@ -0,0 +1,43 @@
|
||||
apiVersion: v2
|
||||
name: signoz-platform
|
||||
description: SigNoz Observability Platform Helm Chart
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: 0.1.6
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
appVersion: 0.1.1
|
||||
|
||||
dependencies:
|
||||
- name: zookeeper
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
version: 6.0.0
|
||||
- name: kafka
|
||||
repository: "https://charts.bitnami.com/bitnami"
|
||||
version: 12.0.0
|
||||
- name: druid
|
||||
repository: "https://charts.helm.sh/incubator"
|
||||
version: 0.2.18
|
||||
- name: flattener-processor
|
||||
repository: "file://./signoz-charts/flattener-processor"
|
||||
version: 0.1.1
|
||||
- name: query-service
|
||||
repository: "file://./signoz-charts/query-service"
|
||||
version: 0.1.1
|
||||
- name: frontend
|
||||
repository: "file://./signoz-charts/frontend"
|
||||
version: 0.1.4
|
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
@ -0,0 +1,21 @@
|
||||
apiVersion: v2
|
||||
name: flattener-processor
|
||||
description: A Helm chart for Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.1
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: v0.1.0
|
@ -0,0 +1,21 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "flattener-processor.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "flattener-processor.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "flattener-processor.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "flattener-processor.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
|
||||
{{- end }}
|
@ -0,0 +1,63 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "flattener-processor.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "flattener-processor.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "flattener-processor.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "flattener-processor.labels" -}}
|
||||
helm.sh/chart: {{ include "flattener-processor.chart" . }}
|
||||
{{ include "flattener-processor.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "flattener-processor.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "flattener-processor.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "flattener-processor.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{ default (include "flattener-processor.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
@ -0,0 +1,65 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "flattener-processor.fullname" . }}
|
||||
labels:
|
||||
{{- include "flattener-processor.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "flattener-processor.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "flattener-processor.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "flattener-processor.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
command:
|
||||
- "/root/flattener"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: KAFKA_BROKER
|
||||
value: {{ .Values.configVars.KAFKA_BROKER }}
|
||||
- name: KAFKA_INPUT_TOPIC
|
||||
value: {{ .Values.configVars.KAFKA_INPUT_TOPIC }}
|
||||
- name: KAFKA_OUTPUT_TOPIC
|
||||
value: {{ .Values.configVars.KAFKA_OUTPUT_TOPIC }}
|
||||
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,41 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "flattener-processor.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "flattener-processor.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ . }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "flattener-processor.fullname" . }}
|
||||
labels:
|
||||
{{- include "flattener-processor.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "flattener-processor.selectorLabels" . | nindent 4 }}
|
@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "flattener-processor.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "flattener-processor.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "flattener-processor.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "flattener-processor.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "flattener-processor.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
@ -0,0 +1,74 @@
|
||||
# Default values for flattener-processor.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: signoz/flattener-processor
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
|
||||
configVars:
|
||||
KAFKA_BROKER: signoz-kafka:9092
|
||||
KAFKA_INPUT_TOPIC: otlp_spans
|
||||
KAFKA_OUTPUT_TOPIC: flattened_spans
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: false
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
21
deploy/kubernetes/platform/signoz-charts/frontend/Chart.yaml
Normal file
21
deploy/kubernetes/platform/signoz-charts/frontend/Chart.yaml
Normal file
@ -0,0 +1,21 @@
|
||||
apiVersion: v2
|
||||
name: frontend
|
||||
description: A Helm chart for SigNoz Frontend Service
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.4
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: v0.1.3
|
@ -0,0 +1,21 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "frontend.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "frontend.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "frontend.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "frontend.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
|
||||
{{- end }}
|
@ -0,0 +1,63 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "frontend.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "frontend.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "frontend.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "frontend.labels" -}}
|
||||
helm.sh/chart: {{ include "frontend.chart" . }}
|
||||
{{ include "frontend.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "frontend.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "frontend.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "frontend.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{ default (include "frontend.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
@ -0,0 +1,27 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Values.config.name }}
|
||||
labels:
|
||||
release: {{ .Release.Name }}
|
||||
data:
|
||||
default.conf: |-
|
||||
server {
|
||||
listen {{ .Values.service.port }};
|
||||
server_name _;
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location /api {
|
||||
proxy_pass http://{{ .Values.config.queryServiceUrl }}/api;
|
||||
}
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "frontend.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "frontend.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
volumes:
|
||||
- name: nginx-config
|
||||
configMap:
|
||||
name: {{ .Values.config.name }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.port }}
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: REACT_APP_QUERY_SERVICE_URL
|
||||
value: {{ .Values.configVars.REACT_APP_QUERY_SERVICE_URL }}
|
||||
volumeMounts:
|
||||
- name: nginx-config
|
||||
mountPath: /etc/nginx/conf.d
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,41 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "frontend.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ . }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "frontend.fullname" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "frontend.selectorLabels" . | nindent 4 }}
|
@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "frontend.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "frontend.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "frontend.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "frontend.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
@ -0,0 +1,76 @@
|
||||
# Default values for frontend.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: signoz/frontend
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
configVars:
|
||||
REACT_APP_QUERY_SERVICE_URL: http://localhost:3000/api/v1/
|
||||
|
||||
config:
|
||||
name: signoz-nginx-config
|
||||
queryServiceUrl: signoz-query-service:8080
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: false
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 3000
|
||||
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
@ -0,0 +1,21 @@
|
||||
apiVersion: v2
|
||||
name: query-service
|
||||
description: A Helm chart for running SigNoz Query Service in Kubernetes
|
||||
|
||||
# A chart can be either an 'application' or a 'library' chart.
|
||||
#
|
||||
# Application charts are a collection of templates that can be packaged into versioned archives
|
||||
# to be deployed.
|
||||
#
|
||||
# Library charts provide useful utilities or functions for the chart developer. They're included as
|
||||
# a dependency of application charts to inject those utilities and functions into the rendering
|
||||
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
|
||||
type: application
|
||||
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
version: 0.1.1
|
||||
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application.
|
||||
appVersion: v0.1.0
|
@ -0,0 +1,21 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "query-service.fullname" . }})
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "query-service.fullname" . }}'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "query-service.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.service.type }}
|
||||
export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "query-service.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
|
||||
{{- end }}
|
@ -0,0 +1,63 @@
|
||||
{{/* vim: set filetype=mustache: */}}
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "query-service.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "query-service.fullname" -}}
|
||||
{{- if .Values.fullnameOverride -}}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride -}}
|
||||
{{- if contains $name .Release.Name -}}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "query-service.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "query-service.labels" -}}
|
||||
helm.sh/chart: {{ include "query-service.chart" . }}
|
||||
{{ include "query-service.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "query-service.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "query-service.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "query-service.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
{{ default (include "query-service.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else -}}
|
||||
{{ default "default" .Values.serviceAccount.name }}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "query-service.fullname" . }}
|
||||
labels:
|
||||
{{- include "query-service.labels" . | nindent 4 }}
|
||||
spec:
|
||||
replicas: {{ .Values.replicaCount }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "query-service.selectorLabels" . | nindent 6 }}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "query-service.selectorLabels" . | nindent 8 }}
|
||||
spec:
|
||||
{{- with .Values.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "query-service.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: {{ .Chart.Name }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: DruidClientUrl
|
||||
value: {{ .Values.configVars.DruidClientUrl }}
|
||||
- name: DruidDatasource
|
||||
value: {{ .Values.configVars.DruidDatasource }}
|
||||
|
||||
|
||||
# livenessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
# readinessProbe:
|
||||
# httpGet:
|
||||
# path: /
|
||||
# port: http
|
||||
resources:
|
||||
{{- toYaml .Values.resources | nindent 12 }}
|
||||
{{- with .Values.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
@ -0,0 +1,41 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
{{- $fullName := include "query-service.fullname" . -}}
|
||||
{{- $svcPort := .Values.service.port -}}
|
||||
{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
{{- else -}}
|
||||
apiVersion: extensions/v1beta1
|
||||
{{- end }}
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ $fullName }}
|
||||
labels:
|
||||
{{- include "query-service.labels" . | nindent 4 }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ . }}
|
||||
backend:
|
||||
serviceName: {{ $fullName }}
|
||||
servicePort: {{ $svcPort }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "query-service.fullname" . }}
|
||||
labels:
|
||||
{{- include "query-service.labels" . | nindent 4 }}
|
||||
spec:
|
||||
type: {{ .Values.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "query-service.selectorLabels" . | nindent 4 }}
|
@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "query-service.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "query-service.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end -}}
|
@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: "{{ include "query-service.fullname" . }}-test-connection"
|
||||
labels:
|
||||
{{- include "query-service.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
"helm.sh/hook": test-success
|
||||
spec:
|
||||
containers:
|
||||
- name: wget
|
||||
image: busybox
|
||||
command: ['wget']
|
||||
args: ['{{ include "query-service.fullname" . }}:{{ .Values.service.port }}']
|
||||
restartPolicy: Never
|
@ -0,0 +1,74 @@
|
||||
# Default values for query-service.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: signoz/query-service
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
imagePullSecrets: []
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
|
||||
configVars:
|
||||
DruidClientUrl: http://signoz-druid-router:8888
|
||||
DruidDatasource: flattened_spans
|
||||
|
||||
|
||||
serviceAccount:
|
||||
# Specifies whether a service account should be created
|
||||
create: false
|
||||
# Annotations to add to the service account
|
||||
annotations: {}
|
||||
# The name of the service account to use.
|
||||
# If not set and create is true, a name is generated using the fullname template
|
||||
name:
|
||||
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 2000
|
||||
|
||||
securityContext: {}
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsNonRoot: true
|
||||
# runAsUser: 1000
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
|
||||
ingress:
|
||||
enabled: false
|
||||
annotations: {}
|
||||
# kubernetes.io/ingress.class: nginx
|
||||
# kubernetes.io/tls-acme: "true"
|
||||
hosts:
|
||||
- host: chart-example.local
|
||||
paths: []
|
||||
tls: []
|
||||
# - secretName: chart-example-tls
|
||||
# hosts:
|
||||
# - chart-example.local
|
||||
|
||||
resources: {}
|
||||
# We usually recommend not to specify default resources and to leave this as a conscious
|
||||
# choice for the user. This also increases chances charts run on environments with little
|
||||
# resources, such as Minikube. If you do want to specify resources, uncomment the following
|
||||
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
# requests:
|
||||
# cpu: 100m
|
||||
# memory: 128Mi
|
||||
|
||||
nodeSelector: {}
|
||||
|
||||
tolerations: []
|
||||
|
||||
affinity: {}
|
45
deploy/kubernetes/platform/values.yaml
Normal file
45
deploy/kubernetes/platform/values.yaml
Normal file
@ -0,0 +1,45 @@
|
||||
kafka:
|
||||
zookeeper:
|
||||
enabled: false
|
||||
externalZookeeper:
|
||||
servers: ["signoz-zookeeper:2181"]
|
||||
zookeeperConnectionTimeoutMs: 6000
|
||||
|
||||
druid:
|
||||
configVars:
|
||||
druid_extensions_loadList: '["druid-histogram", "druid-datasketches", "druid-lookups-cached-global", "postgresql-metadata-storage", "druid-kafka-indexing-service"]'
|
||||
druid_storage_type: local
|
||||
# druid_storage_type: s3
|
||||
# druid_storage_bucket: signoz-druid
|
||||
# druid_storage_baseKey: baseKey
|
||||
# aws_accessKeyId: AKIAI6WYDSRKPE57PYPA
|
||||
# aws_secretKey: 3YjnS/TXcUkkjHcrzOik7aKdSf6+P4S0FlXmbRHg
|
||||
# druid_s3_accessKey: AKIAI6WYDSRKPE57PYPA
|
||||
# druid_s3_secretKey: 3YjnS/TXcUkkjHcrzOik7aKdSf6+P4S0FlXmbRHg
|
||||
# AWS_ACCESS_KEY_ID: AKIAI6WYDSRKPE57PYPA
|
||||
# AWS_SECRET_ACCESS_KEY: 3YjnS/TXcUkkjHcrzOik7aKdSf6+P4S0FlXmbRHg
|
||||
# AWS_REGION: ap-south-1
|
||||
|
||||
historical:
|
||||
persistence:
|
||||
size: "12Gi"
|
||||
|
||||
zkHosts: "signoz-zookeeper:2181"
|
||||
|
||||
zookeeper:
|
||||
enabled: false
|
||||
|
||||
flattener-processor:
|
||||
configVars:
|
||||
KAFKA_BROKER: signoz-kafka:9092
|
||||
KAFKA_INPUT_TOPIC: otlp_spans
|
||||
KAFKA_OUTPUT_TOPIC: flattened_spans
|
||||
|
||||
query-service:
|
||||
configVars:
|
||||
DruidClientUrl: http://signoz-druid-router:8888
|
||||
DruidDatasource: flattened_spans
|
||||
|
||||
frontend:
|
||||
configVars:
|
||||
REACT_APP_QUERY_SERVICE_URL: http://localhost:3000/api/v1/
|
148
deploy/kubernetes/signoz.sh
Normal file
148
deploy/kubernetes/signoz.sh
Normal file
@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eEu -o functrace
|
||||
script=$0
|
||||
|
||||
function clean() {
|
||||
EXIT_CODE=0
|
||||
kubectl get ns ${HT_KUBE_NAMESPACE} ${KUBE_FLAGS} >/dev/null 2>&1 || EXIT_CODE=$?
|
||||
if [ ${EXIT_CODE} != 0 ]; then
|
||||
echo "[ERROR] Couldn't find namespace to uninstall. namespace: ${HT_KUBE_NAMESPACE}, context: ${HT_KUBE_CONTEXT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
EXIT_CODE=0
|
||||
helm get all hypertrace-platform-services ${HELM_FLAGS} >/dev/null 2>&1 || EXIT_CODE=$?
|
||||
if [ ${EXIT_CODE} == 0 ]; then
|
||||
echo "[INFO] found existing platform services deployment. running helm uninstall. release: 'hypertrace-platform-services'"
|
||||
helm uninstall hypertrace-platform-services ${HELM_FLAGS}
|
||||
else
|
||||
echo "[INFO] Couldn't find helm release 'hypertrace-platform-services'"
|
||||
fi
|
||||
|
||||
EXIT_CODE=0
|
||||
helm get all hypertrace-data-services ${HELM_FLAGS} >/dev/null 2>&1 || EXIT_CODE=$?
|
||||
if [ ${EXIT_CODE} == 0 ]; then
|
||||
echo "[INFO] found existing data services deployment. running helm uninstall. release: 'hypertrace-data-services'"
|
||||
helm uninstall hypertrace-data-services ${HELM_FLAGS}
|
||||
else
|
||||
echo "[INFO] Couldn't find helm release 'hypertrace-data-services'"
|
||||
fi
|
||||
echo "[INFO] We are clean! good to go!"
|
||||
}
|
||||
|
||||
error_report() {
|
||||
local retval=$?
|
||||
echo "${script}: Error at line#: $1, command: $2, error code: ${retval}"
|
||||
exit ${retval}
|
||||
}
|
||||
|
||||
trap 'error_report ${LINENO} ${BASH_COMMAND}' ERR
|
||||
|
||||
function usage() {
|
||||
echo "usage: $script {install|uninstall} [option]"
|
||||
echo " "
|
||||
echo "available options:"
|
||||
echo " "
|
||||
echo "--clean removes previous deployments of Hypertrace and do clean install"
|
||||
|
||||
exit 1
|
||||
}
|
||||
|
||||
HYPERTRACE_HOME="`dirname \"$0\"`"
|
||||
HYPERTRACE_HOME="`( cd \"${HYPERTRACE_HOME}\" && pwd )`" # absolutized and normalized
|
||||
|
||||
HYPERTRACE_CONFIG=${HYPERTRACE_HOME}/config/hypertrace.properties
|
||||
|
||||
if [ ! -f "${HYPERTRACE_CONFIG}" ]; then
|
||||
echo "Configuration file not found: '${HYPERTRACE_CONFIG}'"
|
||||
fi
|
||||
source ${HYPERTRACE_CONFIG}
|
||||
|
||||
LOG_DIR=${HYPERTRACE_HOME}/logs
|
||||
LOG_FILE=hypertrace.log
|
||||
mkdir -p ${LOG_DIR}
|
||||
|
||||
if [ x${HT_KUBE_CONTEXT} == "x" ]; then
|
||||
HT_KUBE_CONTEXT="$(kubectl config current-context)"
|
||||
fi
|
||||
|
||||
HELM_FLAGS="--kube-context=${HT_KUBE_CONTEXT} --namespace=${HT_KUBE_NAMESPACE} --log-dir=${LOG_DIR} --log-file=${LOG_FILE} --log-file-max-size=16 --alsologtostderr=true --logtostderr=false"
|
||||
KUBE_FLAGS="--context=${HT_KUBE_CONTEXT} --namespace=${HT_KUBE_NAMESPACE} --log-dir=${LOG_DIR} --log-file=${LOG_FILE} --log-file-max-size=16 --alsologtostderr=true --logtostderr=false"
|
||||
|
||||
|
||||
if [[ "$HT_ENABLE_DEBUG" == "true" ]]; then
|
||||
HELM_FLAGS="$HELM_FLAGS --debug --v=4"
|
||||
KUBE_FLAGS="$KUBE_FLAGS --v=4"
|
||||
fi
|
||||
|
||||
if [ "$#" -gt 1 ]; then
|
||||
if [[ $2 == "--clean" ]]; then
|
||||
clean
|
||||
else
|
||||
usage
|
||||
fi
|
||||
elif [ "$#" -ne 1 ]; then
|
||||
echo "[ERROR] Illegal number of parameters"
|
||||
echo "-------------------------------------"
|
||||
usage
|
||||
fi
|
||||
|
||||
|
||||
subcommand=$1; shift
|
||||
|
||||
case "$subcommand" in
|
||||
install)
|
||||
EXIT_CODE=0;
|
||||
|
||||
# namespace - create when it doesn't exists. ignore, otherwise.
|
||||
kubectl get ns ${HT_KUBE_NAMESPACE} ${KUBE_FLAGS} >/dev/null 2>&1 || EXIT_CODE=$?
|
||||
if [ ${EXIT_CODE} == 0 ]; then
|
||||
echo "[WARN] Skipping namespace creation. Namespace already exists. namespace: ${HT_KUBE_NAMESPACE}, context: ${HT_KUBE_CONTEXT}"
|
||||
else
|
||||
echo "[INFO] Creating namespace. namespace: ${HT_KUBE_NAMESPACE}, context: ${HT_KUBE_CONTEXT}"
|
||||
kubectl create ns ${HT_KUBE_NAMESPACE} ${KUBE_FLAGS}
|
||||
fi
|
||||
|
||||
|
||||
echo "[INFO] cleaning up any helm temporary working directory"
|
||||
rm -rf ${HYPERTRACE_HOME}/data-services/charts
|
||||
rm -rf ${HYPERTRACE_HOME}/data-services/tmpcharts
|
||||
rm -rf ${HYPERTRACE_HOME}/data-services/Chart.lock
|
||||
rm -rf ${HYPERTRACE_HOME}/platform-services/charts
|
||||
rm -rf ${HYPERTRACE_HOME}/platform-services/tmpcharts
|
||||
rm -rf ${HYPERTRACE_HOME}/platform-services/Chart.lock
|
||||
|
||||
echo "[INFO] installing hypertrace data services. namespace: ${HT_KUBE_NAMESPACE}, context: ${HT_KUBE_CONTEXT}"
|
||||
helm dependency update ${HYPERTRACE_HOME}/data-services ${HELM_FLAGS}
|
||||
helm upgrade hypertrace-data-services ${HYPERTRACE_HOME}/data-services -f ${HYPERTRACE_HOME}/data-services/values.yaml -f ${HYPERTRACE_HOME}/clusters/$HT_PROFILE/values.yaml --install --wait ${HELM_FLAGS} --timeout ${HT_INSTALL_TIMEOUT}m --set htEnv=${HT_ENV}
|
||||
|
||||
echo "[INFO] installing hypertrace platform services. namespace: ${HT_KUBE_NAMESPACE}, context: ${HT_KUBE_CONTEXT}"
|
||||
helm dependency update ${HYPERTRACE_HOME}/platform-services ${HELM_FLAGS}
|
||||
helm upgrade hypertrace-platform-services ${HYPERTRACE_HOME}/platform-services -f ${HYPERTRACE_HOME}/platform-services/values.yaml -f ${HYPERTRACE_HOME}/clusters/$HT_PROFILE/values.yaml --install ${HELM_FLAGS} --timeout ${HT_INSTALL_TIMEOUT}m --set htEnv=${HT_ENV}
|
||||
echo "[INFO] Hypertrace installation completed"
|
||||
;;
|
||||
|
||||
uninstall)
|
||||
echo "[INFO] Uninstalling Hypertrace deletes the hypertrace namespace and deletes any data stored."
|
||||
echo "Choose an option to continue"
|
||||
|
||||
select yn in "Yes" "No"; do
|
||||
case $yn in
|
||||
Yes )
|
||||
clean
|
||||
kubectl delete ns ${HT_KUBE_NAMESPACE} ${KUBE_FLAGS}
|
||||
echo "[INFO] Uninstall successful."
|
||||
break;;
|
||||
|
||||
No )
|
||||
echo "[INFO] Uninstall cancelled."
|
||||
exit;;
|
||||
esac
|
||||
done
|
||||
;;
|
||||
*)
|
||||
echo "[ERROR] Unknown command: ${subcommand}"
|
||||
usage
|
||||
;;
|
||||
esac
|
32
frontend/Dockerfile
Normal file
32
frontend/Dockerfile
Normal file
@ -0,0 +1,32 @@
|
||||
# stage1 as builder
|
||||
FROM node:14-alpine as builder
|
||||
|
||||
# copy the package.json to install dependencies
|
||||
COPY package.json ./
|
||||
|
||||
# Install the dependencies and make the folder
|
||||
RUN npm install && mkdir /react-ui && mv ./node_modules ./react-ui
|
||||
|
||||
WORKDIR /react-ui
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build the project and copy the files
|
||||
RUN npm run build
|
||||
|
||||
|
||||
FROM nginx:1.15-alpine
|
||||
|
||||
#!/bin/sh
|
||||
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
## Remove default nginx index page
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy from the stahg 1
|
||||
COPY --from=builder /react-ui/build /usr/share/nginx/html
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["nginx", "-g", "daemon off;"]
|
70
frontend/README.md
Normal file
70
frontend/README.md
Normal file
@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.\
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
|
||||
|
||||
If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
|
||||
|
||||
You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
20
frontend/conf/default.conf
Normal file
20
frontend/conf/default.conf
Normal file
@ -0,0 +1,20 @@
|
||||
server {
|
||||
listen 3000;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
location = /api {
|
||||
proxy_pass http://signoz-query-service:8080/api;
|
||||
}
|
||||
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
28
frontend/gulpfile.js
Normal file
28
frontend/gulpfile.js
Normal file
@ -0,0 +1,28 @@
|
||||
const gulp = require('gulp')
|
||||
const gulpless = require('gulp-less')
|
||||
const postcss = require('gulp-postcss')
|
||||
const debug = require('gulp-debug')
|
||||
var csso = require('gulp-csso')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const NpmImportPlugin = require('less-plugin-npm-import')
|
||||
|
||||
gulp.task('less', function () {
|
||||
const plugins = [autoprefixer()]
|
||||
|
||||
return gulp
|
||||
.src('src/themes/*-theme.less')
|
||||
.pipe(debug({title: 'Less files:'}))
|
||||
.pipe(
|
||||
gulpless({
|
||||
javascriptEnabled: true,
|
||||
plugins: [new NpmImportPlugin({prefix: '~'})],
|
||||
}),
|
||||
)
|
||||
.pipe(postcss(plugins))
|
||||
.pipe(
|
||||
csso({
|
||||
debug: true,
|
||||
}),
|
||||
)
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
87
frontend/package.json
Normal file
87
frontend/package.json
Normal file
@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^4.0.0",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"@types/chart.js": "^2.9.28",
|
||||
"@types/d3": "^6.2.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/node": "^14.14.7",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^16.9.9",
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/redux": "^3.6.0",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/vis": "^4.21.21",
|
||||
"antd": "^4.8.0",
|
||||
"axios": "^0.21.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"d3": "^6.2.0",
|
||||
"d3-array": "^2.8.0",
|
||||
"d3-ease": "^2.0.0",
|
||||
"d3-flame-graph": "^3.1.1",
|
||||
"d3-tip": "^0.9.1",
|
||||
"material-ui-chip-input": "^2.0.0-beta.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "17.0.0",
|
||||
"react-chartjs-2": "^2.11.1",
|
||||
"react-chips": "^0.8.0",
|
||||
"react-css-theme-switcher": "^0.1.6",
|
||||
"react-dom": "17.0.0",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.0",
|
||||
"react-vis": "^1.11.7",
|
||||
"recharts": "^1.8.5",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"typescript": "^4.0.5",
|
||||
"web-vitals": "^0.2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"storybook": "start-storybook -p 6006 -s public --no-dll",
|
||||
"build-storybook": "build-storybook -s public --no-dll"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/preset-typescript": "^7.12.7",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-csso": "^4.0.1",
|
||||
"gulp-debug": "^4.0.0",
|
||||
"gulp-less": "^4.0.1",
|
||||
"gulp-postcss": "^9.0.0",
|
||||
"less-plugin-npm-import": "^2.1.0",
|
||||
"react-is": "^17.0.1"
|
||||
}
|
||||
}
|
1
frontend/public/dark-theme.css
Normal file
1
frontend/public/dark-theme.css
Normal file
File diff suppressed because one or more lines are too long
BIN
frontend/public/favicon.ico
Normal file
BIN
frontend/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
44
frontend/public/index.html
Normal file
44
frontend/public/index.html
Normal file
@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
1
frontend/public/light-theme.css
Normal file
1
frontend/public/light-theme.css
Normal file
File diff suppressed because one or more lines are too long
BIN
frontend/public/logo192.png
Normal file
BIN
frontend/public/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
BIN
frontend/public/logo512.png
Normal file
BIN
frontend/public/logo512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
25
frontend/public/manifest.json
Normal file
25
frontend/public/manifest.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
frontend/public/robots.txt
Normal file
3
frontend/public/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
5
frontend/public/signoz.svg
Normal file
5
frontend/public/signoz.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="320" height="120" viewBox="0 0 320 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M165.742 72.0469C165.742 70.7188 165.273 69.7031 164.336 69C163.398 68.2812 161.711 67.5312 159.273 66.75C156.836 65.9531 154.906 65.1719 153.484 64.4062C149.609 62.3125 147.672 59.4922 147.672 55.9453C147.672 54.1016 148.188 52.4609 149.219 51.0234C150.266 49.5703 151.758 48.4375 153.695 47.625C155.648 46.8125 157.836 46.4062 160.258 46.4062C162.695 46.4062 164.867 46.8516 166.773 47.7422C168.68 48.6172 170.156 49.8594 171.203 51.4688C172.266 53.0781 172.797 54.9062 172.797 56.9531H165.766C165.766 55.3906 165.273 54.1797 164.289 53.3203C163.305 52.4453 161.922 52.0078 160.141 52.0078C158.422 52.0078 157.086 52.375 156.133 53.1094C155.18 53.8281 154.703 54.7812 154.703 55.9688C154.703 57.0781 155.258 58.0078 156.367 58.7578C157.492 59.5078 159.141 60.2109 161.312 60.8672C165.312 62.0703 168.227 63.5625 170.055 65.3438C171.883 67.125 172.797 69.3438 172.797 72C172.797 74.9531 171.68 77.2734 169.445 78.9609C167.211 80.6328 164.203 81.4688 160.422 81.4688C157.797 81.4688 155.406 80.9922 153.25 80.0391C151.094 79.0703 149.445 77.75 148.305 76.0781C147.18 74.4062 146.617 72.4688 146.617 70.2656H153.672C153.672 74.0312 155.922 75.9141 160.422 75.9141C162.094 75.9141 163.398 75.5781 164.336 74.9062C165.273 74.2188 165.742 73.2656 165.742 72.0469ZM184.281 81H177.484V55.6406H184.281V81ZM177.086 49.0781C177.086 48.0625 177.422 47.2266 178.094 46.5703C178.781 45.9141 179.711 45.5859 180.883 45.5859C182.039 45.5859 182.961 45.9141 183.648 46.5703C184.336 47.2266 184.68 48.0625 184.68 49.0781C184.68 50.1094 184.328 50.9531 183.625 51.6094C182.938 52.2656 182.023 52.5938 180.883 52.5938C179.742 52.5938 178.82 52.2656 178.117 51.6094C177.43 50.9531 177.086 50.1094 177.086 49.0781ZM188.898 68.1328C188.898 64.2422 189.82 61.1094 191.664 58.7344C193.523 56.3594 196.023 55.1719 199.164 55.1719C201.945 55.1719 204.109 56.125 205.656 58.0312L205.938 55.6406H212.078V80.1562C212.078 82.375 211.57 84.3047 210.555 85.9453C209.555 87.5859 208.141 88.8359 206.312 89.6953C204.484 90.5547 202.344 90.9844 199.891 90.9844C198.031 90.9844 196.219 90.6094 194.453 89.8594C192.688 89.125 191.352 88.1719 190.445 87L193.445 82.875C195.133 84.7656 197.18 85.7109 199.586 85.7109C201.383 85.7109 202.781 85.2266 203.781 84.2578C204.781 83.3047 205.281 81.9453 205.281 80.1797V78.8203C203.719 80.5859 201.664 81.4688 199.117 81.4688C196.07 81.4688 193.602 80.2812 191.711 77.9062C189.836 75.5156 188.898 72.3516 188.898 68.4141V68.1328ZM195.672 68.625C195.672 70.9219 196.133 72.7266 197.055 74.0391C197.977 75.3359 199.242 75.9844 200.852 75.9844C202.914 75.9844 204.391 75.2109 205.281 73.6641V63C204.375 61.4531 202.914 60.6797 200.898 60.6797C199.273 60.6797 197.992 61.3438 197.055 62.6719C196.133 64 195.672 65.9844 195.672 68.625ZM245.5 81H238.469L224.781 58.5469V81H217.75V46.875H224.781L238.492 69.375V46.875H245.5V81ZM250.141 68.0859C250.141 65.5703 250.625 63.3281 251.594 61.3594C252.562 59.3906 253.953 57.8672 255.766 56.7891C257.594 55.7109 259.711 55.1719 262.117 55.1719C265.539 55.1719 268.328 56.2188 270.484 58.3125C272.656 60.4062 273.867 63.25 274.117 66.8438L274.164 68.5781C274.164 72.4688 273.078 75.5938 270.906 77.9531C268.734 80.2969 265.82 81.4688 262.164 81.4688C258.508 81.4688 255.586 80.2969 253.398 77.9531C251.227 75.6094 250.141 72.4219 250.141 68.3906V68.0859ZM256.914 68.5781C256.914 70.9844 257.367 72.8281 258.273 74.1094C259.18 75.375 260.477 76.0078 262.164 76.0078C263.805 76.0078 265.086 75.3828 266.008 74.1328C266.93 72.8672 267.391 70.8516 267.391 68.0859C267.391 65.7266 266.93 63.8984 266.008 62.6016C265.086 61.3047 263.789 60.6562 262.117 60.6562C260.461 60.6562 259.18 61.3047 258.273 62.6016C257.367 63.8828 256.914 65.875 256.914 68.5781ZM285.742 75.5391H298.141V81H277.094V76.875L289.023 61.125H277.445V55.6406H297.766V59.6484L285.742 75.5391Z" fill="#F2F2F2"/>
|
||||
<path opacity="0.9" d="M69.1393 109.354C41.2841 109.354 19 87.4976 19 60.1771C19 33.1297 41.2841 11 69.1393 11H100.616C110.922 11 119 19.1962 119 29.0316V60.1771C119 87.4976 96.7159 109.354 69.1393 109.354Z" fill="#002B76"/>
|
||||
<path d="M68.746 38.8879C47.9401 38.8879 36.2207 56.1168 35.7319 56.8501C34.5269 58.6568 34.5269 60.9932 35.7328 62.8011C36.2207 63.5331 47.9401 80.762 68.746 80.762C89.5517 80.762 101.271 63.5331 101.76 62.7997C102.965 60.9932 102.965 58.6568 101.759 56.8489C101.271 56.1168 89.5517 38.8879 68.746 38.8879ZM38.6117 60.8807C38.1839 60.2393 38.1839 59.4106 38.6117 58.7691C39.0048 58.1792 47.3488 45.9228 62.2671 42.9827C55.5068 45.5922 50.6965 52.1553 50.6965 59.825C50.6965 67.4946 55.5068 74.0577 62.2671 76.6672C47.3488 73.7271 39.0048 61.4706 38.6117 60.8807ZM60.4284 59.8249C60.4284 60.7806 59.6538 61.5552 58.698 61.5552C57.7423 61.5552 56.9676 60.7806 56.9676 59.8249C56.9676 53.3304 62.2513 48.0468 68.7458 48.0468C69.7015 48.0468 70.4762 48.8213 70.4762 49.7771C70.4762 50.7329 69.7015 51.5074 68.7458 51.5074C64.1595 51.5074 60.4284 55.2384 60.4284 59.8249ZM68.746 64.0317C66.4263 64.0317 64.5392 62.1446 64.5392 59.825C64.5392 57.5053 66.4263 55.6181 68.746 55.6181C71.0658 55.6181 72.9528 57.5052 72.9528 59.825C72.9528 62.1447 71.0658 64.0317 68.746 64.0317ZM98.8802 60.8807C98.4871 61.4706 90.1431 73.7269 75.2249 76.6672C81.9853 74.0575 86.7955 67.4945 86.7955 59.825C86.7955 52.1554 81.9853 45.5923 75.2249 42.9828C90.1431 45.923 98.4871 58.1793 98.8802 58.7692C99.308 59.4106 99.308 60.2393 98.8802 60.8807Z" fill="#F9F2F9"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
78
frontend/src/actions/global.ts
Normal file
78
frontend/src/actions/global.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { ActionTypes } from './types';
|
||||
import { Moment } from 'moment'
|
||||
|
||||
|
||||
export type DateTimeRangeType = [Moment|null,Moment|null]|null;
|
||||
|
||||
|
||||
export interface GlobalTime {
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
}
|
||||
|
||||
export interface updateTimeIntervalAction {
|
||||
type: ActionTypes.updateTimeInterval;
|
||||
payload: GlobalTime;
|
||||
}
|
||||
|
||||
export const updateTimeInterval = (interval:string, datetimeRange?:[number,number]) => {
|
||||
|
||||
let maxTime: number = 0;
|
||||
let minTime: number = 0;
|
||||
console.log('interval', interval, typeof(interval))
|
||||
// if interval string is custom, then datetimRange should be present and max & min time should be
|
||||
// set directly based on that. Assuming datetimeRange values are in ms, and minTime is 0th element
|
||||
|
||||
switch (interval) {
|
||||
case '15min':
|
||||
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-15*60*1000)*1000000;
|
||||
console.log('max time 15 in case',maxTime)
|
||||
break;
|
||||
|
||||
case '30min':
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-30*60*1000)*1000000;
|
||||
console.log('max time in 30 min case',maxTime)
|
||||
break;
|
||||
|
||||
case '1hr':
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-1*60*60*1000)*1000000;
|
||||
break;
|
||||
|
||||
case '6hr':
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-6*60*60*1000)*1000000;
|
||||
break;
|
||||
|
||||
case '1day':
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-24*60*60*1000)*1000000;
|
||||
break;
|
||||
|
||||
case '1week':
|
||||
maxTime=Date.now()*1000000; // in nano sec
|
||||
minTime=(Date.now()-7*24*60*60*1000)*1000000;
|
||||
break;
|
||||
|
||||
case 'custom':
|
||||
if (datetimeRange !== undefined)
|
||||
{
|
||||
maxTime=datetimeRange[1]*1000000;// in nano sec
|
||||
minTime=datetimeRange[0]*1000000;// in nano sec
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('not found matching case')
|
||||
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
type: ActionTypes.updateTimeInterval,
|
||||
payload: {maxTime:maxTime, minTime:minTime},
|
||||
};
|
||||
};
|
6
frontend/src/actions/index.ts
Normal file
6
frontend/src/actions/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export * from './types';
|
||||
export * from './traceFilters';
|
||||
export * from './traces';
|
||||
export * from './metrics';
|
||||
export * from './usage';
|
||||
export * from './global';
|
113
frontend/src/actions/metrics.ts
Normal file
113
frontend/src/actions/metrics.ts
Normal file
@ -0,0 +1,113 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import metricsAPI from '../api/metricsAPI';
|
||||
import { GlobalTime } from './global';
|
||||
import { ActionTypes } from './types';
|
||||
|
||||
export interface servicesListItem{
|
||||
"serviceName": string;
|
||||
"p99": number;
|
||||
"avgDuration": number;
|
||||
"numCalls": number;
|
||||
"callRate": number;
|
||||
"numErrors": number;
|
||||
"errorRate": number;
|
||||
};
|
||||
|
||||
export interface metricItem{
|
||||
"timestamp":number;
|
||||
"p50":number;
|
||||
"p90":number;
|
||||
"p99":number;
|
||||
"numCalls":number;
|
||||
"callRate":number;
|
||||
"numErrors":number;
|
||||
"errorRate":number;
|
||||
}
|
||||
|
||||
export interface topEndpointListItem{
|
||||
"p50": number;
|
||||
"p90": number;
|
||||
"p99": number;
|
||||
"numCalls": number;
|
||||
"name": string;
|
||||
};
|
||||
|
||||
export interface customMetricsItem{
|
||||
"timestamp": number;
|
||||
"value": number;
|
||||
};
|
||||
|
||||
|
||||
export interface getServicesListAction {
|
||||
type: ActionTypes.getServicesList;
|
||||
payload: servicesListItem[];
|
||||
}
|
||||
|
||||
export interface getServiceMetricsAction{
|
||||
type: ActionTypes.getServiceMetrics;
|
||||
payload: metricItem[];
|
||||
}
|
||||
|
||||
export interface getTopEndpointsAction {
|
||||
type: ActionTypes.getTopEndpoints;
|
||||
payload: topEndpointListItem[];
|
||||
}
|
||||
|
||||
export interface getFilteredTraceMetricsAction{
|
||||
type: ActionTypes.getFilteredTraceMetrics;
|
||||
payload: customMetricsItem[];
|
||||
}
|
||||
|
||||
|
||||
export const getServicesList = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'services?start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
||||
console.log(request_string);
|
||||
const response = await metricsAPI.get<servicesListItem[]>(request_string);
|
||||
|
||||
dispatch<getServicesListAction>({
|
||||
type: ActionTypes.getServicesList,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const getServicesMetrics = (serviceName:string, globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'service/overview?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=60';
|
||||
const response = await metricsAPI.get<metricItem[]>(request_string);
|
||||
|
||||
dispatch<getServiceMetricsAction>({
|
||||
type: ActionTypes.getServiceMetrics,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const getTopEndpoints = (serviceName:string, globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'service/top_endpoints?service='+serviceName+'&start='+globalTime.minTime+'&end='+globalTime.maxTime;
|
||||
const response = await metricsAPI.get<topEndpointListItem[]>(request_string);
|
||||
|
||||
dispatch<getTopEndpointsAction>({
|
||||
type: ActionTypes.getTopEndpoints,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export const getFilteredTraceMetrics = (filter_params: string, globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'spans/aggregates?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
||||
const response = await metricsAPI.get<customMetricsItem[]>(request_string);
|
||||
|
||||
dispatch<getFilteredTraceMetricsAction>({
|
||||
type: ActionTypes.getFilteredTraceMetrics,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
};
|
50
frontend/src/actions/traceFilters.ts
Normal file
50
frontend/src/actions/traceFilters.ts
Normal file
@ -0,0 +1,50 @@
|
||||
// Action creator must have a type and optionally a payload
|
||||
import { ActionTypes } from './types'
|
||||
|
||||
export interface TagItem {
|
||||
key: string;
|
||||
value: string;
|
||||
operator: 'equals'|'contains';
|
||||
}
|
||||
|
||||
export interface LatencyValue {
|
||||
min:string;
|
||||
max:string;
|
||||
}
|
||||
|
||||
export interface TraceFilters{
|
||||
tags?: TagItem[];
|
||||
service?:string;
|
||||
latency?:LatencyValue;
|
||||
operation?:string;
|
||||
}
|
||||
|
||||
//define interface for action. Action creator always returns object of this type
|
||||
export interface updateTraceFiltersAction {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: TraceFilters,
|
||||
}
|
||||
|
||||
export const updateTraceFilters = (traceFilters: TraceFilters) => {
|
||||
console.log('in update trace filters',traceFilters)
|
||||
return {
|
||||
type: ActionTypes.updateTraceFilters,
|
||||
payload: traceFilters,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export interface updateInputTagAction {
|
||||
type: ActionTypes.updateInput,
|
||||
payload: string,
|
||||
}
|
||||
|
||||
export const updateInputTag = (Input: string) => {
|
||||
|
||||
return {
|
||||
type: ActionTypes.updateInput,
|
||||
payload: Input,
|
||||
};
|
||||
};
|
||||
|
||||
//named export when you want to export multiple functions from the same file
|
146
frontend/src/actions/traces.ts
Normal file
146
frontend/src/actions/traces.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import { ActionTypes } from './types';
|
||||
import tracesAPI from '../api/tracesAPI';
|
||||
import { Dispatch } from 'redux';
|
||||
import { GlobalTime } from './global';
|
||||
|
||||
|
||||
// PNOTE
|
||||
// define trace interface - what it should return
|
||||
// define action creator to show list of traces on component mount -- use useEffect ( , []) -> Mounts when loaded first time
|
||||
// Date() - takes number of milliseconds as input, our API takes in microseconds
|
||||
// Sample API call for traces - https://api.signoz.io/api/traces?end=1606968273667000&limit=20&lookback=2d&maxDuration=&minDuration=&service=driver&operation=&start=1606968100867000
|
||||
|
||||
|
||||
export interface Tree{
|
||||
name: string;
|
||||
value: number;
|
||||
children?: Tree[];
|
||||
}
|
||||
|
||||
export interface RefItem{
|
||||
refType: string;
|
||||
traceID: string;
|
||||
spanID: string;
|
||||
|
||||
}
|
||||
|
||||
export interface TraceTagItem{
|
||||
key: string;
|
||||
// type: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface ProcessItem{
|
||||
serviceName: string;
|
||||
tags: TraceTagItem[];
|
||||
}
|
||||
|
||||
// PNOTE - Temp DS for converting span to tree which can be consumed by flamegraph
|
||||
export interface pushDStree {
|
||||
id: string;
|
||||
name: string;
|
||||
value: number;
|
||||
time: number;
|
||||
startTime: number;
|
||||
tags: TraceTagItem[];
|
||||
children: pushDStree[];
|
||||
}
|
||||
|
||||
export interface spanItem{
|
||||
traceID: string; // index 0
|
||||
spanID: string; // index 1
|
||||
operationName: string; // index 2
|
||||
startTime: number; // index 3
|
||||
duration: number; // index 4
|
||||
references: RefItem[]; // index 5
|
||||
tags: []; //index 6
|
||||
logs: []; // index 7
|
||||
processID: string; // index 8
|
||||
warnings: []; // index 9
|
||||
children: pushDStree[]; // index 10 // PNOTE - added this as we are adding extra field in span item to convert trace to tree.
|
||||
// Should this field be optional?
|
||||
}
|
||||
|
||||
//let mapped_array :{ [id: string] : spanItem; } = {};
|
||||
|
||||
export interface traceItem{
|
||||
traceID: string;
|
||||
spans: spanItem[];
|
||||
processes: { [id: string] : ProcessItem; } ;
|
||||
warnings: [];
|
||||
}
|
||||
|
||||
export interface traceResponse{
|
||||
data: traceItem[];
|
||||
total: number;
|
||||
limit: number;
|
||||
offset: number;
|
||||
error: [];
|
||||
}
|
||||
|
||||
export type span = [number, string, string, string, string, string, string, string|string[], string|string[], string|string[], pushDStree[]];
|
||||
|
||||
|
||||
|
||||
export interface spanList{
|
||||
|
||||
events: span[];
|
||||
segmentID: string;
|
||||
columns: string[];
|
||||
|
||||
}
|
||||
|
||||
// export interface traceResponseNew{
|
||||
// [id: string] : spanList;
|
||||
// }
|
||||
export interface traceResponseNew{
|
||||
[id: string] : spanList;
|
||||
|
||||
}
|
||||
|
||||
export interface spansWSameTraceIDResponse{
|
||||
[id: string] : spanList;
|
||||
}
|
||||
|
||||
|
||||
export interface FetchTracesAction {
|
||||
type: ActionTypes.fetchTraces;
|
||||
payload: traceResponseNew;
|
||||
}
|
||||
|
||||
export interface FetchTraceItemAction {
|
||||
type: ActionTypes.fetchTraceItem;
|
||||
payload: spansWSameTraceIDResponse;
|
||||
}
|
||||
|
||||
export const fetchTraces = (globalTime: GlobalTime, filter_params: string ) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
if (globalTime){
|
||||
let request_string = 'spans?limit=100&lookback=2d&start='+globalTime.minTime+'&end='+globalTime.maxTime+'&'+filter_params;
|
||||
const response = await tracesAPI.get<traceResponseNew>(request_string);
|
||||
|
||||
dispatch<FetchTracesAction>({
|
||||
type: ActionTypes.fetchTraces,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response?
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
export const fetchTraceItem = (traceID: string) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'traces/'+traceID;
|
||||
const response = await tracesAPI.get<spansWSameTraceIDResponse>(request_string);
|
||||
|
||||
dispatch<FetchTraceItemAction>({
|
||||
type: ActionTypes.fetchTraceItem,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response?
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
|
22
frontend/src/actions/types.ts
Normal file
22
frontend/src/actions/types.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { FetchTracesAction, FetchTraceItemAction } from './traces';
|
||||
import { updateTraceFiltersAction, updateInputTagAction } from './traceFilters';
|
||||
import {getServicesListAction,getServiceMetricsAction, getTopEndpointsAction, getFilteredTraceMetricsAction} from './metrics'
|
||||
import {getUsageDataAction} from './usage'
|
||||
import {updateTimeIntervalAction} from './global';
|
||||
|
||||
|
||||
|
||||
export enum ActionTypes {
|
||||
updateTraceFilters,
|
||||
updateInput,
|
||||
fetchTraces,
|
||||
fetchTraceItem,
|
||||
getServicesList,
|
||||
getServiceMetrics,
|
||||
getTopEndpoints,
|
||||
getUsageData,
|
||||
updateTimeInterval,
|
||||
getFilteredTraceMetrics,
|
||||
}
|
||||
|
||||
export type Action = FetchTraceItemAction | FetchTracesAction | updateTraceFiltersAction | updateInputTagAction |getServicesListAction| getServiceMetricsAction| getTopEndpointsAction | getUsageDataAction | updateTimeIntervalAction| getFilteredTraceMetricsAction;
|
29
frontend/src/actions/usage.ts
Normal file
29
frontend/src/actions/usage.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Dispatch } from 'redux';
|
||||
import metricsAPI from '../api/metricsAPI';
|
||||
import { ActionTypes } from './types';
|
||||
import { GlobalTime } from './global';
|
||||
|
||||
|
||||
export interface usageDataItem{
|
||||
"timestamp":number;
|
||||
"count":number;
|
||||
}
|
||||
|
||||
export interface getUsageDataAction {
|
||||
type: ActionTypes.getUsageData;
|
||||
payload: usageDataItem[];
|
||||
}
|
||||
|
||||
export const getUsageData = (globalTime: GlobalTime) => {
|
||||
return async (dispatch: Dispatch) => {
|
||||
let request_string = 'usage?start='+globalTime.minTime+'&end='+globalTime.maxTime+'&step=3600&service=driver';
|
||||
//Step can only be multiple of 3600
|
||||
const response = await metricsAPI.get<usageDataItem[]>(request_string);
|
||||
|
||||
dispatch<getUsageDataAction>({
|
||||
type: ActionTypes.getUsageData,
|
||||
payload: response.data
|
||||
//PNOTE - response.data in the axios response has the actual API response
|
||||
});
|
||||
};
|
||||
};
|
9
frontend/src/api/graphQuery.js
Normal file
9
frontend/src/api/graphQuery.js
Normal file
@ -0,0 +1,9 @@
|
||||
import axios from 'axios';
|
||||
|
||||
// No auth for the API
|
||||
export default axios.create({
|
||||
baseURL: 'https://api.signoz.io/api/prom/api/v1',
|
||||
}
|
||||
);
|
||||
|
||||
|
9
frontend/src/api/metricsAPI.js
Normal file
9
frontend/src/api/metricsAPI.js
Normal file
@ -0,0 +1,9 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export default axios.create({
|
||||
// baseURL: 'http://104.211.113.204:8080/api/v1/',
|
||||
// baseURL: process.env.REACT_APP_QUERY_SERVICE_URL,
|
||||
baseURL: 'http://localhost:3000/api/v1/',
|
||||
// console.log('in metrics API', process.env.QUERY_SERVICE_URL)
|
||||
}
|
||||
);
|
16
frontend/src/api/tracesAPI.js
Normal file
16
frontend/src/api/tracesAPI.js
Normal file
@ -0,0 +1,16 @@
|
||||
import axios from 'axios';
|
||||
//import { format } from 'path';
|
||||
|
||||
export default axios.create({
|
||||
// baseURL: 'http://104.211.113.204:8080/api/v1/'
|
||||
// baseURL: process.env.QUERY_SERVICE_URL,
|
||||
// console.log('in traces API', process.env.QUERY_SERVICE_URL)
|
||||
baseURL: 'http://localhost:3000/api/v1/',
|
||||
|
||||
|
||||
});
|
||||
|
||||
//individual trace format
|
||||
//https://api.signoz.io/api/traces/146754946da0552e
|
||||
|
||||
//http://104.211.113.204:8080/api/v1/traces/000000000000000053a5b7a93bc5e08a
|
12
frontend/src/assets/index.css
Normal file
12
frontend/src/assets/index.css
Normal file
@ -0,0 +1,12 @@
|
||||
@import "~antd/dist/antd.dark.css";
|
||||
@import "~antd/dist/antd.compact.css";
|
||||
|
||||
/* #components-layout-demo-side .logo {
|
||||
height: 32px;
|
||||
margin: 16px;
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.site-layout .site-layout-background {
|
||||
background: #fff;
|
||||
} */
|
118
frontend/src/components/App.tsx
Normal file
118
frontend/src/components/App.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import React, { Suspense, useState } from 'react';
|
||||
import { Layout, Menu, Switch as ToggleButton, Spin, Row, Col } from 'antd';
|
||||
import { useThemeSwitcher } from "react-css-theme-switcher";
|
||||
|
||||
|
||||
|
||||
import { NavLink, BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
import {
|
||||
|
||||
LineChartOutlined,
|
||||
BarChartOutlined,
|
||||
DeploymentUnitOutlined,
|
||||
AlignLeftOutlined,
|
||||
} from '@ant-design/icons';
|
||||
|
||||
|
||||
|
||||
import DateTimeSelector from './DateTimeSelector';
|
||||
import ShowBreadcrumbs from './ShowBreadcrumbs';
|
||||
|
||||
|
||||
const { Content, Footer, Sider } = Layout;
|
||||
|
||||
const ServiceMetrics = React.lazy(() => import('./metrics/ServiceMetricsDef'));
|
||||
const ServiceMap = React.lazy(() => import('./servicemap/ServiceMap'));
|
||||
const TraceDetail = React.lazy(() => import('./traces/TraceDetail'));
|
||||
const TraceGraph = React.lazy(() => import ('./traces/TraceGraphDef' ));
|
||||
const UsageExplorer = React.lazy(() => import ('./usage/UsageExplorerDef' ));
|
||||
const ServicesTable = React.lazy(() => import('./metrics/ServicesTableDef'));
|
||||
|
||||
|
||||
//PNOTE
|
||||
//React. lazy currently only supports default exports. If the module you want to import uses named exports, you can create an intermediate module that reexports it as the default. This ensures that tree shaking keeps working and that you don't pull in unused components.
|
||||
|
||||
const App = () => {
|
||||
// state = { collapsed: false, isDarkMode: true };
|
||||
const { switcher, currentTheme, status, themes } = useThemeSwitcher();
|
||||
const [isDarkMode, setIsDarkMode] = useState(true);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
const toggleTheme = (isChecked :boolean) => {
|
||||
setIsDarkMode(isChecked);
|
||||
switcher({ theme: isChecked ? themes.dark : themes.light });
|
||||
};
|
||||
|
||||
|
||||
const onCollapse = (): void => {
|
||||
// console.log(collapsed);
|
||||
setCollapsed(!collapsed);
|
||||
};
|
||||
|
||||
if (status === "loading") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Router basename="/">
|
||||
<Layout style={{ minHeight: '100vh' }}>
|
||||
<Sider collapsible collapsed={collapsed} onCollapse={onCollapse} width={160}>
|
||||
<div className="logo">
|
||||
<ToggleButton checked={isDarkMode} onChange={toggleTheme} />
|
||||
<NavLink to='/'><img src={"signoz.svg"} alt={'SigNoz'} style={{margin: '5%', width: 100, display: !collapsed ? 'block' : 'none'}} /></NavLink>
|
||||
</div>
|
||||
|
||||
<Menu theme="dark" defaultSelectedKeys={['1']} mode="inline">
|
||||
<Menu.Item key="1" icon={<BarChartOutlined />}>
|
||||
<NavLink to='/application' style={{fontSize: 12, textDecoration: 'none'}}>Metrics</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="2" icon={<AlignLeftOutlined />}>
|
||||
<NavLink to='/traces' style={{fontSize: 12, textDecoration: 'none'}}>Traces</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="3" icon={<DeploymentUnitOutlined />}>
|
||||
<NavLink to='/service-map' style={{fontSize: 12, textDecoration: 'none'}}>Service Map</NavLink>
|
||||
</Menu.Item>
|
||||
<Menu.Item key="4" icon={<LineChartOutlined />}>
|
||||
<NavLink to='/usage-explorer' style={{fontSize: 12, textDecoration: 'none'}}>Usage Explorer</NavLink>
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</Sider>
|
||||
<Layout className="site-layout">
|
||||
|
||||
<Content style={{ margin: '0 16px' }}>
|
||||
<Row>
|
||||
<Col span={20}>
|
||||
<ShowBreadcrumbs />
|
||||
</Col>
|
||||
|
||||
<Col span={4}>
|
||||
<DateTimeSelector />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* <Divider /> */}
|
||||
|
||||
<Suspense fallback={<Spin size="large" />}>
|
||||
<Switch>
|
||||
<Route path="/application/:servicename" component={ServiceMetrics}/>
|
||||
<Route path="/service-map" component={ServiceMap}/>
|
||||
<Route path="/traces" exact component={TraceDetail}/>
|
||||
<Route path="/traces/:id" component={TraceGraph}/>
|
||||
<Route path="/usage-explorer" component={UsageExplorer}/>
|
||||
<Route path="/" component={ServicesTable}/>
|
||||
|
||||
</Switch>
|
||||
</Suspense>
|
||||
|
||||
|
||||
</Content>
|
||||
<Footer style={{ textAlign: 'center', fontSize: 10 }}>SigNoz Inc. ©2020 </Footer>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Router>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
68
frontend/src/components/CustomDateTimeModal.tsx
Normal file
68
frontend/src/components/CustomDateTimeModal.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Modal, Col, Row, DatePicker, TimePicker} from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import {DateTimeRangeType} from '../actions'
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
interface CustomDateTimeModalProps {
|
||||
visible: boolean;
|
||||
onCreate: (dateTimeRange: DateTimeRangeType) => void; //Store is defined in antd forms library
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
|
||||
const CustomDateTimeModal: React.FC<CustomDateTimeModalProps> = ({ //destructuring props
|
||||
visible,
|
||||
onCreate,
|
||||
onCancel,
|
||||
}) => {
|
||||
|
||||
// RangeValue<Moment> == [Moment|null,Moment|null]|null
|
||||
|
||||
const [customDateTimeRange, setCustomDateTimeRange]=useState<DateTimeRangeType>();
|
||||
|
||||
function handleRangePickerOk(date_time: DateTimeRangeType) {
|
||||
console.log('onhandleRangePickerOk: ', date_time?date_time[0]?.toDate():null,date_time?date_time[1]?.toDate():null );
|
||||
setCustomDateTimeRange(date_time);
|
||||
}
|
||||
|
||||
// function handleApplyDateTimeModal (){
|
||||
// if (customDateTimeRange !== null && customDateTimeRange !== undefined && customDateTimeRange[0] !== null && customDateTimeRange[1] !== null )
|
||||
// console.log('in handleApplyDateTimeModal', customDateTimeRange[0].toDate(),customDateTimeRange[1].toDate())
|
||||
// }
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="Chose date and time range"
|
||||
okText="Apply"
|
||||
cancelText="Cancel"
|
||||
onCancel={onCancel}
|
||||
// style={{ position: "absolute", top: 60, left: parseInt(`${positionleft}`) }} //get position as a prop from parent component, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
|
||||
style={{ position: "absolute", top: 60, right: 40 }}
|
||||
onOk={() => onCreate(customDateTimeRange?customDateTimeRange:null)}
|
||||
>
|
||||
|
||||
<RangePicker onOk={handleRangePickerOk} showTime />
|
||||
{/* <RangePicker renderExtraFooter={() => 'extra footer'} showTime /> */}
|
||||
{/* <Row>
|
||||
<Col span={6}> <DatePicker />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<TimePicker />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
|
||||
<DatePicker />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<TimePicker />
|
||||
</Col>
|
||||
</Row> */}
|
||||
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomDateTimeModal;
|
157
frontend/src/components/DateTimeSelector.tsx
Normal file
157
frontend/src/components/DateTimeSelector.tsx
Normal file
@ -0,0 +1,157 @@
|
||||
import React, { useState } from 'react';
|
||||
import {Select, Button,Space, Form} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import CustomDateTimeModal from './CustomDateTimeModal';
|
||||
import { GlobalTime, updateTimeInterval } from '../actions';
|
||||
import { StoreState } from '../reducers';
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
|
||||
import {DateTimeRangeType} from '../actions'
|
||||
|
||||
|
||||
import {
|
||||
|
||||
RedoOutlined,
|
||||
|
||||
} from '@ant-design/icons';
|
||||
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const DateTimeWrapper = styled.div`
|
||||
margin-top:20px;
|
||||
`;
|
||||
|
||||
interface DateTimeSelectorProps extends RouteComponentProps<any> {
|
||||
currentpath?:string;
|
||||
updateTimeInterval: Function;
|
||||
globalTime: GlobalTime;
|
||||
// urlTime:string;
|
||||
|
||||
}
|
||||
|
||||
|
||||
const _DateTimeSelector = (props:DateTimeSelectorProps) => {
|
||||
|
||||
const [customDTPickerVisible,setCustomDTPickerVisible]=useState(false);
|
||||
const [timeInterval,setTimeInterval]=useState('15min')
|
||||
const [refreshButtonHidden, setRefreshButtonHidden]=useState(false)
|
||||
|
||||
const [form_dtselector] = Form.useForm();
|
||||
|
||||
console.log('props in DateTimeSelector',props)
|
||||
|
||||
const handleOnSelect = (value:string) =>
|
||||
{
|
||||
console.log(value);
|
||||
if (value === 'custom')
|
||||
{
|
||||
setCustomDTPickerVisible(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
props.history.push({
|
||||
search: '?time='+value,
|
||||
}) //pass time in URL query param for all choices except custom in datetime picker
|
||||
props.updateTimeInterval(value);
|
||||
// console.log('in handle on select urlTime',props.urlTime)
|
||||
// console.log('global time', props.globalTime.maxTime,props.globalTime.minTime)
|
||||
setTimeInterval(value);
|
||||
setRefreshButtonHidden(false); // for normal intervals, show refresh button
|
||||
}
|
||||
}
|
||||
|
||||
//function called on clicking apply in customDateTimeModal
|
||||
const handleOk = (dateTimeRange:DateTimeRangeType) =>
|
||||
{
|
||||
console.log('in handleOk of DateTimeSelector',dateTimeRange);
|
||||
// pass values in ms [minTime, maxTime]
|
||||
if (dateTimeRange!== null && dateTimeRange!== undefined && dateTimeRange[0]!== null && dateTimeRange[1]!== null )
|
||||
{
|
||||
props.updateTimeInterval('custom',[dateTimeRange[0].valueOf(),dateTimeRange[1].valueOf()])
|
||||
//setting globaltime
|
||||
setRefreshButtonHidden(true);
|
||||
form_dtselector.setFieldsValue({interval:(dateTimeRange[0].valueOf().toString()+'-'+dateTimeRange[1].valueOf().toString()) ,})
|
||||
}
|
||||
setCustomDTPickerVisible(false);
|
||||
}
|
||||
|
||||
const timeSinceLastRefresh = () => {
|
||||
let timeDiffSec = Math.round((Date.now() - Math.round(props.globalTime.maxTime/1000000))/1000);
|
||||
|
||||
//How will Refresh button get updated? Needs to be periodically updated via timer.
|
||||
// For now, not returning any text here
|
||||
// if (timeDiffSec < 60)
|
||||
// return timeDiffSec.toString()+' s';
|
||||
// else if (timeDiffSec < 3600)
|
||||
// return Math.round(timeDiffSec/60).toString()+' min';
|
||||
// else
|
||||
// return Math.round(timeDiffSec/3600).toString()+' hr';
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
const handleRefresh = () =>
|
||||
{
|
||||
console.log('refreshing time interval', timeInterval)
|
||||
props.updateTimeInterval(timeInterval);
|
||||
}
|
||||
if (props.location.pathname.startsWith('/usage-explorer')) {
|
||||
return null;
|
||||
} else
|
||||
{
|
||||
return (
|
||||
|
||||
<DateTimeWrapper>
|
||||
<Space>
|
||||
<Form form={form_dtselector} layout='inline' initialValues={{ interval:'15min', }} style={{marginTop: 10, marginBottom:10}}>
|
||||
<FormItem name='interval'>
|
||||
<Select style={{ width: 300 }} onSelect={handleOnSelect} >
|
||||
<Option value="custom">Custom</Option>
|
||||
<Option value="15min">15 min</Option>
|
||||
<Option value="30min">30 min</Option>
|
||||
<Option value="1hr">1 hour</Option>
|
||||
<Option value="6hr">6 hour</Option>
|
||||
<Option value="1day">1 day</Option>
|
||||
<Option value="1week">1 week</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem hidden={refreshButtonHidden} name='refresh_button'>
|
||||
|
||||
<Button type="primary" onClick={handleRefresh}>Refresh {timeSinceLastRefresh()}</Button>
|
||||
{/* if refresh time is more than x min, give a message? */}
|
||||
</FormItem>
|
||||
</Form>
|
||||
<CustomDateTimeModal
|
||||
visible={customDTPickerVisible}
|
||||
onCreate={handleOk}
|
||||
onCancel={() => {
|
||||
setCustomDTPickerVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
</DateTimeWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
//const mapStateToProps = (state: StoreState, ownProps: RouteComponentProps<any> -- ownProps is of type RouteComponentProps<any>
|
||||
const mapStateToProps = (state: StoreState ): { globalTime: GlobalTime} => {
|
||||
|
||||
return { globalTime : state.globalTime };
|
||||
// Check if we should pass as urltime or how do we handle this
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const DateTimeSelector = connect(mapStateToProps, {
|
||||
updateTimeInterval: updateTimeInterval,
|
||||
|
||||
})(_DateTimeSelector);
|
||||
|
||||
export default withRouter(DateTimeSelector);
|
77
frontend/src/components/ShowBreadcrumbs.tsx
Normal file
77
frontend/src/components/ShowBreadcrumbs.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
import {Breadcrumb} from 'antd';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const BreadCrumbWrapper = styled.div`
|
||||
padding-top:20px;
|
||||
padding-left:20px;
|
||||
`;
|
||||
|
||||
|
||||
const breadcrumbNameMap :any = { // PNOTE - TO DO - Remove any and do typechecking - like https://stackoverflow.com/questions/56568423/typescript-no-index-signature-with-a-parameter-of-type-string-was-found-on-ty
|
||||
'/application': 'Application',
|
||||
'/traces': 'Traces',
|
||||
'/service-map': 'Service Map',
|
||||
'/usage-explorer': 'Usage Explorer',
|
||||
// only top level things should be mapped here, rest should be taken dynamically from url
|
||||
// Does this work if url has 2 levels of dynamic parameters? - Currently we have only 1 level
|
||||
// this structure ignores query params like time -- which is good
|
||||
};
|
||||
|
||||
|
||||
const ShowBreadcrumbs = withRouter(props => {
|
||||
const { location } = props;
|
||||
const pathSnippets = location.pathname.split('/').filter(i => i);
|
||||
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
|
||||
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
|
||||
if (breadcrumbNameMap[url] === undefined){
|
||||
return (
|
||||
<Breadcrumb.Item key={url}>
|
||||
<Link to={url}>{url.split('/').slice(-1)[0]}</Link>
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
} else
|
||||
{
|
||||
return (
|
||||
<Breadcrumb.Item key={url}>
|
||||
<Link to={url}>{breadcrumbNameMap[url]}</Link>
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
});
|
||||
const breadcrumbItems = [
|
||||
<Breadcrumb.Item key="home">
|
||||
<Link to="/">Home</Link>
|
||||
</Breadcrumb.Item>,
|
||||
].concat(extraBreadcrumbItems);
|
||||
return (
|
||||
|
||||
<BreadCrumbWrapper>
|
||||
<Breadcrumb>{breadcrumbItems}</Breadcrumb>
|
||||
</BreadCrumbWrapper>
|
||||
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// const ShowBreadcrumbs = () => {
|
||||
|
||||
// return (
|
||||
// <Breadcrumb style={{ margin: '16px 0' , fontSize: 12 }}>
|
||||
// <Breadcrumb.Item>
|
||||
// <Link to="/">Home</Link></Breadcrumb.Item>
|
||||
// <Breadcrumb.Item><Link to="/application">Application</Link></Breadcrumb.Item>
|
||||
// </Breadcrumb>
|
||||
// //programmatically populate it with links
|
||||
// );
|
||||
|
||||
// }
|
||||
|
||||
export default ShowBreadcrumbs;
|
295
frontend/src/components/metrics/ErrorRateChart.tsx
Normal file
295
frontend/src/components/metrics/ErrorRateChart.tsx
Normal file
@ -0,0 +1,295 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { metricItem } from '../../actions/metrics'
|
||||
|
||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
||||
background-color:white;
|
||||
border:1px solid rgba(219,112,147,0.5);
|
||||
zIndex:10;
|
||||
position:absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
font-size:10px;
|
||||
border-radius:2px;
|
||||
`;
|
||||
|
||||
const PopUpElements = styled.p`
|
||||
color:black;
|
||||
margin-bottom:0px;
|
||||
padding-left:4px;
|
||||
padding-right:4px;
|
||||
&:hover {
|
||||
cursor:pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
// PNOTE - Check if this should be the case
|
||||
const theme = 'dark';
|
||||
|
||||
|
||||
|
||||
interface ErrorRateChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
}
|
||||
|
||||
interface ErrorRateChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class ErrorRateChart extends React.Component<ErrorRateChartProps>{
|
||||
|
||||
constructor(props: ErrorRateChartProps) {
|
||||
super(props);
|
||||
console.log('React CreatRef', React.createRef());
|
||||
this.chartRef = React.createRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
state = {
|
||||
// data: props.data,
|
||||
xcoordinate:0,
|
||||
ycoordinate:0,
|
||||
showpopUp:false,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
|
||||
onClickhandler = async(e:any,event:any) => {
|
||||
|
||||
console.log('e graph', e)
|
||||
// console.log('event graph',event)
|
||||
//PNOTE - e has all key values from mouse event, event has only reference to handler functions
|
||||
// PNOTE - https://github.com/emn178/angular2-chartjs/issues/29 - for listening only to element points
|
||||
// var firstPoin = this.chart.current.getElementAtEvent(e)
|
||||
|
||||
console.log('chartref',this.chartRef.current.chartInstance);
|
||||
|
||||
var firstPoint;
|
||||
if(this.chartRef){
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
console.log('firstPoint', firstPoint);
|
||||
|
||||
|
||||
// if (firstPoint) {
|
||||
// var label = myChart.data.labels[firstPoint._index];
|
||||
// var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
|
||||
// }
|
||||
if (firstPoint)
|
||||
{// PNOTE - TODO - Is await needed in this expression?
|
||||
await this.setState({
|
||||
xcoordinate:e.offsetX+20,
|
||||
ycoordinate:e.offsetY,
|
||||
showpopUp:true,
|
||||
// graphInfo:{...event}
|
||||
})
|
||||
}
|
||||
// console.log(this.state.graphInfo.payload.timestamp)
|
||||
//this.props.applicationTimeStamp(e.payload.x)
|
||||
// this.props.history.push('/traces?timestamp=' + e.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoTracesHandler=()=>{
|
||||
console.log('in gotoTraces handler')
|
||||
this.props.history.push('/traces')
|
||||
// this.props.history.push('/traces?timestamp=' + this.state.graphInfo.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoAlertsHandler=()=>{
|
||||
console.log('in gotoAlerts handler')
|
||||
this.props.history.push('/service-map')
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
responsive: true,
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Error per sec',
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
padding: 2,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
|
||||
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontSize: 10,
|
||||
boxWidth : 10,
|
||||
usePointStyle : true,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
bodyFontSize: 10,
|
||||
titleFontSize: 10,
|
||||
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
// return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel!.Fixed(3);
|
||||
// not able to do toFixed(3) in typescript as string|number type is not working with toFixed(3) function for
|
||||
// as toFixed() function only works with numbers
|
||||
// using type of check gives issues in 'label' variable name
|
||||
//!That's the non-null assertion operator. It is a way to tell the compiler "this expression cannot be null or undefined here, so don't complain about the possibility of it being null or undefined." Sometimes the type checker is unable to make that determination itself.
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
stacked: false,
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
|
||||
// scaleLabel: {
|
||||
// display: true,
|
||||
// labelString: 'latency in ms',
|
||||
// fontSize: 6,
|
||||
// padding: 4,
|
||||
// },
|
||||
gridLines: {
|
||||
// You can change the color, the dash effect, the main axe color, etc.
|
||||
borderDash: [1, 4],
|
||||
color: "#D3D3D3",
|
||||
lineWidth: 0.25,
|
||||
}
|
||||
},
|
||||
],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution:'linear',
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}]
|
||||
},
|
||||
}
|
||||
|
||||
GraphTracePopUp = () => {
|
||||
console.log('state in GraphTracePopPup',this.state);
|
||||
|
||||
if (this.state.showpopUp){
|
||||
return(
|
||||
|
||||
// <div className='applicationpopup' style={{top:`${this.state.ycoordinate}px`,zIndex:10,position:'absolute',left:`${this.state.xcoordinate}px`,backgroundColor:'white',border:'1px solid grey'}}>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </div>
|
||||
// <ChartPopUpUnique>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </ChartPopUpUnique>
|
||||
|
||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
</ChartPopUpUnique>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(){
|
||||
|
||||
const ndata = this.props.data;
|
||||
// console.log("in chartJS line render function")
|
||||
// console.log(ndata);
|
||||
|
||||
// const data_charts = data.map( s => ({label:s.ts, value:parseFloat(s.val)}) );
|
||||
// if(this.chartRef.ctx)
|
||||
// {
|
||||
// var gradient = this.chartRef.ctx.createLinearGradient(0, 0, 0, 400);
|
||||
// gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
// gradient.addColorStop(1, 'rgba(250,174,50,0)');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
const data_chartJS = (canvas:any) => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
||||
return{labels: ndata.map(s => new Date(s.timestamp/1000000)), // converting from nano second to mili second
|
||||
datasets: [{
|
||||
label: 'Errors per sec',
|
||||
data: ndata.map(s => s.errorRate),
|
||||
// backgroundColor:'#000000',
|
||||
// fill: true,
|
||||
// backgroundColor: gradient,
|
||||
pointRadius: 0.5,
|
||||
borderColor: 'rgba(227, 74, 51,1)', // Can also add transparency in border color
|
||||
borderWidth: 2,
|
||||
},
|
||||
|
||||
]}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
{this.GraphTracePopUp()}
|
||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(ErrorRateChart);
|
108
frontend/src/components/metrics/GenericVisualization.tsx
Normal file
108
frontend/src/components/metrics/GenericVisualization.tsx
Normal file
@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import { Bar, Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { customMetricsItem } from '../../actions/metrics'
|
||||
|
||||
const GenVisualizationWrapper = styled.div`
|
||||
height:160px;
|
||||
`;
|
||||
|
||||
interface GenericVisualizationsProps {
|
||||
chartType: string;
|
||||
data: customMetricsItem[];
|
||||
}
|
||||
|
||||
const GenericVisualizations = (props: GenericVisualizationsProps) => {
|
||||
// const [serviceName, setServiceName] = useState('Frontend'); //default value of service name
|
||||
|
||||
const data = {
|
||||
labels: props.data.map(s => new Date(s.timestamp/1000000)),
|
||||
datasets: [{
|
||||
data: props.data.map(s => s.value),
|
||||
// label: "Africa",
|
||||
borderColor: 'rgba(250,174,50,1)',// for line chart
|
||||
backgroundColor: props.chartType==='bar'?'rgba(250,174,50,1)':'', // for bar chart, don't assign backgroundcolor if its not a bar chart, may be relevant for area graph though
|
||||
},
|
||||
// {
|
||||
// data: [282,350,411,502,635,809,947,1402,3700,5267,282,350,411,502,635,809,947,1402,3700,5267],
|
||||
// label: "Asia",
|
||||
// borderColor: 'rgba(227, 74, 51, 1.0)',
|
||||
// backgroundColor: props.chartType==='bar'?'rgba(227, 74, 51, 1.0)':'',
|
||||
// },
|
||||
//{
|
||||
// data: [168,170,178,190,203,276,408,547,675,734],
|
||||
// label: "Europe",
|
||||
// borderColor: "#3cba9f",
|
||||
// fill: false
|
||||
// }, {
|
||||
// data: [40,20,10,16,24,38,74,167,508,784],
|
||||
// label: "Latin America",
|
||||
// borderColor: "#e8c3b9",
|
||||
// fill: false
|
||||
// }, {
|
||||
// data: [6,3,2,2,7,26,82,172,312,433],
|
||||
// label: "North America",
|
||||
// borderColor: "#c45850",
|
||||
// fill: false
|
||||
// }
|
||||
]
|
||||
};
|
||||
|
||||
const options= {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
scales: {
|
||||
yAxes: [{
|
||||
gridLines: {
|
||||
drawBorder: false,
|
||||
},
|
||||
ticks: {
|
||||
display: false
|
||||
}
|
||||
}],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
//PNOTE - How to enable distribution == linear?
|
||||
// distribution: 'linear',
|
||||
//'linear': data are spread according to their time (distances can vary)
|
||||
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}],
|
||||
},
|
||||
};
|
||||
|
||||
if(props.chartType === 'line')
|
||||
{
|
||||
return (
|
||||
<GenVisualizationWrapper>
|
||||
<ChartJSLine data={data} options={options} />
|
||||
</GenVisualizationWrapper>
|
||||
);
|
||||
|
||||
} else if (props.chartType === 'bar')
|
||||
{
|
||||
return (
|
||||
<GenVisualizationWrapper>
|
||||
<Bar data={data} options={options} />
|
||||
</GenVisualizationWrapper>
|
||||
);
|
||||
}
|
||||
else
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
export default GenericVisualizations;
|
357
frontend/src/components/metrics/LatencyLineChart.tsx
Normal file
357
frontend/src/components/metrics/LatencyLineChart.tsx
Normal file
@ -0,0 +1,357 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { metricItem } from '../../actions/metrics'
|
||||
|
||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
||||
background-color:white;
|
||||
border:1px solid rgba(219,112,147,0.5);
|
||||
zIndex:10;
|
||||
position:absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
font-size:10px;
|
||||
border-radius:2px;
|
||||
`;
|
||||
|
||||
const PopUpElements = styled.p`
|
||||
color:black;
|
||||
margin-bottom:0px;
|
||||
padding-left:4px;
|
||||
padding-right:4px;
|
||||
&:hover {
|
||||
cursor:pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
// const data_charts = {
|
||||
// labels: ['1', '2', '3', '4', '5', '6'],
|
||||
// datasets: [
|
||||
// {
|
||||
// // label: '# of Votes',
|
||||
// data: [12, 19, 3, 5, 2, 3],
|
||||
// fill: false,
|
||||
// backgroundColor: 'rgb(255, 99, 132)',
|
||||
// borderColor: 'rgba(255, 99, 132, 0.2)',
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
|
||||
// const onClickHandler = (e) => {
|
||||
// console.log("ChartJS chart clicked");
|
||||
// console.log(e);
|
||||
// };
|
||||
|
||||
// export interface ApiData {
|
||||
// 0: number, // has epoch timestamp
|
||||
// 1: string, // has value of the metric as a string
|
||||
// }
|
||||
|
||||
const theme = 'dark';
|
||||
|
||||
// PNOTE - accessing history object in typescript - https://stackoverflow.com/questions/49342390/typescript-how-to-add-type-check-for-history-object-in-react
|
||||
// withRouter is used to pass on history object as prop - https://stackoverflow.com/questions/43107912/how-to-access-history-object-in-new-react-router-v4
|
||||
|
||||
|
||||
interface LatencyLineChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
// chartRef: any,
|
||||
// chartReference :ChartJSLineChart,
|
||||
// data passed to ChartJSLineChart component is an array if json objects
|
||||
}
|
||||
|
||||
interface LatencyLineChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class LatencyLineChart extends React.Component<LatencyLineChartProps>{
|
||||
|
||||
constructor(props: LatencyLineChartProps) {
|
||||
super(props);
|
||||
console.log('React CreatRef', React.createRef());
|
||||
this.chartRef = React.createRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
state = {
|
||||
// data: props.data,
|
||||
xcoordinate:0,
|
||||
ycoordinate:0,
|
||||
showpopUp:false,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
|
||||
onClickhandler = async(e:any,event:any) => {
|
||||
|
||||
console.log('e graph', e)
|
||||
// console.log('event graph',event)
|
||||
//PNOTE - e has all key values from mouse event, event has only reference to handler functions
|
||||
// PNOTE - https://github.com/emn178/angular2-chartjs/issues/29 - for listening only to element points
|
||||
// var firstPoin = this.chart.current.getElementAtEvent(e)
|
||||
|
||||
console.log('chartref',this.chartRef.current.chartInstance);
|
||||
|
||||
var firstPoint;
|
||||
if(this.chartRef){
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
console.log('firstPoint', firstPoint);
|
||||
|
||||
|
||||
// if (firstPoint) {
|
||||
// var label = myChart.data.labels[firstPoint._index];
|
||||
// var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
|
||||
// }
|
||||
if (firstPoint)
|
||||
{// PNOTE - TODO - Is await needed in this expression?
|
||||
await this.setState({
|
||||
xcoordinate:e.offsetX+20,
|
||||
ycoordinate:e.offsetY,
|
||||
showpopUp:true,
|
||||
// graphInfo:{...event}
|
||||
})
|
||||
}
|
||||
// console.log(this.state.graphInfo.payload.timestamp)
|
||||
//this.props.applicationTimeStamp(e.payload.x)
|
||||
// this.props.history.push('/traces?timestamp=' + e.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoTracesHandler=()=>{
|
||||
console.log('in gotoTraces handler')
|
||||
this.props.history.push('/traces')
|
||||
// this.props.history.push('/traces?timestamp=' + this.state.graphInfo.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoAlertsHandler=()=>{
|
||||
console.log('in gotoAlerts handler')
|
||||
this.props.history.push('/service-map')
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
// onClick: function(evt, element) {
|
||||
// // console.log(evt);
|
||||
// },
|
||||
|
||||
//- PNOTE - TO DO -- element is of type ChartElement, how to define its type
|
||||
// https://gitlab.com/signoz-frontend/sample-project/-/blob/darkthemechanges/src/Components/Application/Graphs/SimpleLineChart.js
|
||||
// Code for popup
|
||||
// onClick: function(evt, element :any[]) {
|
||||
// if (element.length > 0) {
|
||||
// var ind = element[0]._index;
|
||||
// console.log(element)
|
||||
// alert(ind);
|
||||
// }
|
||||
// },
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
responsive: true,
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Application Latency in ms',
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
padding: 8,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
|
||||
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontSize: 10,
|
||||
boxWidth : 10,
|
||||
usePointStyle : true,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
bodyFontSize: 10,
|
||||
titleFontSize: 10,
|
||||
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
// return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel!.Fixed(3);
|
||||
// not able to do toFixed(3) in typescript as string|number type is not working with toFixed(3) function for
|
||||
// as toFixed() function only works with numbers
|
||||
// using type of check gives issues in 'label' variable name
|
||||
//!That's the non-null assertion operator. It is a way to tell the compiler "this expression cannot be null or undefined here, so don't complain about the possibility of it being null or undefined." Sometimes the type checker is unable to make that determination itself.
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
stacked: false,
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
|
||||
// scaleLabel: {
|
||||
// display: true,
|
||||
// labelString: 'latency in ms',
|
||||
// fontSize: 6,
|
||||
// padding: 4,
|
||||
// },
|
||||
gridLines: {
|
||||
// You can change the color, the dash effect, the main axe color, etc.
|
||||
borderDash: [1, 4],
|
||||
color: "#D3D3D3",
|
||||
lineWidth: 0.25,
|
||||
}
|
||||
},
|
||||
],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution: 'linear',
|
||||
//'linear': data are spread according to their time (distances can vary)
|
||||
// From https://www.chartjs.org/docs/latest/axes/cartesian/time.html
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}]
|
||||
},
|
||||
}
|
||||
|
||||
GraphTracePopUp = () => {
|
||||
console.log('state in GraphTracePopPup',this.state);
|
||||
|
||||
if (this.state.showpopUp){
|
||||
return(
|
||||
|
||||
// <div className='applicationpopup' style={{top:`${this.state.ycoordinate}px`,zIndex:10,position:'absolute',left:`${this.state.xcoordinate}px`,backgroundColor:'white',border:'1px solid grey'}}>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </div>
|
||||
// <ChartPopUpUnique>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </ChartPopUpUnique>
|
||||
|
||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
</ChartPopUpUnique>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(){
|
||||
|
||||
const ndata = this.props.data;
|
||||
// console.log("in chartJS line render function")
|
||||
// console.log(ndata);
|
||||
|
||||
// const data_charts = data.map( s => ({label:s.ts, value:parseFloat(s.val)}) );
|
||||
// if(this.chartRef.ctx)
|
||||
// {
|
||||
// var gradient = this.chartRef.ctx.createLinearGradient(0, 0, 0, 400);
|
||||
// gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
// gradient.addColorStop(1, 'rgba(250,174,50,0)');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
const data_chartJS = (canvas:any) => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
||||
return{ labels: ndata.map(s => new Date(s.timestamp/1000000)),
|
||||
datasets: [{
|
||||
label: 'p99 Latency',
|
||||
data: ndata.map(s => s.p99/1000000), //converting latency from nano sec to ms
|
||||
// backgroundColor:'#000000',
|
||||
// fill: true,
|
||||
// backgroundColor: gradient,
|
||||
pointRadius: 0.5,
|
||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
||||
borderWidth: 2,
|
||||
},
|
||||
{
|
||||
label: 'p90 Latency',
|
||||
data: ndata.map(s => s.p90/1000000), //converting latency from nano sec to ms
|
||||
// backgroundColor:'#dd0000',
|
||||
// fill: true,
|
||||
// backgroundColor: 'rgba(227, 74, 51, 1.0)',
|
||||
pointRadius: 0.5,
|
||||
borderColor: 'rgba(227, 74, 51, 1.0)',
|
||||
borderWidth: 2,
|
||||
},
|
||||
{
|
||||
label: 'p50 Latency',
|
||||
data: ndata.map(s => s.p50/1000000), //converting latency from nano sec to ms
|
||||
// backgroundColor:'#dd0000',
|
||||
// fill: true,
|
||||
// backgroundColor: 'rgba(227, 74, 51, 1.0)',
|
||||
pointRadius: 0.5,
|
||||
borderColor: 'rgba(57, 255, 20, 1.0)',
|
||||
borderWidth: 2,
|
||||
},
|
||||
]}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
{this.GraphTracePopUp()}
|
||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(LatencyLineChart);
|
336
frontend/src/components/metrics/RequestRateChart.tsx
Normal file
336
frontend/src/components/metrics/RequestRateChart.tsx
Normal file
@ -0,0 +1,336 @@
|
||||
import React from 'react';
|
||||
import { Line as ChartJSLine } from 'react-chartjs-2';
|
||||
import { ChartOptions } from 'chart.js';
|
||||
import { withRouter } from "react-router";
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { metricItem } from '../../actions/metrics'
|
||||
|
||||
const ChartPopUpUnique = styled.div<{ ycoordinate: number, xcoordinate: number }>`
|
||||
background-color:white;
|
||||
border:1px solid rgba(219,112,147,0.5);
|
||||
zIndex:10;
|
||||
position:absolute;
|
||||
top:${props => props.ycoordinate}px;
|
||||
left:${props => props.xcoordinate}px;
|
||||
font-size:10px;
|
||||
border-radius:2px;
|
||||
`;
|
||||
|
||||
const PopUpElements = styled.p`
|
||||
color:black;
|
||||
margin-bottom:0px;
|
||||
padding-left:4px;
|
||||
padding-right:4px;
|
||||
&:hover {
|
||||
cursor:pointer;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
// const data_charts = {
|
||||
// labels: ['1', '2', '3', '4', '5', '6'],
|
||||
// datasets: [
|
||||
// {
|
||||
// // label: '# of Votes',
|
||||
// data: [12, 19, 3, 5, 2, 3],
|
||||
// fill: false,
|
||||
// backgroundColor: 'rgb(255, 99, 132)',
|
||||
// borderColor: 'rgba(255, 99, 132, 0.2)',
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
|
||||
// const onClickHandler = (e) => {
|
||||
// console.log("ChartJS chart clicked");
|
||||
// console.log(e);
|
||||
// };
|
||||
|
||||
// export interface ApiData {
|
||||
// 0: number, // has epoch timestamp
|
||||
// 1: string, // has value of the metric as a string
|
||||
// }
|
||||
|
||||
const theme = 'dark';
|
||||
|
||||
// PNOTE - accessing history object in typescript - https://stackoverflow.com/questions/49342390/typescript-how-to-add-type-check-for-history-object-in-react
|
||||
// withRouter is used to pass on history object as prop - https://stackoverflow.com/questions/43107912/how-to-access-history-object-in-new-react-router-v4
|
||||
|
||||
|
||||
interface RequestRateChartProps extends RouteComponentProps<any> {
|
||||
data : metricItem[],
|
||||
// chartRef: any,
|
||||
// chartReference :ChartJSLineChart,
|
||||
// data passed to ChartJSLineChart component is an array if json objects
|
||||
}
|
||||
|
||||
interface RequestRateChart {
|
||||
chartRef: any;
|
||||
}
|
||||
|
||||
|
||||
class RequestRateChart extends React.Component<RequestRateChartProps>{
|
||||
|
||||
constructor(props: RequestRateChartProps) {
|
||||
super(props);
|
||||
console.log('React CreatRef', React.createRef());
|
||||
this.chartRef = React.createRef();
|
||||
}
|
||||
|
||||
|
||||
|
||||
state = {
|
||||
// data: props.data,
|
||||
xcoordinate:0,
|
||||
ycoordinate:0,
|
||||
showpopUp:false,
|
||||
// graphInfo:{}
|
||||
}
|
||||
|
||||
|
||||
onClickhandler = async(e:any,event:any) => {
|
||||
|
||||
console.log('e graph', e)
|
||||
// console.log('event graph',event)
|
||||
//PNOTE - e has all key values from mouse event, event has only reference to handler functions
|
||||
// PNOTE - https://github.com/emn178/angular2-chartjs/issues/29 - for listening only to element points
|
||||
// var firstPoin = this.chart.current.getElementAtEvent(e)
|
||||
|
||||
console.log('chartref',this.chartRef.current.chartInstance);
|
||||
|
||||
var firstPoint;
|
||||
if(this.chartRef){
|
||||
firstPoint = this.chartRef.current.chartInstance.getElementAtEvent(e)[0];
|
||||
}
|
||||
|
||||
console.log('firstPoint', firstPoint);
|
||||
|
||||
|
||||
// if (firstPoint) {
|
||||
// var label = myChart.data.labels[firstPoint._index];
|
||||
// var value = myChart.data.datasets[firstPoint._datasetIndex].data[firstPoint._index];
|
||||
// }
|
||||
if (firstPoint)
|
||||
{// PNOTE - TODO - Is await needed in this expression?
|
||||
await this.setState({
|
||||
xcoordinate:e.offsetX+20,
|
||||
ycoordinate:e.offsetY,
|
||||
showpopUp:true,
|
||||
// graphInfo:{...event}
|
||||
})
|
||||
}
|
||||
// console.log(this.state.graphInfo.payload.timestamp)
|
||||
//this.props.applicationTimeStamp(e.payload.x)
|
||||
// this.props.history.push('/traces?timestamp=' + e.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoTracesHandler=()=>{
|
||||
console.log('in gotoTraces handler')
|
||||
this.props.history.push('/traces')
|
||||
// this.props.history.push('/traces?timestamp=' + this.state.graphInfo.payload.timestamp + '&service=' + this.props.service.name)
|
||||
}
|
||||
|
||||
gotoAlertsHandler=()=>{
|
||||
console.log('in gotoAlerts handler')
|
||||
this.props.history.push('/service-map')
|
||||
// PNOTE - Keeping service map for now, will replace with alerts when alert page is made
|
||||
}
|
||||
|
||||
options_charts: ChartOptions = {
|
||||
|
||||
// onClick: function(evt, element) {
|
||||
// // console.log(evt);
|
||||
// },
|
||||
|
||||
//- PNOTE - TO DO -- element is of type ChartElement, how to define its type
|
||||
// https://gitlab.com/signoz-frontend/sample-project/-/blob/darkthemechanges/src/Components/Application/Graphs/SimpleLineChart.js
|
||||
// Code for popup
|
||||
// onClick: function(evt, element :any[]) {
|
||||
// if (element.length > 0) {
|
||||
// var ind = element[0]._index;
|
||||
// console.log(element)
|
||||
// alert(ind);
|
||||
// }
|
||||
// },
|
||||
|
||||
onClick: this.onClickhandler,
|
||||
|
||||
maintainAspectRatio: true,
|
||||
responsive: true,
|
||||
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Request per sec',
|
||||
fontSize: 20,
|
||||
position:'top',
|
||||
padding: 2,
|
||||
fontFamily: 'Arial',
|
||||
fontStyle: 'regular',
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
|
||||
|
||||
},
|
||||
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'bottom',
|
||||
align: 'center',
|
||||
|
||||
labels: {
|
||||
fontColor:theme === 'dark'? 'rgb(200, 200, 200)':'rgb(20, 20, 20)' ,
|
||||
fontSize: 10,
|
||||
boxWidth : 10,
|
||||
usePointStyle : true,
|
||||
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
tooltips: {
|
||||
mode: 'label',
|
||||
bodyFontSize: 10,
|
||||
titleFontSize: 10,
|
||||
|
||||
callbacks: {
|
||||
label: function(tooltipItem, data) {
|
||||
|
||||
if (typeof(tooltipItem.yLabel) === 'number')
|
||||
{
|
||||
return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel.toFixed(3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return '';
|
||||
}
|
||||
// return data.datasets![tooltipItem.datasetIndex!].label +' : '+ tooltipItem.yLabel!.Fixed(3);
|
||||
// not able to do toFixed(3) in typescript as string|number type is not working with toFixed(3) function for
|
||||
// as toFixed() function only works with numbers
|
||||
// using type of check gives issues in 'label' variable name
|
||||
//!That's the non-null assertion operator. It is a way to tell the compiler "this expression cannot be null or undefined here, so don't complain about the possibility of it being null or undefined." Sometimes the type checker is unable to make that determination itself.
|
||||
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
stacked: false,
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 6,
|
||||
},
|
||||
|
||||
// scaleLabel: {
|
||||
// display: true,
|
||||
// labelString: 'latency in ms',
|
||||
// fontSize: 6,
|
||||
// padding: 4,
|
||||
// },
|
||||
gridLines: {
|
||||
// You can change the color, the dash effect, the main axe color, etc.
|
||||
borderDash: [1, 4],
|
||||
color: "#D3D3D3",
|
||||
lineWidth: 0.25,
|
||||
}
|
||||
},
|
||||
],
|
||||
xAxes: [{
|
||||
type: 'time',
|
||||
// time: {
|
||||
// unit: 'second'
|
||||
// },
|
||||
distribution:'linear',
|
||||
ticks: {
|
||||
beginAtZero: false,
|
||||
fontSize: 10,
|
||||
autoSkip: true,
|
||||
maxTicksLimit: 10,
|
||||
},
|
||||
// gridLines: false, --> not a valid option
|
||||
}]
|
||||
},
|
||||
}
|
||||
|
||||
GraphTracePopUp = () => {
|
||||
console.log('state in GraphTracePopPup',this.state);
|
||||
|
||||
if (this.state.showpopUp){
|
||||
return(
|
||||
|
||||
// <div className='applicationpopup' style={{top:`${this.state.ycoordinate}px`,zIndex:10,position:'absolute',left:`${this.state.xcoordinate}px`,backgroundColor:'white',border:'1px solid grey'}}>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </div>
|
||||
// <ChartPopUpUnique>
|
||||
// <p style={{color:'black'}} onClick={this.gotoTracesHandler}>View Traces</p>
|
||||
// <p style={{color:'black'}}> Set Alerts</p>
|
||||
// </ChartPopUpUnique>
|
||||
|
||||
<ChartPopUpUnique xcoordinate={this.state.xcoordinate} ycoordinate={this.state.ycoordinate}>
|
||||
<PopUpElements onClick={this.gotoTracesHandler}>View Traces</PopUpElements>
|
||||
<PopUpElements onClick={this.gotoAlertsHandler}>Set Alerts</PopUpElements>
|
||||
</ChartPopUpUnique>
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
render(){
|
||||
|
||||
const ndata = this.props.data;
|
||||
// console.log("in chartJS line render function")
|
||||
// console.log(ndata);
|
||||
|
||||
// const data_charts = data.map( s => ({label:s.ts, value:parseFloat(s.val)}) );
|
||||
// if(this.chartRef.ctx)
|
||||
// {
|
||||
// var gradient = this.chartRef.ctx.createLinearGradient(0, 0, 0, 400);
|
||||
// gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
// gradient.addColorStop(1, 'rgba(250,174,50,0)');
|
||||
// }
|
||||
|
||||
|
||||
|
||||
const data_chartJS = (canvas:any) => {
|
||||
const ctx = canvas.getContext("2d");
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, 100);
|
||||
gradient.addColorStop(0, 'rgba(250,174,50,1)');
|
||||
gradient.addColorStop(1, 'rgba(250,174,50,1)');
|
||||
return{labels: ndata.map(s => new Date(s.timestamp/1000000)),
|
||||
datasets: [{
|
||||
label: 'Request per sec',
|
||||
data: ndata.map(s => s.callRate),
|
||||
// backgroundColor:'#000000',
|
||||
// fill: true,
|
||||
// backgroundColor: gradient,
|
||||
pointRadius: 0.5,
|
||||
borderColor: 'rgba(250,174,50,1)', // Can also add transparency in border color
|
||||
borderWidth: 2,
|
||||
},
|
||||
|
||||
]}
|
||||
|
||||
};
|
||||
|
||||
|
||||
return(
|
||||
<div>
|
||||
{this.GraphTracePopUp()}
|
||||
<ChartJSLine ref={this.chartRef} data={data_chartJS} options={this.options_charts} />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(RequestRateChart);
|
98
frontend/src/components/metrics/ServiceMetrics.tsx
Normal file
98
frontend/src/components/metrics/ServiceMetrics.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
import React,{useEffect} from 'react';
|
||||
import { Tabs, Card, Row, Col} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
|
||||
import { getServicesMetrics, metricItem, getTopEndpoints, topEndpointListItem, GlobalTime } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import LatencyLineChart from "./LatencyLineChart"
|
||||
import RequestRateChart from './RequestRateChart'
|
||||
import ErrorRateChart from './ErrorRateChart'
|
||||
import TopEndpointsTable from './TopEndpointsTable';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
interface ServicesMetricsProps {
|
||||
serviceMetrics: metricItem[],
|
||||
getServicesMetrics: Function,
|
||||
topEndpointsList: topEndpointListItem[],
|
||||
getTopEndpoints: Function,
|
||||
globalTime: GlobalTime,
|
||||
}
|
||||
|
||||
|
||||
const _ServiceMetrics = (props: ServicesMetricsProps) => {
|
||||
|
||||
const params = useParams<{ servicename?: string; }>();
|
||||
console.log('service name',params.servicename);
|
||||
|
||||
useEffect( () => {
|
||||
props.getServicesMetrics(params.servicename,props.globalTime);
|
||||
props.getTopEndpoints(params.servicename,props.globalTime);
|
||||
}, [props.globalTime,params.servicename]);
|
||||
|
||||
return (
|
||||
<Tabs defaultActiveKey="1">
|
||||
<TabPane tab="Application Metrics" key="1">
|
||||
|
||||
<Row gutter={32} style={{ margin: 20 }}>
|
||||
<Col span={12} >
|
||||
<Card bodyStyle={{padding:10}}>
|
||||
<LatencyLineChart data={props.serviceMetrics} />
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card bodyStyle={{padding:10}}>
|
||||
<RequestRateChart data={props.serviceMetrics} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={32} style={{ margin: 20 }}>
|
||||
<Col span={12}>
|
||||
<Card bodyStyle={{padding:10}}>
|
||||
<ErrorRateChart data={props.serviceMetrics} />
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
<Col span={12}>
|
||||
<Card bodyStyle={{padding:10}}>
|
||||
<TopEndpointsTable data={props.topEndpointsList} />
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</TabPane>
|
||||
|
||||
<TabPane tab="External Calls" key="2">
|
||||
<div style={{ margin: 20 }}> Coming Soon.. </div>
|
||||
<div className="container" style={{ display: 'flex', flexFlow: 'column wrap' }}>
|
||||
|
||||
<div className='row'>
|
||||
<div className='col-md-6 col-sm-12 col-12'>
|
||||
|
||||
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
||||
</div>
|
||||
<div className='col-md-6 col-sm-12 col-12'>
|
||||
{/* <ChartJSLineChart data={this.state.graphData} /> */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { serviceMetrics: metricItem[], topEndpointsList: topEndpointListItem[],globalTime: GlobalTime} => {
|
||||
|
||||
return { serviceMetrics : state.serviceMetrics, topEndpointsList: state.topEndpointsList, globalTime:state.globalTime};
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const ServiceMetrics = connect(mapStateToProps, {
|
||||
getServicesMetrics: getServicesMetrics,
|
||||
getTopEndpoints: getTopEndpoints,
|
||||
})(_ServiceMetrics);
|
||||
|
1
frontend/src/components/metrics/ServiceMetricsDef.tsx
Normal file
1
frontend/src/components/metrics/ServiceMetricsDef.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { ServiceMetrics as default } from './ServiceMetrics';
|
99
frontend/src/components/metrics/ServicesTable.tsx
Normal file
99
frontend/src/components/metrics/ServicesTable.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import React, {useEffect} from 'react';
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { Table } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import { getServicesList, GlobalTime, servicesListItem } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
|
||||
interface ServicesTableProps {
|
||||
servicesList: servicesListItem[],
|
||||
getServicesList: Function,
|
||||
globalTime: GlobalTime,
|
||||
}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-top:40px;
|
||||
padding-bottom:40px;
|
||||
padding-left:40px;
|
||||
padding-right:40px;
|
||||
.ant-table table { font-size: 12px; };
|
||||
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; };
|
||||
`;
|
||||
|
||||
//styling antd with styled components - https://codesandbox.io/s/8x1r670rxj
|
||||
|
||||
|
||||
|
||||
|
||||
const columns = [
|
||||
|
||||
{
|
||||
title: 'Application',
|
||||
dataIndex: 'serviceName',
|
||||
key: 'serviceName',
|
||||
render: (text :string) => <NavLink style={{textTransform:'capitalize'}} to={'/application/' + text}><strong>{text}</strong></NavLink>,
|
||||
},
|
||||
{
|
||||
title: 'P99 latency (in ms)',
|
||||
dataIndex: 'p99',
|
||||
key: 'p99',
|
||||
sorter: (a:any, b:any) => a.p99 - b.p99,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value/1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Error Rate (in %)',
|
||||
dataIndex: 'errorRate',
|
||||
key: 'errorRate',
|
||||
sorter: (a:any, b:any) => a.errorRate - b.errorRate,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value*100).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Requests Per Second',
|
||||
dataIndex: 'callRate',
|
||||
key: 'callRate',
|
||||
sorter: (a:any, b:any) => a.callRate - b.callRate,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => value.toFixed(2),
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
|
||||
const _ServicesTable = (props: ServicesTableProps) => {
|
||||
|
||||
const search = useLocation().search;
|
||||
const time_interval = new URLSearchParams(search).get('time');
|
||||
console.log(time_interval)
|
||||
|
||||
useEffect( () => {
|
||||
props.getServicesList(props.globalTime);
|
||||
}, [props.globalTime]);
|
||||
|
||||
|
||||
return(
|
||||
|
||||
<Wrapper>
|
||||
{console.log(props.servicesList)}
|
||||
<Table dataSource={props.servicesList} columns={columns} pagination={false} />
|
||||
</Wrapper>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { servicesList: servicesListItem[], globalTime: GlobalTime } => {
|
||||
// console.log(state);
|
||||
return { servicesList : state.servicesList, globalTime:state.globalTime};
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const ServicesTable = connect(mapStateToProps, {
|
||||
getServicesList: getServicesList,
|
||||
})(_ServicesTable);
|
1
frontend/src/components/metrics/ServicesTableDef.tsx
Normal file
1
frontend/src/components/metrics/ServicesTableDef.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { ServicesTable as default } from './ServicesTable';
|
81
frontend/src/components/metrics/TopEndpointsTable.tsx
Normal file
81
frontend/src/components/metrics/TopEndpointsTable.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { Table } from 'antd'
|
||||
import styled from 'styled-components';
|
||||
import { topEndpointListItem } from '../../actions/metrics';
|
||||
|
||||
|
||||
|
||||
const Wrapper = styled.div`
|
||||
padding-top:10px;
|
||||
padding-bottom:10px;
|
||||
padding-left:20px;
|
||||
padding-right:20px;
|
||||
.ant-table table { font-size: 12px; };
|
||||
.ant-table tfoot>tr>td, .ant-table tfoot>tr>th, .ant-table-tbody>tr>td, .ant-table-thead>tr>th { padding: 10px; };
|
||||
`;
|
||||
|
||||
interface TopEndpointsTableProps {
|
||||
data : topEndpointListItem[],
|
||||
}
|
||||
|
||||
const TopEndpointsTable = (props: TopEndpointsTableProps) => {
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
// sorter: (a:any, b:any) => a.startTime - b.startTime,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
//PNOTE - TO DO - Change this to API link if available
|
||||
render: (text :string) => <NavLink to={'/' + text}>{text}</NavLink>,
|
||||
|
||||
},
|
||||
{
|
||||
title: 'P50 (in ms)',
|
||||
dataIndex: 'p50',
|
||||
key: 'p50',
|
||||
sorter: (a:any, b:any) => a.p50 - b.p50,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value/1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'P90 (in ms)',
|
||||
dataIndex: 'p90',
|
||||
key: 'p90',
|
||||
sorter: (a:any, b:any) => a.p90 - b.p90,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value/1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'P99 (in ms)',
|
||||
dataIndex: 'p99',
|
||||
key: 'p99',
|
||||
sorter: (a:any, b:any) => a.p99 - b.p99,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value/1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Number of Calls',
|
||||
dataIndex: 'numCalls',
|
||||
key: 'numCalls',
|
||||
sorter: (a:any, b:any) => a.numCalls - b.numCalls,
|
||||
// sortDirections: ['descend', 'ascend'],
|
||||
// render: (value: number) => value.toFixed(2),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
|
||||
return(
|
||||
<Wrapper>
|
||||
<h6> Top Endpoints</h6>
|
||||
<Table dataSource={props.data} columns={columns} pagination={false} />
|
||||
</Wrapper>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default TopEndpointsTable;
|
67
frontend/src/components/servicemap/ServiceGraph.tsx
Normal file
67
frontend/src/components/servicemap/ServiceGraph.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
// import {useState} from "react";
|
||||
import Graph from "react-graph-vis";
|
||||
// import { graphEvents } from "react-graph-vis";
|
||||
|
||||
//PNOTE - types of react-graph-vis defined in typings folder.
|
||||
//How is it imported directly?
|
||||
// type definition for service graph - https://github.com/crubier/react-graph-vis/issues/80
|
||||
|
||||
// Set shapes - https://visjs.github.io/vis-network/docs/network/nodes.html#
|
||||
// https://github.com/crubier/react-graph-vis/issues/93
|
||||
const graph = {
|
||||
nodes: [
|
||||
{ id: 1, label: "Catalogue", shape: "box", color: "green",border: "black",size: 100 },
|
||||
{ id: 2, label: "Users", shape: "box", color: "#FFFF00" },
|
||||
{ id: 3, label: "Payment App", shape: "box", color: "#FB7E81" },
|
||||
{ id: 4, label: "My Sql", shape: "box", size: 10, color: "#7BE141" },
|
||||
{ id: 5, label: "Redis-db", shape: "box", color: "#6E6EFD" },
|
||||
],
|
||||
edges: [
|
||||
{from:1,to:2,color: { color: "red" },size:{size:20}},
|
||||
{from:2,to:3,color: { color: "red" }},
|
||||
{from:1,to:3,color: { color: "red" }},
|
||||
{from:3,to:4,color: { color: "red" }},
|
||||
{from:3,to:5,color: { color: "red" }},
|
||||
]
|
||||
};
|
||||
|
||||
const options = {
|
||||
layout: {
|
||||
hierarchical: true
|
||||
},
|
||||
edges: {
|
||||
color: "#000000"
|
||||
},
|
||||
height: "500px"
|
||||
};
|
||||
|
||||
// const events = {
|
||||
// select: function(event:any) { //PNOTE - TO DO - Get rid of any type
|
||||
// var { nodes, edges } = event;
|
||||
// }
|
||||
// };
|
||||
|
||||
const ServiceGraph = () => {
|
||||
|
||||
// const [network, setNetwork] = useState(null);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div> Service Graph Component</div>
|
||||
<Graph
|
||||
graph={graph}
|
||||
options={options}
|
||||
// events={events}
|
||||
// getNetwork={network => {
|
||||
// // if you want access to vis.js network api you can set the state in a parent component using this property
|
||||
// }}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default ServiceGraph;
|
19
frontend/src/components/servicemap/ServiceMap.tsx
Normal file
19
frontend/src/components/servicemap/ServiceMap.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import ServiceGraph from './ServiceGraph'
|
||||
|
||||
const ServiceMap = () => {
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
{/* <div> Service Map </div> */}
|
||||
<ServiceGraph />
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default ServiceMap;
|
80
frontend/src/components/traces/FilterStateDisplay.tsx
Normal file
80
frontend/src/components/traces/FilterStateDisplay.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { Card, Tag } from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
|
||||
import { StoreState } from '../../reducers'
|
||||
import { TagItem, TraceFilters, updateTraceFilters } from '../../actions';
|
||||
|
||||
interface FilterStateDisplayProps {
|
||||
traceFilters: TraceFilters,
|
||||
updateTraceFilters: Function,
|
||||
|
||||
}
|
||||
|
||||
const _FilterStateDisplay = (props: FilterStateDisplayProps) => {
|
||||
|
||||
function handleCloseTag(value:string) {
|
||||
console.log('on close tag', value)
|
||||
if (value==='service')
|
||||
props.updateTraceFilters({...props.traceFilters,service:''})
|
||||
if (value==='operation')
|
||||
props.updateTraceFilters({...props.traceFilters,operation:''})
|
||||
if (value==='maxLatency')
|
||||
props.updateTraceFilters({...props.traceFilters,latency:{'max':'','min':props.traceFilters.latency?.min}})
|
||||
if (value==='minLatency')
|
||||
props.updateTraceFilters({...props.traceFilters,latency:{'min':'','max':props.traceFilters.latency?.max}})
|
||||
|
||||
}
|
||||
|
||||
function handleCloseTagElement(item:TagItem){
|
||||
console.log('tag item closed in handle closeTagElement', item)
|
||||
props.updateTraceFilters({...props.traceFilters,tags:props.traceFilters.tags?.filter(elem => elem !== item)})
|
||||
|
||||
}
|
||||
return(
|
||||
|
||||
<Card style={{padding: 6, marginTop: 10, marginBottom: 10}} bodyStyle={{padding: 6}}>
|
||||
|
||||
{(props.traceFilters.service===''||props.traceFilters.operation===undefined)? null:
|
||||
<Tag style={{fontSize:14, padding: 8}} closable
|
||||
onClose={e => {handleCloseTag('service');}}>
|
||||
service:{props.traceFilters.service}
|
||||
</Tag> }
|
||||
{(props.traceFilters.operation===''||props.traceFilters.operation===undefined)? null:
|
||||
<Tag style={{fontSize:14, padding: 8}} closable
|
||||
onClose={e => {handleCloseTag('operation');}}>
|
||||
operation:{props.traceFilters.operation}
|
||||
</Tag> }
|
||||
{props.traceFilters.latency===undefined||props.traceFilters.latency?.min===''? null:
|
||||
<Tag style={{fontSize:14, padding: 8}} closable
|
||||
onClose={e => {handleCloseTag('minLatency');}}>
|
||||
minLatency:{(parseInt(props.traceFilters.latency!.min)/1000000).toString()}ms
|
||||
</Tag> }
|
||||
{props.traceFilters.latency===undefined||props.traceFilters.latency?.max===''? null:
|
||||
<Tag style={{fontSize:14, padding: 8}} closable
|
||||
onClose={e => {handleCloseTag('maxLatency');}}>
|
||||
maxLatency:{(parseInt(props.traceFilters.latency!.max)/1000000).toString()}ms
|
||||
</Tag> }
|
||||
{console.log('tagfilters before showing on card',props.traceFilters.tags)}
|
||||
{props.traceFilters.tags === undefined? null: props.traceFilters.tags.map( item => (
|
||||
<Tag style={{fontSize:14, padding: 8}} closable
|
||||
onClose={e => {handleCloseTagElement(item);}}>
|
||||
{item.key} {item.operator} {item.value}
|
||||
</Tag>))}
|
||||
</Card>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters } => {
|
||||
return { traceFilters : state.traceFilters };
|
||||
};
|
||||
|
||||
|
||||
export const FilterStateDisplay = connect(mapStateToProps,
|
||||
{
|
||||
updateTraceFilters: updateTraceFilters,
|
||||
|
||||
})(_FilterStateDisplay);
|
||||
|
72
frontend/src/components/traces/LatencyModalForm.tsx
Normal file
72
frontend/src/components/traces/LatencyModalForm.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import { Modal, Form, InputNumber, Col, Row} from 'antd';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
|
||||
// interface Values {
|
||||
// title: string;
|
||||
// description: string;
|
||||
// modifier: string;
|
||||
// }
|
||||
|
||||
interface LatencyModalFormProps {
|
||||
visible: boolean;
|
||||
onCreate: (values: Store) => void; //Store is defined in antd forms library
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const LatencyModalForm: React.FC<LatencyModalFormProps> = ({
|
||||
visible,
|
||||
onCreate,
|
||||
onCancel,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title="Chose min and max values of Latency"
|
||||
okText="Apply"
|
||||
cancelText="Cancel"
|
||||
onCancel={onCancel}
|
||||
onOk={() => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(values => {
|
||||
form.resetFields();
|
||||
// onCreate({title:"hello",description:'good',modifier:'public'});
|
||||
onCreate(values); // giving error for values
|
||||
})
|
||||
.catch(info => {
|
||||
console.log('Validate Failed:', info);
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
name="form_in_modal"
|
||||
initialValues={{ min: '100', max:'500' }}
|
||||
>
|
||||
<Row>
|
||||
{/* <Input.Group compact> */}
|
||||
<Col span={12}>
|
||||
<Form.Item
|
||||
name="min"
|
||||
label="Min (in ms)"
|
||||
// rules={[{ required: true, message: 'Please input the title of collection!' }]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="max" label="Max (in ms)">
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
{/* </Input.Group> */}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default LatencyModalForm;
|
47
frontend/src/components/traces/SelectedSpanDetails.tsx
Normal file
47
frontend/src/components/traces/SelectedSpanDetails.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
import {Card,Tabs} from 'antd';
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
|
||||
|
||||
interface spanTagItem {
|
||||
key:string;
|
||||
type:string;
|
||||
value:string;
|
||||
}
|
||||
|
||||
interface SelectedSpanDetailsProps {
|
||||
clickedSpanTags: spanTagItem[]
|
||||
}
|
||||
|
||||
const SelectedSpanDetails = (props: SelectedSpanDetailsProps) => {
|
||||
|
||||
const callback = (key:any) => {
|
||||
console.log(key);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<Card style={{ height: 320 }} >
|
||||
<Tabs defaultActiveKey="1" onChange={callback}>
|
||||
|
||||
<TabPane tab="Tags" key="1">
|
||||
<strong> Details for selected Span </strong>
|
||||
{props.clickedSpanTags.map((tags,index)=><li key={index} style={{color:'grey',fontSize:'13px',listStyle:'none'}}><span className='mr-1'>{tags.key}</span>:<span className='ml-1'>{tags.key==='error'?"true":tags.value}</span></li>)} </TabPane>
|
||||
<TabPane tab="Errors" key="2">
|
||||
{props.clickedSpanTags.filter(tags=>tags.key==='error').map(
|
||||
error => <div className='ml-5'>
|
||||
<p style={{color:'grey',fontSize:'10px'}}>
|
||||
<span className='mr-1'>{error.key}</span>:
|
||||
<span className='ml-1'>true</span></p>
|
||||
</div>)}
|
||||
</TabPane>
|
||||
|
||||
</Tabs>
|
||||
</Card>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default SelectedSpanDetails;
|
252
frontend/src/components/traces/TraceCustomVisualizations.tsx
Normal file
252
frontend/src/components/traces/TraceCustomVisualizations.tsx
Normal file
@ -0,0 +1,252 @@
|
||||
import React, {useState,useEffect} from 'react';
|
||||
import GenericVisualizations from '../metrics/GenericVisualization'
|
||||
import {Select, Card, Space, Form} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { StoreState } from '../../reducers'
|
||||
import {customMetricsItem, getFilteredTraceMetrics, GlobalTime, TraceFilters} from '../../actions';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const entity = [
|
||||
{
|
||||
title: 'Calls',
|
||||
key:'calls',
|
||||
dataindex:'calls'
|
||||
},
|
||||
{
|
||||
title: 'Duration',
|
||||
key:'duration',
|
||||
dataindex:'duration'
|
||||
},
|
||||
{
|
||||
title: 'Error',
|
||||
key:'error',
|
||||
dataindex:'error'
|
||||
},
|
||||
{
|
||||
title: 'Status Code',
|
||||
key:'status_code',
|
||||
dataindex:'status_code'
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
const aggregation_options = [
|
||||
{
|
||||
linked_entity: 'calls',
|
||||
default_selected:{title:'Count', dataindex:'count'},
|
||||
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}]
|
||||
},
|
||||
{
|
||||
linked_entity: 'duration',
|
||||
default_selected:{title:'p99', dataindex:'p99'},
|
||||
// options_available: [ {title:'Avg', dataindex:'avg'}, {title:'Max', dataindex:'max'},{title:'Min', dataindex:'min'}, {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p95', dataindex:'p95'}]
|
||||
options_available: [ {title:'p50', dataindex:'p50'},{title:'p90', dataindex:'p90'}, {title:'p99', dataindex:'p99'}]
|
||||
},
|
||||
{
|
||||
linked_entity: 'error',
|
||||
default_selected:{title:'Count', dataindex:'count'},
|
||||
options_available: [ {title:'Count', dataindex:'count'}, {title:'Rate (per sec)', dataindex:'rate_per_sec'}]
|
||||
},
|
||||
{
|
||||
linked_entity: 'status_code',
|
||||
default_selected: {title:'Count', dataindex:'count'},
|
||||
options_available: [ {title:'Count', dataindex:'count'}]
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
interface TraceCustomVisualizationsProps {
|
||||
filteredTraceMetrics: customMetricsItem[],
|
||||
globalTime: GlobalTime,
|
||||
getFilteredTraceMetrics: Function,
|
||||
traceFilters: TraceFilters,
|
||||
}
|
||||
|
||||
const _TraceCustomVisualizations = (props: TraceCustomVisualizationsProps) => {
|
||||
|
||||
const [selectedEntity, setSelectedEntity] = useState('calls');
|
||||
// const [defaultOption, setDefaultOption]=useState('count');
|
||||
const [selectedAggOption, setSelectedAggOption] = useState('count');
|
||||
const [selectedStep, setSelectedStep] = useState('60');
|
||||
// Step should be multiples of 60, 60 -> 1 min
|
||||
|
||||
|
||||
useEffect( () => {
|
||||
|
||||
let request_string= 'service='+props.traceFilters.service+
|
||||
'&operation='+props.traceFilters.operation+
|
||||
'&maxDuration='+props.traceFilters.latency?.max+
|
||||
'&minDuration='+props.traceFilters.latency?.min
|
||||
if(props.traceFilters.tags)
|
||||
request_string=request_string+'&tags='+encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||
if(selectedEntity)
|
||||
request_string=request_string+'&dimension='+selectedEntity.toLowerCase();
|
||||
if(selectedAggOption)
|
||||
request_string=request_string+'&aggregation_option='+selectedAggOption.toLowerCase();
|
||||
if(selectedStep)
|
||||
request_string=request_string+'&step='+selectedStep;
|
||||
|
||||
|
||||
props.getFilteredTraceMetrics(request_string, props.globalTime)
|
||||
|
||||
}, [selectedEntity,selectedAggOption,props.traceFilters, props.globalTime ]);
|
||||
|
||||
//Custom metrics API called if time, tracefilters, selected entity or agg option changes
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
function handleChange(value:string) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
// function handleChangeEntity(value:string) {
|
||||
// // console.log(value);
|
||||
// // setSelectedEntity(value);
|
||||
// setDefaultOption(aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].default_selected)
|
||||
// }
|
||||
|
||||
function handleFinish(value:string) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
|
||||
// PNOTE - Can also use 'coordinate' option in antd Select for implementing this - https://ant.design/components/select/
|
||||
const handleFormValuesChange = (changedValues:any) => {
|
||||
const formFieldName = Object.keys(changedValues)[0];
|
||||
// console.log('Object keys',Object.keys(changedValues) );
|
||||
if (formFieldName === 'entity') {
|
||||
|
||||
|
||||
// const a = selectedEntity; // why is selected entity not set instantly??
|
||||
|
||||
// setDefaultOption(aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].default_selected)
|
||||
const temp_entity = aggregation_options.filter((item) => item.linked_entity === changedValues[formFieldName])[0];
|
||||
|
||||
form.setFieldsValue( {
|
||||
// agg_options : aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].default_selected,
|
||||
agg_options : temp_entity.default_selected.title,
|
||||
// PNOTE - TO DO Check if this has the same behaviour as selecting an option?
|
||||
})
|
||||
|
||||
let temp = form.getFieldsValue(['agg_options','entity']);
|
||||
console.log('custom metric field values',temp,temp.entity,temp.agg_options);
|
||||
|
||||
setSelectedEntity(temp.entity);
|
||||
setSelectedAggOption(temp.agg_options);
|
||||
//form.setFieldsValue({ agg_options: aggregation_options.filter( item => item.linked_entity === selectedEntity )[0] }); //reset product selection
|
||||
// PNOTE - https://stackoverflow.com/questions/64377293/update-select-option-list-based-on-other-select-field-selection-ant-design
|
||||
|
||||
}
|
||||
|
||||
if (formFieldName === 'agg_options') {
|
||||
|
||||
setSelectedAggOption(changedValues[formFieldName]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Make api calls here and display data
|
||||
// PNOTE - TO DO - API CALL location - may be through action creators and set states in redux store?
|
||||
|
||||
// PNOTE - Change this
|
||||
// API call via useEffects after monitoring traces
|
||||
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<Card>
|
||||
{/* <Space direction="vertical"> */}
|
||||
<div>Custom Visualizations</div>
|
||||
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
onValuesChange={handleFormValuesChange}
|
||||
// initialValues={ agg_options: 'Count'}
|
||||
initialValues={{ agg_options: 'Count', chart_style:'line', interval:'5m', group_by:'none' }}
|
||||
>
|
||||
<Space>
|
||||
<Form.Item name="entity">
|
||||
{/* <Select defaultValue={selectedEntity} style={{ width: 120 }} onChange={handleChangeEntity} allowClear> */}
|
||||
|
||||
<Select defaultValue={selectedEntity} style={{ width: 120 }} allowClear>
|
||||
{entity.map((item) => (
|
||||
<Option key={item.key} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
)
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="agg_options">
|
||||
|
||||
<Select style={{ width: 120 }} allowClear>
|
||||
{ aggregation_options.filter((item) => item.linked_entity === selectedEntity)[0].options_available
|
||||
.map((item) => (
|
||||
<Option key={item.dataindex} value={item.dataindex}>
|
||||
{item.title}
|
||||
</Option>
|
||||
))
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{/* <Select defaultValue="count" style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||
<Option value="count">Count</Option>
|
||||
<Option value="sume">Sum</Option>
|
||||
<Option value="rate">Rate</Option>
|
||||
</Select> */}
|
||||
<Form.Item name="chart_style">
|
||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||
<Option value="line">Line Chart</Option>
|
||||
<Option value="bar">Bar Chart</Option>
|
||||
<Option value="area">Area Chart</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="interval">
|
||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||
<Option value="1m">1 min</Option>
|
||||
<Option value="5m">5 min</Option>
|
||||
<Option value="30m">30 min</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
{/* Need heading for each option */}
|
||||
<Form.Item name="group_by">
|
||||
<Select style={{ width: 120 }} onChange={handleChange} allowClear>
|
||||
<Option value="none">Group By</Option>
|
||||
<Option value="status">Status Code</Option>
|
||||
<Option value="protocol">Protocol</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form>
|
||||
|
||||
|
||||
<GenericVisualizations chartType='line' data={props.filteredTraceMetrics}/>
|
||||
{/* This component should take bar or line as an input */}
|
||||
|
||||
</Card>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { filteredTraceMetrics: customMetricsItem[] , globalTime: GlobalTime, traceFilters: TraceFilters} => {
|
||||
// console.log(state);
|
||||
return { filteredTraceMetrics : state.filteredTraceMetrics, globalTime: state.globalTime,traceFilters:state.traceFilters };
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const TraceCustomVisualizations = connect(mapStateToProps, {
|
||||
getFilteredTraceMetrics: getFilteredTraceMetrics,
|
||||
})(_TraceCustomVisualizations);
|
||||
|
||||
// export default TraceCustomVisualizations;
|
25
frontend/src/components/traces/TraceDetail.tsx
Normal file
25
frontend/src/components/traces/TraceDetail.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import {TraceCustomVisualizations} from './TraceCustomVisualizations';
|
||||
import { TraceFilter } from './TraceFilter';
|
||||
import { TraceList } from './TraceList';
|
||||
|
||||
|
||||
|
||||
|
||||
const TraceDetail = () => {
|
||||
// const [serviceName, setServiceName] = useState('Frontend'); //default value of service name
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
{/* <div>Tracing Detail Page</div> */}
|
||||
<TraceFilter />
|
||||
{/* <TraceFilter servicename={serviceName} /> */}
|
||||
<TraceCustomVisualizations />
|
||||
<TraceList />
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default TraceDetail;
|
339
frontend/src/components/traces/TraceFilter.tsx
Normal file
339
frontend/src/components/traces/TraceFilter.tsx
Normal file
@ -0,0 +1,339 @@
|
||||
import React,{useEffect, useState} from 'react';
|
||||
import { Select, Button, Input, Tag, Card, Form, AutoComplete} from 'antd';
|
||||
import { connect } from 'react-redux';
|
||||
import { Store } from 'antd/lib/form/interface';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { updateTraceFilters, fetchTraces, TraceFilters, GlobalTime } from '../../actions';
|
||||
import { StoreState } from '../../reducers';
|
||||
import LatencyModalForm from './LatencyModalForm';
|
||||
import {FilterStateDisplay} from './FilterStateDisplay';
|
||||
|
||||
|
||||
import FormItem from 'antd/lib/form/FormItem';
|
||||
import metricsAPI from '../../api/metricsAPI';
|
||||
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
const InfoWrapper = styled.div`
|
||||
padding-top:10px;
|
||||
font-style:italic;
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
interface TraceFilterProps {
|
||||
traceFilters: TraceFilters,
|
||||
globalTime: GlobalTime,
|
||||
updateTraceFilters: Function,
|
||||
fetchTraces: Function,
|
||||
}
|
||||
|
||||
interface TagKeyOptionItem {
|
||||
"tagKeys": string;
|
||||
"tagCount": number;
|
||||
}
|
||||
|
||||
const _TraceFilter = (props: TraceFilterProps) => {
|
||||
|
||||
const [serviceList, setServiceList] = useState<string[]>([]);
|
||||
const [operationList, setOperationsList] = useState<string[]>([]);
|
||||
const [tagKeyOptions, setTagKeyOptions] = useState<TagKeyOptionItem[]>([]);
|
||||
|
||||
|
||||
useEffect( () => {
|
||||
metricsAPI.get<string[]>('services/list').then(response => {
|
||||
// console.log(response.data);
|
||||
setServiceList( response.data );
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect( () => {
|
||||
let request_string='service='+props.traceFilters.service+
|
||||
'&operation='+props.traceFilters.operation+
|
||||
'&maxDuration='+props.traceFilters.latency?.max+
|
||||
'&minDuration='+props.traceFilters.latency?.min
|
||||
if(props.traceFilters.tags)
|
||||
request_string=request_string+'&tags='+encodeURIComponent(JSON.stringify(props.traceFilters.tags));
|
||||
|
||||
props.fetchTraces(props.globalTime, request_string)
|
||||
console.log('stringified traceFilters redux state',encodeURIComponent(JSON.stringify(props.traceFilters.tags)));
|
||||
}, [props.traceFilters,props.globalTime]);
|
||||
|
||||
|
||||
// useEffect( () => {
|
||||
|
||||
// tagKeyOptions.map(s => options.push({'value':s.tagKeys}));
|
||||
// console.log('tagoptions USeEffect',options)
|
||||
// }, [tagKeyOptions]);
|
||||
|
||||
// Use effects run in the order they are specified
|
||||
|
||||
useEffect ( () => {
|
||||
|
||||
let latencyButtonText = 'Latency';
|
||||
if (props.traceFilters.latency?.min === '' && props.traceFilters.latency?.max !== '')
|
||||
latencyButtonText = 'Latency<'+(parseInt(props.traceFilters.latency?.max)/1000000).toString()+'ms';
|
||||
else if (props.traceFilters.latency?.min !== '' && props.traceFilters.latency?.max === '')
|
||||
latencyButtonText = 'Latency>'+(parseInt(props.traceFilters.latency?.min)/1000000).toString()+'ms';
|
||||
else if ( props.traceFilters.latency !== undefined && props.traceFilters.latency?.min !== '' && props.traceFilters.latency?.max !== '')
|
||||
latencyButtonText = (parseInt(props.traceFilters.latency.min)/1000000).toString()+'ms <Latency<'+(parseInt(props.traceFilters.latency.max)/1000000).toString()+'ms';
|
||||
|
||||
|
||||
form_basefilter.setFieldsValue({latency:latencyButtonText ,})
|
||||
|
||||
}, [props.traceFilters.latency])
|
||||
|
||||
useEffect ( () => {
|
||||
|
||||
form_basefilter.setFieldsValue({service: props.traceFilters.service,})
|
||||
|
||||
}, [props.traceFilters.service])
|
||||
|
||||
useEffect ( () => {
|
||||
|
||||
form_basefilter.setFieldsValue({operation: props.traceFilters.operation,})
|
||||
|
||||
}, [props.traceFilters.operation])
|
||||
|
||||
const [modalVisible, setModalVisible] = useState(false);
|
||||
const [loading] = useState(false);
|
||||
|
||||
const [tagKeyValueApplied, setTagKeyValueApplied]=useState(['']);
|
||||
const [latencyFilterValues, setLatencyFilterValues]=useState({min:'',max:''})
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [form_basefilter] = Form.useForm();
|
||||
|
||||
function handleChange(value:string) {
|
||||
console.log(value);
|
||||
}
|
||||
|
||||
function handleChangeOperation(value:string) {
|
||||
props.updateTraceFilters({...props.traceFilters,operation:value})
|
||||
}
|
||||
|
||||
function handleChangeService(value:string) {
|
||||
console.log(value);
|
||||
let service_request='/service/'+value+'/operations';
|
||||
metricsAPI.get<string[]>(service_request).then(response => {
|
||||
console.log('operations',response.data);
|
||||
// form_basefilter.resetFields(['operation',])
|
||||
setOperationsList( response.data );
|
||||
});
|
||||
|
||||
let tagkeyoptions_request='tags?service='+value;
|
||||
metricsAPI.get<TagKeyOptionItem[]>(tagkeyoptions_request).then(response => {
|
||||
console.log('tag key options',response.data);
|
||||
setTagKeyOptions( response.data );
|
||||
});
|
||||
|
||||
props.updateTraceFilters({...props.traceFilters,service:value})
|
||||
|
||||
}
|
||||
|
||||
const onLatencyButtonClick = () => {
|
||||
setModalVisible(true);
|
||||
}
|
||||
|
||||
|
||||
const onLatencyModalApply = (values: Store) => {
|
||||
setModalVisible(false);
|
||||
console.log('Received values of form: ', values);
|
||||
//Latency modal form returns null if the value entered is not a number or string etc or empty
|
||||
props.updateTraceFilters({...props.traceFilters,latency:{min:values.min?(parseInt(values.min)*1000000).toString():"", max:values.max?(parseInt(values.max)*1000000).toString():""}})
|
||||
// setLatencyFilterValues()
|
||||
}
|
||||
|
||||
const onTagFormSubmit = (values:any) => {
|
||||
console.log(values);
|
||||
|
||||
// setTagKeyValueApplied(tagKeyValueApplied => [...tagKeyValueApplied, values.tag_key+' '+values.operator+' '+values.tag_value]);
|
||||
// making API calls with only tags data for testing, for last tagform submit
|
||||
// ideally all tags including service, operations, tags, latency selected should be in one state and used
|
||||
// to make API call and update trace list
|
||||
let request_tags= 'service=frontend&tags='+encodeURIComponent(JSON.stringify([{"key":values.tag_key,"value":values.tag_value,"operator":values.operator}]))
|
||||
// props.fetchTraces(request_tags)
|
||||
|
||||
// form field names are tag_key & tag_value
|
||||
// data structure has key, value & operator
|
||||
|
||||
if (props.traceFilters.tags){ // If there are existing tag filters present
|
||||
props.updateTraceFilters(
|
||||
{
|
||||
service:props.traceFilters.service,
|
||||
operation:props.traceFilters.operation,
|
||||
latency:props.traceFilters.latency,
|
||||
tags:[...props.traceFilters.tags, {'key':values.tag_key,'value':values.tag_value,'operator':values.operator}]
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
props.updateTraceFilters(
|
||||
{
|
||||
service:props.traceFilters.service,
|
||||
operation:props.traceFilters.operation,
|
||||
latency:props.traceFilters.latency,
|
||||
tags:[ {'key':values.tag_key,'value':values.tag_value,'operator':values.operator}]
|
||||
});
|
||||
}
|
||||
|
||||
form.resetFields();
|
||||
}
|
||||
|
||||
const onTagClose = (value:string) => {
|
||||
console.log(value);
|
||||
// setJoinList(joinList.filter((e)=>(e !== name)))
|
||||
// removing closed tag from the tagKeyValueApplied array
|
||||
setTagKeyValueApplied(tagKeyValueApplied.filter( e => (e !== value)));
|
||||
|
||||
}
|
||||
|
||||
// For autocomplete
|
||||
//Setting value when autocomplete field is changed
|
||||
const onChangeTagKey = (data: string) => {
|
||||
form.setFieldsValue({ tag_key: data });
|
||||
};
|
||||
|
||||
const dataSource = ['status:200'];
|
||||
const children = [];
|
||||
for (let i = 0; i < dataSource.length; i++) {
|
||||
children.push(<Option value={dataSource[i]} key={dataSource[i]}>{dataSource[i]}</Option>);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// PNOTE - Remove any
|
||||
const handleApplyFilterForm = (values:any) => {
|
||||
console.log('values are', values);
|
||||
console.log(typeof(values.service))
|
||||
console.log(typeof(values.operation))
|
||||
let request_params: string ='';
|
||||
if (typeof values.service !== undefined && typeof(values.operation) !== undefined)
|
||||
{
|
||||
request_params = 'service='+values.service+'&operation='+values.operation;
|
||||
}
|
||||
else if (typeof values.service === undefined && typeof values.operation !== undefined)
|
||||
{
|
||||
request_params = 'operation='+values.operation;
|
||||
}
|
||||
else if (typeof values.service !== undefined && typeof values.operation === undefined)
|
||||
{
|
||||
request_params = 'service='+values.service;
|
||||
}
|
||||
|
||||
request_params=request_params+'&minDuration='+latencyFilterValues.min+'&maxDuration='+latencyFilterValues.max;
|
||||
console.log(request_params);
|
||||
|
||||
// props.fetchTraces(request_params)
|
||||
|
||||
// console.log(props.inputTag)
|
||||
// props.updateTagFilters([{key:props.inputTag, value: props.inputTag }]);
|
||||
setTagKeyValueApplied(tagKeyValueApplied => [...tagKeyValueApplied, 'service eq'+values.service, 'operation eq '+values.operation, 'maxduration eq '+ (parseInt(latencyFilterValues.max)/1000000).toString(), 'minduration eq '+(parseInt(latencyFilterValues.min)/1000000).toString()]);
|
||||
props.updateTraceFilters({'service':values.service,'operation':values.operation,'latency':latencyFilterValues})
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>Filter Traces</div>
|
||||
<div>{JSON.stringify(props.traceFilters)}</div>
|
||||
|
||||
<Form form={form_basefilter} layout='inline' onFinish={handleApplyFilterForm} initialValues={{ service:'', operation:'',latency:'Latency',}} style={{marginTop: 10, marginBottom:10}}>
|
||||
<FormItem rules={[{ required: true }]} name='service'>
|
||||
<Select showSearch style={{ width: 180 }} onChange={handleChangeService} placeholder='Select Service' allowClear>
|
||||
{serviceList.map( s => <Option value={s}>{s}</Option>)}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name='operation'>
|
||||
<Select showSearch style={{ width: 180 }} onChange={handleChangeOperation} placeholder='Select Operation' allowClear>
|
||||
{operationList.map( item => <Option value={item}>{item}</Option>)}
|
||||
{/* We need to URL encode before making API call on form submission */}
|
||||
{/* <Option value="HTTP%20GET">HTTP GET</Option>
|
||||
<Option value="HTTP%20GET%20%2Fdispatch">HTTP GET /dispatch</Option>
|
||||
<Option value="HTTP%20GET%3A%20%2Froute">HTTP GET: /route</Option>
|
||||
<Option value="%2Fdriver.DriverService%2FFindNearest">/driver.DriverService/FindNearest</Option>
|
||||
<Option value="HTTP%20GET%20%2Fcustomer">HTTP GET /customer</Option> */}
|
||||
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name='latency'>
|
||||
<Input style={{ width: 200 }} type='button' onClick={onLatencyButtonClick}/>
|
||||
</FormItem>
|
||||
|
||||
{/* <FormItem>
|
||||
<Button type="primary" htmlType="submit">Apply Filters</Button>
|
||||
</FormItem> */}
|
||||
</Form>
|
||||
|
||||
<FilterStateDisplay />
|
||||
|
||||
{/* <Card style={{padding: 6, marginTop: 10, marginBottom: 10}} bodyStyle={{padding: 6}}>
|
||||
<Tag style={{fontSize:14, padding: 8}} closable> status:200 </Tag><Tag style={{fontSize:14, padding: 8}} closable> customerid:123 </Tag>
|
||||
{tagKeyValueApplied.map( item => <Tag key={item} style={{fontSize:14, padding: 8}} onClose={() => onTagClose(item)} closable> {item} </Tag>)}
|
||||
</Card> */}
|
||||
{/* // What will be the empty state of card when there is no Tag , it should show something */}
|
||||
|
||||
<InfoWrapper>Select Service to get Tag suggestions </InfoWrapper>
|
||||
|
||||
<Form form={form} layout='inline' onFinish={onTagFormSubmit} initialValues={{operator:'equals'}} style={{marginTop: 10, marginBottom:10}}>
|
||||
|
||||
<FormItem rules={[{ required: true }]} name='tag_key'>
|
||||
{/* <Input style={{ width: 160, textAlign: 'center' }} placeholder="Tag Key" /> */}
|
||||
{/* Not using tag count data to show in options */}
|
||||
|
||||
<AutoComplete
|
||||
options={tagKeyOptions.map((s) => { return ({'value' : s.tagKeys}) })}
|
||||
style={{ width: 200, textAlign: 'center' }}
|
||||
// onSelect={onSelect}
|
||||
// onSearch={onSearch}
|
||||
onChange={onChangeTagKey}
|
||||
filterOption={(inputValue, option) =>
|
||||
option!.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||
}
|
||||
placeholder="Tag Key"
|
||||
/>
|
||||
{/* // ! means that we are saying object can't be undefined */}
|
||||
</FormItem>
|
||||
|
||||
<FormItem name='operator'>
|
||||
<Select style={{ width: 120, textAlign: 'center' }}>
|
||||
<Option value="equals">EQUAL</Option>
|
||||
<Option value="contains">CONTAINS</Option>
|
||||
{/* <Option value="not-in">NOT IN</Option> */}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
<FormItem rules={[{ required: true }]} name='tag_value'>
|
||||
<Input style={{ width: 160, textAlign: 'center',}} placeholder="Tag Value" />
|
||||
</FormItem>
|
||||
|
||||
<FormItem>
|
||||
<Button type="primary" htmlType="submit"> Apply Tag Filter </Button>
|
||||
</FormItem>
|
||||
|
||||
</Form>
|
||||
|
||||
<LatencyModalForm
|
||||
visible={modalVisible}
|
||||
onCreate={onLatencyModalApply}
|
||||
onCancel={() => {
|
||||
setModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traceFilters: TraceFilters, globalTime: GlobalTime } => {
|
||||
// console.log(state);
|
||||
return { traceFilters: state.traceFilters, globalTime: state.globalTime };
|
||||
};
|
||||
|
||||
export const TraceFilter = connect(mapStateToProps, {
|
||||
updateTraceFilters: updateTraceFilters,
|
||||
fetchTraces: fetchTraces,
|
||||
})(_TraceFilter);
|
49
frontend/src/components/traces/TraceGraph.css
Normal file
49
frontend/src/components/traces/TraceGraph.css
Normal file
@ -0,0 +1,49 @@
|
||||
.d3-tip {
|
||||
line-height: 1;
|
||||
padding: 2px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
font-size: 6px;
|
||||
}
|
||||
|
||||
/* Creates a small triangle extender for the tooltip */
|
||||
.d3-tip:after {
|
||||
box-sizing: border-box;
|
||||
display: inline;
|
||||
font-size: 6px;
|
||||
width: 100%;
|
||||
line-height: 1;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
content: "\25BC";
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Style northward tooltips differently */
|
||||
.d3-tip.n:after {
|
||||
margin: -1px 0 0 0;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
/* SVG element */
|
||||
/* Way to add borders in SVG - https://stackoverflow.com/questions/18330344/how-to-add-border-outline-stroke-to-svg-elements-in-css */
|
||||
.frame {
|
||||
fill: none;
|
||||
stroke: rgba(255, 255, 255, 0.25);
|
||||
stroke-width: 1;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
/* Transparency simulates sub pixel border https://stackoverflow.com/questions/13891177/css-border-less-than-1px */
|
||||
|
||||
.d3-flame-graph-label:hover {
|
||||
border: 1px dotted;
|
||||
border-color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
/*
|
||||
.d3-flame-graph-label:hover {
|
||||
border: 1px solid;
|
||||
border-color: rgba(255, 255, 255, 0.75);
|
||||
} */
|
147
frontend/src/components/traces/TraceGraph.tsx
Normal file
147
frontend/src/components/traces/TraceGraph.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useParams } from "react-router-dom";
|
||||
import { flamegraph } from 'd3-flame-graph'
|
||||
import { connect } from 'react-redux';
|
||||
import { Card, Button, Row, Col, Space } from 'antd';
|
||||
import * as d3 from 'd3';
|
||||
import * as d3Tip from 'd3-tip';
|
||||
|
||||
//import * as d3Tip from 'd3-tip';
|
||||
// PNOTE - uninstall @types/d3-tip. issues with importing d3-tip https://github.com/Caged/d3-tip/issues/181
|
||||
// import styled from 'styled-components';
|
||||
|
||||
import './TraceGraph.css'
|
||||
import { spanToTreeUtil } from '../../utils/spanToTree'
|
||||
import { fetchTraceItem , spansWSameTraceIDResponse } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
import { TraceGraphColumn } from './TraceGraphColumn'
|
||||
import SelectedSpanDetails from './SelectedSpanDetails'
|
||||
|
||||
|
||||
interface TraceGraphProps {
|
||||
|
||||
traceItem: spansWSameTraceIDResponse ,
|
||||
fetchTraceItem: Function,
|
||||
}
|
||||
|
||||
|
||||
const _TraceGraph = (props: TraceGraphProps) => {
|
||||
|
||||
const params = useParams<{ id?: string; }>();
|
||||
const [clickedSpanTags,setClickedSpanTags]=useState([])
|
||||
|
||||
useEffect( () => {
|
||||
console.log('inside initial fetch trace for flamegraph')
|
||||
props.fetchTraceItem(params.id);
|
||||
}, []);
|
||||
|
||||
useEffect( () => {
|
||||
if (props.traceItem[0].events.length>0)
|
||||
{
|
||||
const tree = spanToTreeUtil(props.traceItem[0].events);
|
||||
d3.select("#chart").datum(tree).call(chart);
|
||||
}
|
||||
},[props.traceItem]);
|
||||
// if this monitoring of props.traceItem.data is removed then zoom on click doesn't work
|
||||
// Doesn't work if only do initial check, works if monitor an element - as it may get updated in sometime
|
||||
|
||||
// PNOTE - Is this being called multiple times?
|
||||
//PNOTE - Do we fetch trace data again based on id or do we call again using rest calls
|
||||
// d3-flame-graph repository -- https://github.com/spiermar/d3-flame-graph
|
||||
|
||||
// const tip = d3.tip().attr('class', 'd3-tip').html(function(d) { return d; });
|
||||
// const tip = d3Tip.default().attr('class', 'd3-flame-graph-tip').html(function(d:any) { console.log(d);return 'Name -> '+d.data.name+'<BR>Duration -> '+d.data.value});
|
||||
//https://stackoverflow.com/questions/5934928/javascript-return-value-for-tooltip -> How to display tooltip
|
||||
//const tip = d3Tip.default().attr('class', 'd3-tip').html(function(d:any) { console.log(d);return <FlamegraphTooltip>{d.data.name}</FlamegraphTooltip>});
|
||||
|
||||
// var tip = flamegraph.tooltip.defaultFlamegraphTooltip().html(function(d) { return "name: " + d.data.name + ", value: " + d.data.value; });
|
||||
const tip = d3Tip.default().attr('class', 'd3-tip').html(function(d:any) { return d.data.name+'<br>duration: '+d.data.value});
|
||||
// PNOTE - Used this example for tooltip styling
|
||||
|
||||
const onClick = (z:any) => {
|
||||
// props.tagsInfo(d.data.tags)
|
||||
// let new_tags = z.data.tags;
|
||||
// let new_tags = [{
|
||||
// key: 'Ankit',
|
||||
// type: 'testin',
|
||||
// value: 'Nothing',
|
||||
// }]
|
||||
// setClickedSpanTags(new_tags);
|
||||
// setNum(9);
|
||||
setClickedSpanTags(z.data.tags);
|
||||
console.log(`Clicked on ${z.data.name}, id: "${z.id}"`);
|
||||
}
|
||||
|
||||
|
||||
const chart = flamegraph()
|
||||
.width(640)
|
||||
.cellHeight(18)
|
||||
.transitionDuration(500)
|
||||
.minFrameSize(5)
|
||||
.sort(true)
|
||||
.inverted(true)
|
||||
.tooltip(tip)
|
||||
.elided(false)
|
||||
.onClick(onClick)
|
||||
// .title("Trace Flame graph")
|
||||
.differential(false)
|
||||
.selfValue(true); //sets span width based on value - which is mapped to duration
|
||||
//PNOTE
|
||||
// used inverted() instead of passing a function in Sort
|
||||
// .sort(function(a :pushDStree,b :pushDStree) :number{
|
||||
// if(a.startTime < b.startTime) return -1;
|
||||
// else return 1;
|
||||
// })
|
||||
//removed transition ease - easeCubic as it was giving type error. d3.easeCubic is the default transition easing function
|
||||
//Example to sort in reverse order
|
||||
//.sort(function(a,b){ return d3.descending(a.name, b.name);})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// PNOTE - filter based on traceid - trace should already be in redux-state, will redux-state become very big if all trace reponses are stored in it
|
||||
|
||||
//if tree
|
||||
// d3.select("#chart").datum(tree).call(chart)
|
||||
|
||||
const resetZoom = () => {
|
||||
chart.resetZoom();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<Row gutter={{ xs: 8, sm: 16, md: 24, lg: 32 }}>
|
||||
<Col md={8} sm={24} >
|
||||
<TraceGraphColumn />
|
||||
</Col>
|
||||
<Col md={16} sm={24} >
|
||||
{/* <Card style={{ width: 640 }}> */}
|
||||
<Space direction="vertical" size='middle' >
|
||||
|
||||
<Card bodyStyle={{padding: 80, }} style={{ height: 320, }}>
|
||||
<div>Trace Graph component ID is {params.id} </div>
|
||||
<Button type="primary" onClick={resetZoom}>Reset Zoom</Button>
|
||||
<div id="chart" style={{ fontSize: 12 }}></div>
|
||||
</Card>
|
||||
|
||||
<SelectedSpanDetails clickedSpanTags={clickedSpanTags}/>
|
||||
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traceItem: spansWSameTraceIDResponse } => {
|
||||
// console.log(state);
|
||||
return { traceItem: state.traceItem };
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const TraceGraph = connect(mapStateToProps, {
|
||||
fetchTraceItem: fetchTraceItem,
|
||||
})(_TraceGraph);
|
83
frontend/src/components/traces/TraceGraphColumn.tsx
Normal file
83
frontend/src/components/traces/TraceGraphColumn.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Table } from 'antd'
|
||||
|
||||
import { traceResponseNew, pushDStree } from '../../actions';
|
||||
import { StoreState } from '../../reducers'
|
||||
|
||||
|
||||
interface TraceGraphColumnProps {
|
||||
traces: traceResponseNew,
|
||||
}
|
||||
|
||||
interface TableDataSourceItem {
|
||||
key: string;
|
||||
operationName: string;
|
||||
startTime: number;
|
||||
duration: number;
|
||||
}
|
||||
|
||||
|
||||
const _TraceGraphColumn = (props: TraceGraphColumnProps) => {
|
||||
|
||||
const columns: any = [
|
||||
{
|
||||
title: 'Start Time (UTC Time)',
|
||||
dataIndex: 'startTime',
|
||||
key: 'startTime',
|
||||
sorter: (a:any, b:any) => a.startTime - b.startTime,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (new Date(Math.round(value/1000))).toUTCString()
|
||||
|
||||
// render: (value: number) => (new Date(Math.round(value/1000))).toLocaleDateString()+' '+(new Date(Math.round(value/1000))).toLocaleTimeString()
|
||||
},
|
||||
{
|
||||
title: 'Duration (in ms)',
|
||||
dataIndex: 'duration',
|
||||
key: 'duration',
|
||||
sorter: (a:any, b:any) => a.duration - b.duration,
|
||||
sortDirections: ['descend', 'ascend'],
|
||||
render: (value: number) => (value/1000000).toFixed(2),
|
||||
},
|
||||
{
|
||||
title: 'Operation',
|
||||
dataIndex: 'operationName',
|
||||
key: 'operationName',
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
let dataSource :TableDataSourceItem[] = [];
|
||||
|
||||
// PNOTE - Define new array
|
||||
// if (props.traces.data.length > 0)
|
||||
// {
|
||||
// props.traces.data[0].spans.map((item: spanItem, index ) => dataSource.push({startTime: item.startTime, operationName: item.operationName , duration: item.duration, key:index.toString()}) );
|
||||
// }
|
||||
|
||||
if (props.traces[0].events.length > 0) {
|
||||
|
||||
props.traces[0].events.map((item: (number|string|string[]|pushDStree[])[], index ) => {
|
||||
if (typeof item[0] === 'number' && typeof item[4] === 'string' && typeof item[6] === 'string' && typeof item[1] === 'string' && typeof item[2] === 'string' )
|
||||
dataSource.push({startTime: item[0], operationName: item[4] , duration:parseInt(item[6]), key:index.toString()});
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
{/* <div>Tracing Graph Column Page</div> */}
|
||||
<Table dataSource={dataSource} columns={columns} size="middle"/>;
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state: StoreState): { traces: traceResponseNew } => {
|
||||
// console.log(state);
|
||||
return { traces : state.traces };
|
||||
};
|
||||
// the name mapStateToProps is only a convention
|
||||
// take state and map it to props which are accessible inside this component
|
||||
|
||||
export const TraceGraphColumn = connect(mapStateToProps)(_TraceGraphColumn);
|
5
frontend/src/components/traces/TraceGraphDef.tsx
Normal file
5
frontend/src/components/traces/TraceGraphDef.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
export { TraceGraph as default } from './TraceGraph';
|
||||
|
||||
// PNOTE
|
||||
// Because react.lazy doesn't work on named components
|
||||
// https://reactjs.org/docs/code-splitting.html#:~:text=Named%20Exports,t%20pull%20in%20unused%20components
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user