Compare commits
235 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4cc2a3a05 | ||
|
|
041a5249b3 | ||
|
|
a767697a86 | ||
|
|
71cb70c62c | ||
|
|
647cabc4f4 | ||
|
|
e864e33ad3 | ||
|
|
5bdbe792f5 | ||
|
|
399efb0fb2 | ||
|
|
4b72de6884 | ||
|
|
9f1473e7de | ||
|
|
7150971dc0 | ||
|
|
d0846b8dd2 | ||
|
|
ead6885b29 | ||
|
|
d72dacdc1f | ||
|
|
1d6ddd4890 | ||
|
|
58daca1579 | ||
|
|
1e522ad8f1 | ||
|
|
8809105a8d | ||
|
|
064c3e0449 | ||
|
|
2a348e916c | ||
|
|
5744193f50 | ||
|
|
ccf352f2db | ||
|
|
6e446dc0ab | ||
|
|
566c2becdf | ||
|
|
3b3fd2b3a9 | ||
|
|
eae53d9eff | ||
|
|
42842b6b17 | ||
|
|
95f8dfb4bc | ||
|
|
a8c5934fc5 | ||
|
|
3f2a4d6eac | ||
|
|
170609a81f | ||
|
|
76fccbbba4 | ||
|
|
147ed9f24b | ||
|
|
a69bc321a9 | ||
|
|
c9e02a8b25 | ||
|
|
24d6a1e7b2 | ||
|
|
a0efa63185 | ||
|
|
fd83cea9a0 | ||
|
|
5be1eb58b2 | ||
|
|
8367c106bc | ||
|
|
8064ae1f37 | ||
|
|
ab4d9af442 | ||
|
|
eb0d3374d5 | ||
|
|
6c4c814b3f | ||
|
|
32e8e48928 | ||
|
|
53e7037f48 | ||
|
|
a566b5dc97 | ||
|
|
4dc668fd13 | ||
|
|
d085506d3e | ||
|
|
1b28a4e6f5 | ||
|
|
20e924b116 | ||
|
|
1d28ceb3d7 | ||
|
|
1002ab553e | ||
|
|
3dc94c8da7 | ||
|
|
5a5aca2113 | ||
|
|
cb22117a0f | ||
|
|
739946fa47 | ||
|
|
7939902f03 | ||
|
|
d34e08fa3d | ||
|
|
5556d1d6fc | ||
|
|
d4d1104a53 | ||
|
|
225a345baa | ||
|
|
0efb901863 | ||
|
|
e7ba5f9f33 | ||
|
|
995232e057 | ||
|
|
cc5d47e3ee | ||
|
|
b1de6c1d7d | ||
|
|
84bfe11285 | ||
|
|
ca78947a55 | ||
|
|
ac49f84982 | ||
|
|
cc47f02ebf | ||
|
|
ac70240b72 | ||
|
|
78b1a750fa | ||
|
|
d5a6336239 | ||
|
|
01bad0f18a | ||
|
|
1b79a9bf35 | ||
|
|
3d8354fb99 | ||
|
|
696241b962 | ||
|
|
8a883f1b5e | ||
|
|
7765cee610 | ||
|
|
b958bad81f | ||
|
|
deff5d5e17 | ||
|
|
44d3f35a5f | ||
|
|
565dfd5b52 | ||
|
|
897c5d2371 | ||
|
|
f22d5f0fbd | ||
|
|
8c56d04988 | ||
|
|
18cfc40982 | ||
|
|
3c66f9d2dd | ||
|
|
4f3bb95a77 | ||
|
|
8aa5eb78b2 | ||
|
|
79a1f79b7c | ||
|
|
1c5c65ddf7 | ||
|
|
b2e78b9358 | ||
|
|
5e02bfe2e4 | ||
|
|
02d89a3a04 | ||
|
|
3ab0e1395a | ||
|
|
f1f606844a | ||
|
|
4e335054fb | ||
|
|
c902a6bac8 | ||
|
|
c00f0f159b | ||
|
|
86bdb9a5ad | ||
|
|
044f02c7c7 | ||
|
|
561d18efec | ||
|
|
ab10a699b1 | ||
|
|
e28733d246 | ||
|
|
a238123eb2 | ||
|
|
4337ab5cd0 | ||
|
|
7a3a3b8d89 | ||
|
|
a9cbd12330 | ||
|
|
c320c20280 | ||
|
|
b3c2fe75d3 | ||
|
|
95d3a27769 | ||
|
|
67f09c6def | ||
|
|
eaeba43179 | ||
|
|
daadc584ea | ||
|
|
da8b16f588 | ||
|
|
17738a58a2 | ||
|
|
3ebffae1c6 | ||
|
|
2ca67f1017 | ||
|
|
00c7eccb0c | ||
|
|
7f3d9e2e35 | ||
|
|
a95656b3a0 | ||
|
|
9404768f9d | ||
|
|
7559445ebe | ||
|
|
112766b265 | ||
|
|
ccf5af089d | ||
|
|
0b6f31420b | ||
|
|
b4ce805c6f | ||
|
|
08f24fbdff | ||
|
|
191925b418 | ||
|
|
84b70c970f | ||
|
|
988ce36047 | ||
|
|
24a4177a73 | ||
|
|
08ca3b7849 | ||
|
|
d0723207c3 | ||
|
|
16cf829ec3 | ||
|
|
23f9949fad | ||
|
|
fafdd4b87f | ||
|
|
f2ace729fd | ||
|
|
f0c627eebe | ||
|
|
1bf8e6bef6 | ||
|
|
f37e6ef1d1 | ||
|
|
c3ebbfa8ca | ||
|
|
12970d6975 | ||
|
|
1112ff7e7a | ||
|
|
09fd877b2a | ||
|
|
c04c0284dc | ||
|
|
239cdad57b | ||
|
|
314f95a914 | ||
|
|
e070ba61cd | ||
|
|
79576b476f | ||
|
|
8e4f987cf6 | ||
|
|
3fe3bde0c7 | ||
|
|
b8a6a27fad | ||
|
|
fb3dbcf662 | ||
|
|
cb04979bb7 | ||
|
|
967b83a5d0 | ||
|
|
3fd086db4d | ||
|
|
9aedcc1777 | ||
|
|
e7d2bb13dc | ||
|
|
39c3f67d86 | ||
|
|
49d1015a72 | ||
|
|
f3b2f30c82 | ||
|
|
ff9d81aefc | ||
|
|
c9b07ee5dd | ||
|
|
bda8ddc0e4 | ||
|
|
540a682bf0 | ||
|
|
9f7c60d2c1 | ||
|
|
80a06300ff | ||
|
|
7fae5207d1 | ||
|
|
b4f781ad47 | ||
|
|
d9468d438d | ||
|
|
25559c8781 | ||
|
|
aa760336d0 | ||
|
|
46aaa8199d | ||
|
|
10faa6f42b | ||
|
|
2d5ad346a6 | ||
|
|
207360e074 | ||
|
|
a4b954e304 | ||
|
|
3e24e371f4 | ||
|
|
bdeadaeff6 | ||
|
|
43b39f1a7c | ||
|
|
6ab7fea8de | ||
|
|
05371085f9 | ||
|
|
938f42bd4f | ||
|
|
1652f35f1a | ||
|
|
3a67d0237c | ||
|
|
7888865ddc | ||
|
|
25fb0deb8d | ||
|
|
b067db633a | ||
|
|
9094f20070 | ||
|
|
9129704a93 | ||
|
|
fc244edc50 | ||
|
|
cbe635bc94 | ||
|
|
bb0a7a956a | ||
|
|
2800b021e6 | ||
|
|
74170ffb4a | ||
|
|
ab72e92fc6 | ||
|
|
8b224dd59c | ||
|
|
ce77a3cd80 | ||
|
|
79fa4e7b74 | ||
|
|
90e97856a9 | ||
|
|
61fb11a232 | ||
|
|
729ea586a8 | ||
|
|
eb28459847 | ||
|
|
10fc3bf456 | ||
|
|
244a07aef8 | ||
|
|
92cf617a53 | ||
|
|
1eb333a890 | ||
|
|
ebc280db0e | ||
|
|
3beb7f1843 | ||
|
|
56c9ea5430 | ||
|
|
05c79b7119 | ||
|
|
a6da00f801 | ||
|
|
0514c5035e | ||
|
|
2df058825a | ||
|
|
a07b8999c0 | ||
|
|
5405f0d9ed | ||
|
|
00cefe2306 | ||
|
|
5aa46c7e96 | ||
|
|
c367f928c5 | ||
|
|
4c2f287e06 | ||
|
|
567c81c0f9 | ||
|
|
31f0a9f0b6 | ||
|
|
bad14a7d09 | ||
|
|
28e8cffeaa | ||
|
|
baba0c389c | ||
|
|
0d98a4fd0c | ||
|
|
09344cfb44 | ||
|
|
9a00dca749 | ||
|
|
e7253b7ca6 | ||
|
|
dddba68bfd | ||
|
|
40c287028b | ||
|
|
30124de409 |
33
.editorconfig
Normal file
33
.editorconfig
Normal file
@@ -0,0 +1,33 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
# Matches multiple files with brace expansion notation
|
||||
# Set default charset
|
||||
[*.{js,py}]
|
||||
charset = utf-8
|
||||
|
||||
# 4 space indentation
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
# Tab indentation (no size specified)
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
# Indentation override for all JS under lib directory
|
||||
[lib/**.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
# Matches the exact files either package.json or .travis.yml
|
||||
[{package.json,.travis.yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
4
.github/CODEOWNERS
vendored
4
.github/CODEOWNERS
vendored
@@ -2,5 +2,5 @@
|
||||
# Owners are automatically requested for review for PRs that changes code
|
||||
# that they own.
|
||||
* @ankitnayan
|
||||
/frontend/ @palash-signoz
|
||||
/deploy/ @prashant-shahi
|
||||
/frontend/ @palash-signoz @pranshuchittora
|
||||
/deploy/ @prashant-shahi
|
||||
|
||||
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
1
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -26,6 +26,7 @@ assignees: ''
|
||||
* **Signoz version**:
|
||||
* **Browser version**:
|
||||
* **Your OS and version**:
|
||||
* **Your CPU Architecture**(ARM/Intel):
|
||||
|
||||
## Additional context
|
||||
|
||||
|
||||
34
.github/workflows/build.yaml
vendored
34
.github/workflows/build.yaml
vendored
@@ -2,11 +2,12 @@ name: build-pipeline
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- main
|
||||
- v*
|
||||
paths:
|
||||
- 'pkg/**'
|
||||
- 'frontend/**'
|
||||
- "pkg/**"
|
||||
- "frontend/**"
|
||||
|
||||
jobs:
|
||||
get_filters:
|
||||
@@ -17,17 +18,17 @@ jobs:
|
||||
query-service: ${{ steps.filter.outputs.query-service }}
|
||||
flattener: ${{ steps.filter.outputs.flattener }}
|
||||
steps:
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'frontend/**'
|
||||
query-service:
|
||||
- 'pkg/query-service/**'
|
||||
flattener:
|
||||
- 'pkg/processors/flattener/**'
|
||||
# For pull requests it's not necessary to checkout the code
|
||||
- uses: dorny/paths-filter@v2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
frontend:
|
||||
- 'frontend/**'
|
||||
query-service:
|
||||
- 'pkg/query-service/**'
|
||||
flattener:
|
||||
- 'pkg/processors/flattener/**'
|
||||
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -39,12 +40,11 @@ jobs:
|
||||
uses: actions/checkout@v2
|
||||
- name: Install dependencies
|
||||
run: cd frontend && yarn install
|
||||
- name: Run Prettier
|
||||
run: cd frontend && npm run prettify
|
||||
continue-on-error: true
|
||||
- name: Run ESLint
|
||||
run: cd frontend && npm run lint
|
||||
continue-on-error: true
|
||||
- name: TSC
|
||||
run: yarn tsc
|
||||
working-directory: ./frontend
|
||||
- name: Build frontend docker image
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
5
.github/workflows/e2e-k3s.yaml
vendored
5
.github/workflows/e2e-k3s.yaml
vendored
@@ -52,14 +52,11 @@ jobs:
|
||||
helm install my-release signoz/signoz -n platform \
|
||||
--wait \
|
||||
--timeout 10m0s \
|
||||
--set cloud=null \
|
||||
--set frontend.service.type=LoadBalancer \
|
||||
--set query-service.image.tag=$DOCKER_TAG \
|
||||
--set queryService.image.tag=$DOCKER_TAG \
|
||||
--set frontend.image.tag=$DOCKER_TAG
|
||||
|
||||
# get pods, services and the container images
|
||||
kubectl describe deploy/my-release-frontend -n platform | grep Image
|
||||
kubectl describe statefulset/my-release-query-service -n platform | grep Image
|
||||
kubectl get pods -n platform
|
||||
kubectl get svc -n platform
|
||||
|
||||
|
||||
25
.github/workflows/repo-stats.yml
vendored
Normal file
25
.github/workflows/repo-stats.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
on:
|
||||
schedule:
|
||||
# Run this once per day, towards the end of the day for keeping the most
|
||||
# recent data point most meaningful (hours are interpreted in UTC).
|
||||
- cron: "0 8 * * *"
|
||||
workflow_dispatch: # Allow for running this manually.
|
||||
|
||||
jobs:
|
||||
j1:
|
||||
name: repostats
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: run-ghrs
|
||||
uses: jgehrcke/github-repo-stats@v1.1.0
|
||||
with:
|
||||
# Define the stats repository (the repo to fetch
|
||||
# stats for and to generate the report for).
|
||||
# Remove the parameter when the stats repository
|
||||
# and the data repository are the same.
|
||||
repository: signoz/signoz
|
||||
# Set a GitHub API token that can read the stats
|
||||
# repository, and that can push to the data
|
||||
# repository (which this workflow file lives in),
|
||||
# to store data and the report files.
|
||||
ghtoken: ${{ github.token }}
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -42,4 +42,11 @@ frontend/cypress.env.json
|
||||
|
||||
frontend/*.env
|
||||
pkg/query-service/signoz.db
|
||||
|
||||
pkg/query-service/tframe/test-deploy/data/
|
||||
|
||||
|
||||
# local data
|
||||
|
||||
/deploy/docker/clickhouse-setup/data/
|
||||
/deploy/docker-swarm/clickhouse-setup/data/
|
||||
|
||||
@@ -11,7 +11,7 @@ tasks:
|
||||
- name: Run Docker Images
|
||||
init: |
|
||||
cd ./deploy
|
||||
sudo docker-compose --env-file ./docker/clickhouse-setup/env/x86_64.env -f docker/clickhouse-setup/docker-compose.yaml up -d
|
||||
sudo docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d
|
||||
# command:
|
||||
|
||||
- name: Run Frontend
|
||||
@@ -22,7 +22,7 @@ tasks:
|
||||
yarn dev
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
- port: 3301
|
||||
onOpen: open-browser
|
||||
- port: 8080
|
||||
onOpen: ignore
|
||||
|
||||
@@ -21,6 +21,12 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/frontend](https://git
|
||||
- comment out frontend service section at `deploy/docker/clickhouse-setup/docker-compose.yaml#L59`
|
||||
- run `cd deploy` to move to deploy directory
|
||||
- Install signoz locally without the frontend
|
||||
- Add below configuration to query-service section at `docker/clickhouse-setup/docker-compose.yaml#L36`
|
||||
|
||||
```docker
|
||||
ports:
|
||||
- "8080:8080"
|
||||
```
|
||||
- If you are using x86_64 processors (All Intel/AMD processors) run `sudo docker-compose -f docker/clickhouse-setup/docker-compose.yaml up -d`
|
||||
- If you are on arm64 processors (Apple M1 Macbooks) run `sudo docker-compose -f docker/clickhouse-setup/docker-compose.arm.yaml up -d`
|
||||
- `cd ../frontend` and change baseURL to `http://localhost:8080` in file `src/constants/env.ts`
|
||||
@@ -47,13 +53,32 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/pkg/query-service](ht
|
||||
### To run ClickHouse setup (recommended for local development)
|
||||
|
||||
- git clone https://github.com/SigNoz/signoz.git
|
||||
- run `cd signoz` to move to signoz directory
|
||||
- run `sudo make dev-setup` to configure local setup to run query-service
|
||||
- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L59`
|
||||
- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L38`
|
||||
- comment out frontend service section at `docker/clickhouse-setup/docker-compose.yaml#L45`
|
||||
- comment out query-service section at `docker/clickhouse-setup/docker-compose.yaml#L28`
|
||||
- add below configuration to clickhouse section at `docker/clickhouse-setup/docker-compose.yaml#L6`
|
||||
```docker
|
||||
expose:
|
||||
- 9000
|
||||
ports:
|
||||
- 9001:9000
|
||||
```
|
||||
|
||||
- run `cd pkg/query-service/` to move to query-service directory
|
||||
- Open ./constants/constants.go
|
||||
- Replace ```const RELATIONAL_DATASOURCE_PATH = "/var/lib/signoz/signoz.db"``` \
|
||||
with ```const RELATIONAL_DATASOURCE_PATH = "./signoz.db".```
|
||||
|
||||
- Install signoz locally without the frontend and query-service
|
||||
- If you are using x86_64 processors (All Intel/AMD processors) run `sudo make run-x86`
|
||||
- If you are on arm64 processors (Apple M1 Macbooks) run `sudo make run-arm`
|
||||
|
||||
#### Run locally
|
||||
```console
|
||||
ClickHouseUrl=tcp://localhost:9001 STORAGE=clickhouse go run main.go
|
||||
```
|
||||
|
||||
> Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh`
|
||||
|
||||
**_Query Service should now be available at `http://localhost:8080`_**
|
||||
@@ -61,13 +86,13 @@ Need to update [https://github.com/SigNoz/signoz/tree/main/pkg/query-service](ht
|
||||
> If you want to see how, frontend plays with query service, you can run frontend also in you local env with the baseURL changed to `http://localhost:8080` in file `src/constants/env.ts` as the query-service is now running at port `8080`
|
||||
|
||||
---
|
||||
Instead of configuring a local setup, you can also use [Gitpod](https://www.gitpod.io/), a VSCode-based Web IDE.
|
||||
<!-- Instead of configuring a local setup, you can also use [Gitpod](https://www.gitpod.io/), a VSCode-based Web IDE.
|
||||
|
||||
Click the button below. A workspace with all required environments will be created.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/SigNoz/signoz)
|
||||
|
||||
> To use it on your forked repo, edit the 'Open in Gitpod' button url to `https://gitpod.io/#https://github.com/<your-github-username>/signoz`
|
||||
> To use it on your forked repo, edit the 'Open in Gitpod' button url to `https://gitpod.io/#https://github.com/<your-github-username>/signoz` -->
|
||||
|
||||
# Contribute to SigNoz Helm Chart
|
||||
|
||||
|
||||
40
Makefile
40
Makefile
@@ -12,6 +12,8 @@ BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
FRONTEND_DIRECTORY ?= frontend
|
||||
FLATTENER_DIRECTORY ?= pkg/processors/flattener
|
||||
QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
||||
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
||||
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
||||
|
||||
REPONAME ?= signoz
|
||||
DOCKER_TAG ?= latest
|
||||
@@ -38,7 +40,8 @@ build-frontend-amd64:
|
||||
@echo "--> Building frontend docker image for amd64"
|
||||
@echo "------------------"
|
||||
@cd $(FRONTEND_DIRECTORY) && \
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) --build-arg TARGETPLATFORM="linux/amd64" .
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||
|
||||
# Step to build and push docker image of frontend(used in push pipeline)
|
||||
build-push-frontend:
|
||||
@@ -46,7 +49,8 @@ build-push-frontend:
|
||||
@echo "--> Building and pushing frontend docker image"
|
||||
@echo "------------------"
|
||||
@cd $(FRONTEND_DIRECTORY) && \
|
||||
docker buildx build --file Dockerfile --progress plane --no-cache --push --platform linux/amd64 --tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
docker buildx build --file Dockerfile --progress plane --no-cache --push --platform linux/amd64 \
|
||||
--tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
# Steps to build and push docker image of query service
|
||||
.PHONY: build-query-service-amd64 build-push-query-service
|
||||
@@ -56,7 +60,8 @@ build-query-service-amd64:
|
||||
@echo "--> Building query-service docker image for amd64"
|
||||
@echo "------------------"
|
||||
@cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) . --build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS=$(LD_FLAGS)
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS=$(LD_FLAGS) .
|
||||
|
||||
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
||||
build-push-query-service:
|
||||
@@ -64,7 +69,9 @@ build-push-query-service:
|
||||
@echo "--> Building and pushing query-service docker image"
|
||||
@echo "------------------"
|
||||
@cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
docker buildx build --file Dockerfile --progress plane --no-cache --push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS=$(LD_FLAGS) --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
docker buildx build --file Dockerfile --progress plane --no-cache \
|
||||
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS=$(LD_FLAGS) \
|
||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
# Steps to build and push docker image of flattener
|
||||
.PHONY: build-flattener-amd64 build-push-flattener
|
||||
@@ -74,7 +81,8 @@ build-flattener-amd64:
|
||||
@echo "--> Building flattener docker image for amd64"
|
||||
@echo "------------------"
|
||||
@cd $(FLATTENER_DIRECTORY) && \
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) . --build-arg TARGETPLATFORM="linux/amd64"
|
||||
docker build -f Dockerfile --no-cache -t $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||
|
||||
# Step to build and push docker image of flattener in amd64 (used in push pipeline)
|
||||
build-push-flattener:
|
||||
@@ -82,7 +90,9 @@ build-push-flattener:
|
||||
@echo "--> Building and pushing flattener docker image"
|
||||
@echo "------------------"
|
||||
@cd $(FLATTENER_DIRECTORY) && \
|
||||
docker buildx build --file Dockerfile --progress plane --no-cache --push --platform linux/arm64,linux/amd64 --tag $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
docker buildx build --file Dockerfile --progress plane \
|
||||
--no-cache --push --platform linux/arm64,linux/amd64 \
|
||||
--tag $(REPONAME)/$(FLATTERNER_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
dev-setup:
|
||||
mkdir -p /var/lib/signoz
|
||||
@@ -93,7 +103,21 @@ dev-setup:
|
||||
@echo "------------------"
|
||||
|
||||
run-x86:
|
||||
@sudo docker-compose -f ./deploy/docker/clickhouse-setup/docker-compose.yaml up -d
|
||||
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml up -d
|
||||
|
||||
run-arm:
|
||||
@sudo docker-compose -f ./deploy/docker/clickhouse-setup/docker-compose.arm.yaml up -d
|
||||
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.arm.yaml up -d
|
||||
|
||||
down-x86:
|
||||
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml down -v
|
||||
|
||||
down-arm:
|
||||
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.arm.yaml down -v
|
||||
|
||||
clear-standalone-data:
|
||||
@docker run --rm -v "$(PWD)/$(STANDALONE_DIRECTORY)/data:/pwd" busybox \
|
||||
sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*"
|
||||
|
||||
clear-swarm-data:
|
||||
@docker run --rm -v "$(PWD)/$(SWARM_DIRECTORY)/data:/pwd" busybox \
|
||||
sh -c "cd /pwd && rm -rf alertmanager/* clickhouse/* signoz/*"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<p align="center">
|
||||
<img alt="License" src="https://img.shields.io/badge/license-MIT-brightgreen"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/frontend?label=Downloads"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Downloads"> </a>
|
||||
<img alt="GitHub issues" src="https://img.shields.io/github/issues/signoz/signoz"> </a>
|
||||
<a href="https://twitter.com/intent/tweet?text=Monitor%20your%20applications%20and%20troubleshoot%20problems%20with%20SigNoz,%20an%20open-source%20alternative%20to%20DataDog,%20NewRelic.&url=https://signoz.io/&via=SigNozHQ&hashtags=opensource,signoz,observability">
|
||||
<img alt="tweet" src="https://img.shields.io/twitter/url/http/shields.io.svg?style=social"> </a>
|
||||
@@ -34,8 +34,10 @@ SigNoz helps developers monitor applications and troubleshoot problems in their
|
||||
|
||||
|
||||

|
||||
|
||||
<br />
|
||||

|
||||
<br />
|
||||

|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
35
deploy/docker-swarm/clickhouse-setup/alertmanager.yml
Normal file
35
deploy/docker-swarm/clickhouse-setup/alertmanager.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
global:
|
||||
resolve_timeout: 1m
|
||||
slack_api_url: 'https://hooks.slack.com/services/xxx'
|
||||
|
||||
route:
|
||||
receiver: 'slack-notifications'
|
||||
|
||||
receivers:
|
||||
- name: 'slack-notifications'
|
||||
slack_configs:
|
||||
- channel: '#alerts'
|
||||
send_resolved: true
|
||||
icon_url: https://avatars3.githubusercontent.com/u/3380462
|
||||
title: |-
|
||||
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .CommonLabels.alertname }} for {{ .CommonLabels.job }}
|
||||
{{- if gt (len .CommonLabels) (len .GroupLabels) -}}
|
||||
{{" "}}(
|
||||
{{- with .CommonLabels.Remove .GroupLabels.Names }}
|
||||
{{- range $index, $label := .SortedPairs -}}
|
||||
{{ if $index }}, {{ end }}
|
||||
{{- $label.Name }}="{{ $label.Value -}}"
|
||||
{{- end }}
|
||||
{{- end -}}
|
||||
)
|
||||
{{- end }}
|
||||
text: >-
|
||||
{{ range .Alerts -}}
|
||||
*Alert:* {{ .Annotations.title }}{{ if .Labels.severity }} - `{{ .Labels.severity }}`{{ end }}
|
||||
|
||||
*Description:* {{ .Annotations.description }}
|
||||
|
||||
*Details:*
|
||||
{{ range .Labels.SortedPairs }} • *{{ .Name }}:* `{{ .Value }}`
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
11
deploy/docker-swarm/clickhouse-setup/alerts.yml
Normal file
11
deploy/docker-swarm/clickhouse-setup/alerts.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
groups:
|
||||
- name: ExampleCPULoadGroup
|
||||
rules:
|
||||
- alert: HighCpuLoad
|
||||
expr: system_cpu_load_average_1m > 0.1
|
||||
for: 0m
|
||||
labels:
|
||||
severity: warning
|
||||
annotations:
|
||||
summary: High CPU load
|
||||
description: "CPU load is > 0.1\n VALUE = {{ $value }}\n LABELS = {{ $labels }}"
|
||||
@@ -1,11 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<logger>
|
||||
<level>trace</level>
|
||||
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
|
||||
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
|
||||
<size>1000M</size>
|
||||
<count>10</count>
|
||||
<level>information</level>
|
||||
<console>1</console>
|
||||
</logger>
|
||||
|
||||
<http_port>8123</http_port>
|
||||
@@ -45,6 +42,34 @@
|
||||
</client>
|
||||
</openSSL>
|
||||
|
||||
<!-- Example config for tiered storage -->
|
||||
<!-- <storage_configuration>
|
||||
<disks>
|
||||
<default>
|
||||
<keep_free_space_bytes>10485760</keep_free_space_bytes>
|
||||
</default>
|
||||
<s3>
|
||||
<type>s3</type>
|
||||
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
|
||||
<access_key_id>ACCESS-KEY-ID</access_key_id>
|
||||
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
|
||||
</s3>
|
||||
</disks>
|
||||
<policies>
|
||||
<tiered>
|
||||
<volumes>
|
||||
<default>
|
||||
<disk>default</disk>
|
||||
</default>
|
||||
<s3>
|
||||
<disk>s3</disk>
|
||||
</s3>
|
||||
</volumes>
|
||||
</tiered>
|
||||
</policies>
|
||||
</storage_configuration> -->
|
||||
|
||||
|
||||
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
|
||||
<!--
|
||||
<http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script src="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
|
||||
|
||||
@@ -1,30 +1,43 @@
|
||||
version: "3"
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
clickhouse:
|
||||
image: yandex/clickhouse-server
|
||||
expose:
|
||||
- 8123
|
||||
- 9000
|
||||
ports:
|
||||
- 9001:9000
|
||||
- 8123:8123
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./docker-entrypoint-initdb.d/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
image: yandex/clickhouse-server:21.12.3.32
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
depends_on:
|
||||
- query-service
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.4.1
|
||||
container_name: query-service
|
||||
restart: always
|
||||
image: signoz/query-service:0.7.5
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
ports:
|
||||
- "8080:8080"
|
||||
@@ -35,72 +48,84 @@ services:
|
||||
environment:
|
||||
- ClickHouseUrl=tcp://clickhouse:9000
|
||||
- STORAGE=clickhouse
|
||||
- POSTHOG_API_KEY=H-htDCae7CR3RV57gUzmol6IAKtm5IMCvbcm_fwnL-w
|
||||
- GODEBUG=netdns=go
|
||||
depends_on:
|
||||
- clickhouse
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-swarm
|
||||
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- clickhouse
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.4.1
|
||||
container_name: frontend
|
||||
|
||||
image: signoz/frontend:0.7.5
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
links:
|
||||
- "query-service"
|
||||
ports:
|
||||
- "3301:3301"
|
||||
volumes:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.4.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=2000"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
- "1777:1777" # pprof extension
|
||||
- "8887:8888" # Prometheus metrics exposed by the agent
|
||||
- "14268:14268" # Jaeger receiver
|
||||
- "55678" # OpenCensus receiver
|
||||
- "55680:55680" # OTLP HTTP/2.0 legacy port
|
||||
- "55681:55681" # OTLP HTTP/1.0 receiver
|
||||
- "4317:4317" # OTLP GRPC receiver
|
||||
- "55679:55679" # zpages extension
|
||||
- "13133" # health_check
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||
deploy:
|
||||
mode: replicated
|
||||
replicas: 3
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
resources:
|
||||
limits:
|
||||
memory: 2000m
|
||||
depends_on:
|
||||
- clickhouse
|
||||
- clickhouse
|
||||
|
||||
otel-collector-hostmetrics:
|
||||
image: signoz/otelcontribcol:0.4.0
|
||||
command: ["--config=/etc/otel-collector-config-hostmetrics.yaml", "--mem-ballast-size-mib=683"]
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config-hostmetrics.yaml:/etc/otel-collector-config-hostmetrics.yaml
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
depends_on:
|
||||
- clickhouse
|
||||
|
||||
- clickhouse
|
||||
|
||||
hotrod:
|
||||
image: jaegertracing/example-hotrod:latest
|
||||
container_name: hotrod
|
||||
ports:
|
||||
- "9000:8080"
|
||||
image: jaegertracing/example-hotrod:1.30
|
||||
command: ["all"]
|
||||
environment:
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
|
||||
load-hotrod:
|
||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||
container_name: load-hotrod
|
||||
hostname: load-hotrod
|
||||
ports:
|
||||
- "8089:8089"
|
||||
environment:
|
||||
ATTACKED_HOST: http://hotrod:8080
|
||||
LOCUST_MODE: standalone
|
||||
@@ -110,4 +135,4 @@ services:
|
||||
QUIET_MODE: "${QUIET_MODE:-false}"
|
||||
LOCUST_OPTS: "--headless -u 10 -r 1"
|
||||
volumes:
|
||||
- ../common/locust-scripts:/locust
|
||||
- ../common/locust-scripts:/locust
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
receivers:
|
||||
otlp/spanmetrics:
|
||||
protocols:
|
||||
grpc:
|
||||
endpoint: "localhost:12345"
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
@@ -7,18 +11,34 @@ receivers:
|
||||
protocols:
|
||||
grpc:
|
||||
thrift_http:
|
||||
hostmetrics:
|
||||
collection_interval: 30s
|
||||
scrapers:
|
||||
cpu:
|
||||
load:
|
||||
memory:
|
||||
disk:
|
||||
filesystem:
|
||||
network:
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 1000
|
||||
timeout: 10s
|
||||
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
|
||||
signozspanmetrics/prometheus:
|
||||
metrics_exporter: prometheus
|
||||
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
||||
dimensions_cache_size: 10000
|
||||
# memory_limiter:
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
#
|
||||
# # 50% of the maximum memory
|
||||
# limit_percentage: 50
|
||||
# # 20% of max memory usage spike expected
|
||||
# spike_limit_percentage: 20
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
@@ -33,15 +53,19 @@ exporters:
|
||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||
resource_to_telemetry_conversion:
|
||||
enabled: true
|
||||
|
||||
prometheus:
|
||||
endpoint: "0.0.0.0:8889"
|
||||
service:
|
||||
extensions: [health_check, zpages]
|
||||
pipelines:
|
||||
traces:
|
||||
receivers: [jaeger, otlp]
|
||||
processors: [batch]
|
||||
processors: [signozspanmetrics/prometheus, batch]
|
||||
exporters: [clickhouse]
|
||||
metrics:
|
||||
receivers: [otlp]
|
||||
receivers: [otlp, hostmetrics]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite]
|
||||
exporters: [clickhousemetricswrite]
|
||||
metrics/spanmetrics:
|
||||
receivers: [otlp/spanmetrics]
|
||||
exporters: [prometheus]
|
||||
@@ -0,0 +1,47 @@
|
||||
receivers:
|
||||
otlp:
|
||||
protocols:
|
||||
grpc:
|
||||
http:
|
||||
|
||||
# Data sources: metrics
|
||||
prometheus:
|
||||
config:
|
||||
scrape_configs:
|
||||
- job_name: "otel-collector"
|
||||
scrape_interval: 30s
|
||||
static_configs:
|
||||
- targets: ["otel-collector:8889"]
|
||||
processors:
|
||||
batch:
|
||||
send_batch_size: 1000
|
||||
timeout: 10s
|
||||
# memory_limiter:
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
#
|
||||
# # 50% of the maximum memory
|
||||
# limit_percentage: 50
|
||||
# # 20% of max memory usage spike expected
|
||||
# spike_limit_percentage: 20
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
# retry_on_failure: true
|
||||
extensions:
|
||||
health_check: {}
|
||||
zpages: {}
|
||||
exporters:
|
||||
clickhousemetricswrite:
|
||||
endpoint: tcp://clickhouse:9000/?database=signoz_metrics
|
||||
|
||||
service:
|
||||
extensions: [health_check, zpages]
|
||||
pipelines:
|
||||
metrics:
|
||||
receivers: [otlp, prometheus]
|
||||
processors: [batch]
|
||||
exporters: [clickhousemetricswrite]
|
||||
@@ -9,12 +9,13 @@ alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
# - alertmanager:9093
|
||||
- alertmanager:9093
|
||||
|
||||
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
|
||||
rule_files:
|
||||
# - "first_rules.yml"
|
||||
# - "second_rules.yml"
|
||||
- 'alerts.yml'
|
||||
|
||||
# A scrape configuration containing exactly one endpoint to scrape:
|
||||
# Here it's Prometheus itself.
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
<?xml version="1.0"?>
|
||||
<yandex>
|
||||
<logger>
|
||||
<level>trace</level>
|
||||
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
|
||||
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
|
||||
<size>1000M</size>
|
||||
<count>10</count>
|
||||
<level>information</level>
|
||||
<console>1</console>
|
||||
</logger>
|
||||
|
||||
<http_port>8123</http_port>
|
||||
@@ -45,6 +42,34 @@
|
||||
</client>
|
||||
</openSSL>
|
||||
|
||||
<!-- Example config for tiered storage -->
|
||||
<!-- <storage_configuration>
|
||||
<disks>
|
||||
<default>
|
||||
<keep_free_space_bytes>10485760</keep_free_space_bytes>
|
||||
</default>
|
||||
<s3>
|
||||
<type>s3</type>
|
||||
<endpoint>https://BUCKET-NAME.s3.amazonaws.com/data/</endpoint>
|
||||
<access_key_id>ACCESS-KEY-ID</access_key_id>
|
||||
<secret_access_key>SECRET-ACCESS-KEY</secret_access_key>
|
||||
</s3>
|
||||
</disks>
|
||||
<policies>
|
||||
<tiered>
|
||||
<volumes>
|
||||
<default>
|
||||
<disk>default</disk>
|
||||
</default>
|
||||
<s3>
|
||||
<disk>s3</disk>
|
||||
</s3>
|
||||
</volumes>
|
||||
</tiered>
|
||||
</policies>
|
||||
</storage_configuration> -->
|
||||
|
||||
|
||||
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
|
||||
<!--
|
||||
<http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script src="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
|
||||
|
||||
0
deploy/docker/clickhouse-setup/data/signoz/.gitkeep
Normal file
0
deploy/docker/clickhouse-setup/data/signoz/.gitkeep
Normal file
@@ -3,9 +3,17 @@ version: "2.4"
|
||||
services:
|
||||
clickhouse:
|
||||
image: altinity/clickhouse-server:21.12.3.32.altinitydev.arm
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
restart: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
@@ -14,16 +22,19 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.5.0
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
query-service:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- '--config.file=/prometheus/alertmanager.yml'
|
||||
- '--storage.path=/data'
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.6.1
|
||||
image: signoz/query-service:0.7.5
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@@ -35,14 +46,24 @@ services:
|
||||
- STORAGE=clickhouse
|
||||
- GODEBUG=netdns=go
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-arm
|
||||
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.6.1
|
||||
image: signoz/frontend:0.7.5
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
ports:
|
||||
- "3301:3301"
|
||||
@@ -50,23 +71,32 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.5.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
- "4317:4317" # OTLP GRPC receiver
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||
mem_limit: 2000m
|
||||
restart: always
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.5.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
@@ -80,7 +110,7 @@ services:
|
||||
max-file: "3"
|
||||
command: ["all"]
|
||||
environment:
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
|
||||
|
||||
load-hotrod:
|
||||
image: "grubykarol/locust:1.2.3-python3.9-alpine3.12"
|
||||
|
||||
@@ -3,9 +3,17 @@ version: "2.4"
|
||||
services:
|
||||
clickhouse:
|
||||
image: yandex/clickhouse-server:21.12.3.32
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
# - "8123:8123"
|
||||
volumes:
|
||||
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
|
||||
- ./data/clickhouse/:/var/lib/clickhouse/
|
||||
restart: on-failure
|
||||
logging:
|
||||
options:
|
||||
max-size: 50m
|
||||
max-file: "3"
|
||||
healthcheck:
|
||||
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8123/ping"]
|
||||
@@ -14,19 +22,21 @@ services:
|
||||
retries: 3
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.5.0
|
||||
image: signoz/alertmanager:0.6.1
|
||||
volumes:
|
||||
- ./alertmanager.yml:/prometheus/alertmanager.yml
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
query-service:
|
||||
condition: service_healthy
|
||||
restart: on-failure
|
||||
command:
|
||||
- '--config.file=/prometheus/alertmanager.yml'
|
||||
- '--storage.path=/data'
|
||||
- --queryService.url=http://query-service:8080
|
||||
- --storage.path=/data
|
||||
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.6.1
|
||||
image: signoz/query-service:0.7.5
|
||||
container_name: query-service
|
||||
command: ["-config=/root/config/prometheus.yml"]
|
||||
volumes:
|
||||
@@ -38,14 +48,23 @@ services:
|
||||
- STORAGE=clickhouse
|
||||
- GODEBUG=netdns=go
|
||||
- TELEMETRY_ENABLED=true
|
||||
- DEPLOYMENT_TYPE=docker-standalone-amd
|
||||
restart: on-failure
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "-q", "localhost:8080/api/v1/version"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.6.1
|
||||
image: signoz/frontend:0.7.5
|
||||
container_name: frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- alertmanager
|
||||
- query-service
|
||||
ports:
|
||||
- "3301:3301"
|
||||
@@ -53,23 +72,32 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/otelcontribcol:0.5.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
|
||||
ports:
|
||||
- "4317:4317" # OTLP GRPC receiver
|
||||
- "4317:4317" # OTLP gRPC receiver
|
||||
- "4318:4318" # OTLP HTTP receiver
|
||||
# - "8889:8889" # Prometheus metrics exposed by the agent
|
||||
# - "13133" # health_check
|
||||
# - "14268:14268" # Jaeger receiver
|
||||
# - "55678:55678" # OpenCensus receiver
|
||||
# - "55679:55679" # zpages extension
|
||||
# - "55680:55680" # OTLP gRPC legacy receiver
|
||||
# - "55681:55681" # OTLP HTTP legacy receiver
|
||||
mem_limit: 2000m
|
||||
restart: always
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/otelcontribcol:0.5.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml", "--mem-ballast-size-mib=683"]
|
||||
image: signoz/otelcontribcol:0.43.0
|
||||
command: ["--config=/etc/otel-collector-metrics-config.yaml"]
|
||||
volumes:
|
||||
- ./otel-collector-metrics-config.yaml:/etc/otel-collector-metrics-config.yaml
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -27,14 +27,18 @@ processors:
|
||||
signozspanmetrics/prometheus:
|
||||
metrics_exporter: prometheus
|
||||
latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 50ms, 100ms, 250ms, 500ms, 1000ms, 1400ms, 2000ms, 5s, 10s, 20s, 40s, 60s ]
|
||||
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
|
||||
dimensions_cache_size: 10000
|
||||
# memory_limiter:
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
#
|
||||
# # 50% of the maximum memory
|
||||
# limit_percentage: 50
|
||||
# # 20% of max memory usage spike expected
|
||||
# spike_limit_percentage: 20
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
|
||||
@@ -16,14 +16,17 @@ processors:
|
||||
batch:
|
||||
send_batch_size: 1000
|
||||
timeout: 10s
|
||||
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
|
||||
# memory_limiter:
|
||||
# # 80% of maximum memory up to 2G
|
||||
# limit_mib: 1500
|
||||
# # 25% of limit up to 2G
|
||||
# spike_limit_mib: 512
|
||||
# check_interval: 5s
|
||||
#
|
||||
# # 50% of the maximum memory
|
||||
# limit_percentage: 50
|
||||
# # 20% of max memory usage spike expected
|
||||
# spike_limit_percentage: 20
|
||||
# queued_retry:
|
||||
# num_workers: 4
|
||||
# queue_size: 100
|
||||
|
||||
@@ -102,7 +102,7 @@ check_os() {
|
||||
# The script should error out in case they aren't available
|
||||
check_ports_occupied() {
|
||||
local port_check_output
|
||||
local ports_pattern="80|3301|8080"
|
||||
local ports_pattern="3301|4317"
|
||||
|
||||
if is_mac; then
|
||||
port_check_output="$(netstat -anp tcp | awk '$6 == "LISTEN" && $4 ~ /^.*\.('"$ports_pattern"')$/')"
|
||||
@@ -119,7 +119,7 @@ check_ports_occupied() {
|
||||
send_event "port_not_available"
|
||||
|
||||
echo "+++++++++++ ERROR ++++++++++++++++++++++"
|
||||
echo "SigNoz requires ports 80 & 443 to be open. Please shut down any other service(s) that may be running on these ports."
|
||||
echo "SigNoz requires ports 3301 & 4317 to be open. Please shut down any other service(s) that may be running on these ports."
|
||||
echo "You can run SigNoz on another port following this guide https://signoz.io/docs/deployment/docker#troubleshooting"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++"
|
||||
echo ""
|
||||
@@ -133,58 +133,44 @@ install_docker() {
|
||||
|
||||
|
||||
if [[ $package_manager == apt-get ]]; then
|
||||
apt_cmd="sudo apt-get --yes --quiet"
|
||||
apt_cmd="$sudo_cmd apt-get --yes --quiet"
|
||||
$apt_cmd update
|
||||
$apt_cmd install software-properties-common gnupg-agent
|
||||
curl -fsSL "https://download.docker.com/linux/$os/gpg" | sudo apt-key add -
|
||||
sudo add-apt-repository \
|
||||
curl -fsSL "https://download.docker.com/linux/$os/gpg" | $sudo_cmd apt-key add -
|
||||
$sudo_cmd add-apt-repository \
|
||||
"deb [arch=amd64] https://download.docker.com/linux/$os $(lsb_release -cs) stable"
|
||||
$apt_cmd update
|
||||
echo "Installing docker"
|
||||
$apt_cmd install docker-ce docker-ce-cli containerd.io
|
||||
elif [[ $package_manager == zypper ]]; then
|
||||
zypper_cmd="sudo zypper --quiet --no-gpg-checks --non-interactive"
|
||||
zypper_cmd="$sudo_cmd zypper --quiet --no-gpg-checks --non-interactive"
|
||||
echo "Installing docker"
|
||||
if [[ $os == sles ]]; then
|
||||
os_sp="$(cat /etc/*-release | awk -F= '$1 == "VERSION_ID" { gsub(/"/, ""); print $2; exit }')"
|
||||
os_arch="$(uname -i)"
|
||||
sudo SUSEConnect -p sle-module-containers/$os_sp/$os_arch -r ''
|
||||
SUSEConnect -p sle-module-containers/$os_sp/$os_arch -r ''
|
||||
fi
|
||||
$zypper_cmd install docker docker-runc containerd
|
||||
sudo systemctl enable docker.service
|
||||
$sudo_cmd systemctl enable docker.service
|
||||
elif [[ $package_manager == yum && $os == 'amazon linux' ]]; then
|
||||
echo
|
||||
echo "Amazon Linux detected ... "
|
||||
echo
|
||||
# sudo yum install docker
|
||||
# sudo service docker start
|
||||
sudo amazon-linux-extras install docker
|
||||
# yum install docker
|
||||
# service docker start
|
||||
$sudo_cmd yum install -y amazon-linux-extras
|
||||
$sudo_cmd amazon-linux-extras enable docker
|
||||
$sudo_cmd yum install -y docker
|
||||
else
|
||||
|
||||
yum_cmd="sudo yum --assumeyes --quiet"
|
||||
yum_cmd="$sudo_cmd yum --assumeyes --quiet"
|
||||
$yum_cmd install yum-utils
|
||||
sudo yum-config-manager --add-repo https://download.docker.com/linux/$os/docker-ce.repo
|
||||
$sudo_cmd yum-config-manager --add-repo https://download.docker.com/linux/$os/docker-ce.repo
|
||||
echo "Installing docker"
|
||||
$yum_cmd install docker-ce docker-ce-cli containerd.io
|
||||
|
||||
fi
|
||||
|
||||
}
|
||||
install_docker_machine() {
|
||||
|
||||
echo "\nInstalling docker machine ..."
|
||||
|
||||
if [[ $os == "Mac" ]];then
|
||||
curl -sL https://github.com/docker/machine/releases/download/v0.16.2/docker-machine-`uname -s`-`uname -m` >/usr/local/bin/docker-machine
|
||||
chmod +x /usr/local/bin/docker-machine
|
||||
else
|
||||
curl -sL https://github.com/docker/machine/releases/download/v0.16.2/docker-machine-`uname -s`-`uname -m` >/tmp/docker-machine
|
||||
chmod +x /tmp/docker-machine
|
||||
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine
|
||||
|
||||
fi
|
||||
|
||||
|
||||
}
|
||||
|
||||
install_docker_compose() {
|
||||
@@ -192,9 +178,9 @@ install_docker_compose() {
|
||||
if [[ ! -f /usr/bin/docker-compose ]];then
|
||||
echo "++++++++++++++++++++++++"
|
||||
echo "Installing docker-compose"
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
|
||||
$sudo_cmd curl -L "https://github.com/docker/compose/releases/download/1.26.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
$sudo_cmd chmod +x /usr/local/bin/docker-compose
|
||||
$sudo_cmd ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
|
||||
echo "docker-compose installed!"
|
||||
echo ""
|
||||
fi
|
||||
@@ -210,16 +196,23 @@ install_docker_compose() {
|
||||
}
|
||||
|
||||
start_docker() {
|
||||
echo "Starting Docker ..."
|
||||
if [ $os = "Mac" ]; then
|
||||
echo -e "🐳 Starting Docker ...\n"
|
||||
if [[ $os == "Mac" ]]; then
|
||||
open --background -a Docker && while ! docker system info > /dev/null 2>&1; do sleep 1; done
|
||||
else
|
||||
if ! sudo systemctl is-active docker.service > /dev/null; then
|
||||
if ! $sudo_cmd systemctl is-active docker.service > /dev/null; then
|
||||
echo "Starting docker service"
|
||||
sudo systemctl start docker.service
|
||||
$sudo_cmd systemctl start docker.service
|
||||
fi
|
||||
if [[ -z $sudo_cmd ]]; then
|
||||
docker ps > /dev/null && true
|
||||
if [[ $? -ne 0 ]]; then
|
||||
request_sudo
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
wait_for_containers_start() {
|
||||
local timeout=$1
|
||||
|
||||
@@ -229,16 +222,6 @@ wait_for_containers_start() {
|
||||
if [[ status_code -eq 200 ]]; then
|
||||
break
|
||||
else
|
||||
if [ $setup_type == 'druid' ]; then
|
||||
SUPERVISORS="$(curl -so - http://localhost:8888/druid/indexer/v1/supervisor)"
|
||||
LEN_SUPERVISORS="${#SUPERVISORS}"
|
||||
|
||||
if [[ LEN_SUPERVISORS -ne 19 && $timeout -eq 50 ]];then
|
||||
echo -e "\n🟠 Supervisors taking time to start ⏳ ... let's wait for some more time ⏱️\n\n"
|
||||
sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml up -d
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -ne "Waiting for all containers to start. This check will timeout in $timeout seconds ...\r\c"
|
||||
fi
|
||||
((timeout--))
|
||||
@@ -249,31 +232,30 @@ wait_for_containers_start() {
|
||||
}
|
||||
|
||||
bye() { # Prints a friendly good bye message and exits the script.
|
||||
if [ "$?" -ne 0 ]; then
|
||||
if [[ "$?" -ne 0 ]]; then
|
||||
set +o errexit
|
||||
|
||||
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
||||
echo ""
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
if is_arm64; then
|
||||
echo -e "sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml ps -a"
|
||||
else
|
||||
echo -e "sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||
fi
|
||||
else
|
||||
echo -e "sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml ps -a"
|
||||
if is_arm64; then
|
||||
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml ps -a"
|
||||
else
|
||||
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||
fi
|
||||
|
||||
# echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker#troubleshooting"
|
||||
echo "or reach us for support in #help channel in our Slack Community https://signoz.io/slack"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
echo -e "\n📨 Please share your email to receive support with the installation"
|
||||
read -rp 'Email: ' email
|
||||
|
||||
while [[ $email == "" ]]
|
||||
do
|
||||
if [[ $email == "" ]]; then
|
||||
echo -e "\n📨 Please share your email to receive support with the installation"
|
||||
read -rp 'Email: ' email
|
||||
done
|
||||
|
||||
while [[ $email == "" ]]
|
||||
do
|
||||
read -rp 'Email: ' email
|
||||
done
|
||||
fi
|
||||
|
||||
send_event "installation_support"
|
||||
|
||||
@@ -284,26 +266,67 @@ bye() { # Prints a friendly good bye message and exits the script.
|
||||
fi
|
||||
}
|
||||
|
||||
request_sudo() {
|
||||
if hash sudo 2>/dev/null; then
|
||||
echo -e "\n\n🙇 We will need sudo access to complete the installation."
|
||||
if (( $EUID != 0 )); then
|
||||
sudo_cmd="sudo"
|
||||
echo -e "Please enter your sudo password, if prompt."
|
||||
$sudo_cmd -l | grep -e "NOPASSWD: ALL" > /dev/null
|
||||
if [[ $? -ne 0 ]] && ! $sudo_cmd -v; then
|
||||
echo "Need sudo privileges to proceed with the installation."
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
echo -e "Got it! Thanks!! 🙏\n"
|
||||
echo -e "Okay! We will bring up the SigNoz cluster from here 🚀\n"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo -e "👋 Thank you for trying out SigNoz! "
|
||||
echo ""
|
||||
|
||||
sudo_cmd=""
|
||||
|
||||
# Check sudo permissions
|
||||
if (( $EUID != 0 )); then
|
||||
echo "🟡 Running installer with non-sudo permissions."
|
||||
echo " In case of any failure or prompt, please consider running the script with sudo privileges."
|
||||
echo ""
|
||||
else
|
||||
sudo_cmd="sudo"
|
||||
fi
|
||||
|
||||
# Checking OS and assigning package manager
|
||||
desired_os=0
|
||||
os=""
|
||||
email=""
|
||||
echo -e "Detecting your OS ..."
|
||||
echo -e "🌏 Detecting your OS ...\n"
|
||||
check_os
|
||||
|
||||
# Obtain unique installation id
|
||||
sysinfo="$(uname -a)"
|
||||
if [ $? -ne 0 ]; then
|
||||
if [[ $? -ne 0 ]]; then
|
||||
uuid="$(uuidgen)"
|
||||
uuid="${uuid:-$(cat /proc/sys/kernel/random/uuid)}"
|
||||
SIGNOZ_INSTALLATION_ID="${uuid:-$(cat /proc/sys/kernel/random/uuid)}"
|
||||
sysinfo="${uuid:-$(cat /proc/sys/kernel/random/uuid)}"
|
||||
fi
|
||||
|
||||
digest_cmd=""
|
||||
if hash shasum 2>/dev/null; then
|
||||
digest_cmd="shasum -a 256"
|
||||
elif hash sha256sum 2>/dev/null; then
|
||||
digest_cmd="sha256sum"
|
||||
elif hash openssl 2>/dev/null; then
|
||||
digest_cmd="openssl dgst -sha256"
|
||||
fi
|
||||
|
||||
if [[ -z $digest_cmd ]]; then
|
||||
SIGNOZ_INSTALLATION_ID="$sysinfo"
|
||||
else
|
||||
SIGNOZ_INSTALLATION_ID=$(echo "$sysinfo" | shasum | cut -d ' ' -f1)
|
||||
SIGNOZ_INSTALLATION_ID=$(echo "$sysinfo" | $digest_cmd | grep -E -o '[a-zA-Z0-9]{64}')
|
||||
fi
|
||||
|
||||
# echo ""
|
||||
@@ -364,13 +387,7 @@ send_event() {
|
||||
'installation_error_checks')
|
||||
event="Installation Error - Checks"
|
||||
error="Containers not started"
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
others='"data": "some_checks",'
|
||||
else
|
||||
supervisors="$(curl -so - http://localhost:8888/druid/indexer/v1/supervisor)"
|
||||
datasources="$(curl -so - http://localhost:8888/druid/coordinator/v1/datasources)"
|
||||
others='"supervisors": "'"$supervisors"'", "datasources": "'"$datasources"'",'
|
||||
fi
|
||||
others='"data": "some_checks",'
|
||||
;;
|
||||
'installation_support')
|
||||
event="Installation Support"
|
||||
@@ -389,7 +406,7 @@ send_event() {
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ "$error" != "" ]; then
|
||||
if [[ "$error" != "" ]]; then
|
||||
error='"error": "'"$error"'", '
|
||||
fi
|
||||
|
||||
@@ -412,15 +429,28 @@ fi
|
||||
|
||||
# Check is Docker daemon is installed and available. If not, the install & start Docker for Linux machines. We cannot automatically install Docker Desktop on Mac OS
|
||||
if ! is_command_present docker; then
|
||||
|
||||
if [[ $package_manager == "apt-get" || $package_manager == "zypper" || $package_manager == "yum" ]]; then
|
||||
request_sudo
|
||||
install_docker
|
||||
else
|
||||
# enable docker without sudo from next reboot
|
||||
sudo usermod -aG docker "${USER}"
|
||||
elif is_mac; then
|
||||
echo ""
|
||||
echo "+++++++++++ IMPORTANT READ ++++++++++++++++++++++"
|
||||
echo "Docker Desktop must be installed manually on Mac OS to proceed. Docker can only be installed automatically on Ubuntu / openSUSE / SLES / Redhat / Cent OS"
|
||||
echo "https://docs.docker.com/docker-for-mac/install/"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
send_event "docker_not_installed"
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "+++++++++++ IMPORTANT READ ++++++++++++++++++++++"
|
||||
echo "Docker must be installed manually on your machine to proceed. Docker can only be installed automatically on Ubuntu / openSUSE / SLES / Redhat / Cent OS"
|
||||
echo "https://docs.docker.com/get-docker/"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++++++++++"
|
||||
|
||||
send_event "docker_not_installed"
|
||||
exit 1
|
||||
fi
|
||||
@@ -428,42 +458,32 @@ fi
|
||||
|
||||
# Install docker-compose
|
||||
if ! is_command_present docker-compose; then
|
||||
request_sudo
|
||||
install_docker_compose
|
||||
fi
|
||||
|
||||
|
||||
start_docker
|
||||
|
||||
|
||||
# sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up -d --remove-orphans || true
|
||||
# $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up -d --remove-orphans || true
|
||||
|
||||
|
||||
echo ""
|
||||
echo -e "\n🟡 Pulling the latest container images for SigNoz. To run as sudo it may ask for system password\n"
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
if is_arm64; then
|
||||
sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml pull
|
||||
else
|
||||
sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml pull
|
||||
fi
|
||||
echo -e "\n🟡 Pulling the latest container images for SigNoz.\n"
|
||||
if is_arm64; then
|
||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml pull
|
||||
else
|
||||
sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml pull
|
||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml pull
|
||||
fi
|
||||
|
||||
|
||||
echo ""
|
||||
echo "🟡 Starting the SigNoz containers. It may take a few minutes ..."
|
||||
echo
|
||||
# The docker-compose command does some nasty stuff for the `--detach` functionality. So we add a `|| true` so that the
|
||||
# script doesn't exit because this command looks like it failed to do it's thing.
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
if is_arm64; then
|
||||
sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml up --detach --remove-orphans || true
|
||||
else
|
||||
sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up --detach --remove-orphans || true
|
||||
fi
|
||||
if is_arm64; then
|
||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml up --detach --remove-orphans || true
|
||||
else
|
||||
sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml up --detach --remove-orphans || true
|
||||
$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml up --detach --remove-orphans || true
|
||||
fi
|
||||
|
||||
wait_for_containers_start 60
|
||||
@@ -473,11 +493,9 @@ if [[ $status_code -ne 200 ]]; then
|
||||
echo "+++++++++++ ERROR ++++++++++++++++++++++"
|
||||
echo "🔴 The containers didn't seem to start correctly. Please run the following command to check containers that may have errored out:"
|
||||
echo ""
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
echo -e "sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||
else
|
||||
echo -e "sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml ps -a"
|
||||
fi
|
||||
|
||||
echo -e "$sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml ps -a"
|
||||
|
||||
echo "Please read our troubleshooting guide https://signoz.io/docs/deployment/docker/#troubleshooting-of-common-issues"
|
||||
echo "or reach us on SigNoz for support https://signoz.io/slack"
|
||||
echo "++++++++++++++++++++++++++++++++++++++++"
|
||||
@@ -495,14 +513,10 @@ else
|
||||
echo -e "🟢 Your frontend is running on http://localhost:3301"
|
||||
echo ""
|
||||
|
||||
if [ $setup_type == 'clickhouse' ]; then
|
||||
if is_arm64; then
|
||||
echo "ℹ️ To bring down SigNoz and clean volumes : sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml down -v"
|
||||
else
|
||||
echo "ℹ️ To bring down SigNoz and clean volumes : sudo docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml down -v"
|
||||
fi
|
||||
if is_arm64; then
|
||||
echo "ℹ️ To bring down SigNoz and clean volumes : $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.arm.yaml down -v"
|
||||
else
|
||||
echo "ℹ️ To bring down SigNoz and clean volumes : sudo docker-compose -f ./docker/druid-kafka-setup/docker-compose-tiny.yaml down -v"
|
||||
echo "ℹ️ To bring down SigNoz and clean volumes : $sudo_cmd docker-compose -f ./docker/clickhouse-setup/docker-compose.yaml down -v"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
build
|
||||
build
|
||||
|
||||
@@ -3,16 +3,23 @@ module.exports = {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
'jest/globals': true,
|
||||
},
|
||||
extends: [
|
||||
'airbnb',
|
||||
'airbnb-typescript',
|
||||
'eslint:recommended',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:sonarjs/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
@@ -25,10 +32,17 @@ module.exports = {
|
||||
'simple-import-sort',
|
||||
'react-hooks',
|
||||
'prettier',
|
||||
'jest',
|
||||
],
|
||||
settings: {
|
||||
react: {
|
||||
version: 'latest',
|
||||
version: 'detect',
|
||||
},
|
||||
'import/resolver': {
|
||||
node: {
|
||||
paths: ['src'],
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
@@ -40,9 +54,13 @@ module.exports = {
|
||||
],
|
||||
'react/prop-types': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
'@typescript-eslint/no-var-requires': 0,
|
||||
'react/no-array-index-key': 2,
|
||||
'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'],
|
||||
'@typescript-eslint/no-var-requires': 'error',
|
||||
'react/no-array-index-key': 'error',
|
||||
'linebreak-style': [
|
||||
'error',
|
||||
process.platform === 'win32' ? 'windows' : 'unix',
|
||||
],
|
||||
'@typescript-eslint/default-param-last': 'off',
|
||||
|
||||
// simple sort error
|
||||
'simple-import-sort/imports': 'error',
|
||||
@@ -50,7 +68,46 @@ module.exports = {
|
||||
|
||||
// hooks
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'react-hooks/exhaustive-deps': 'error',
|
||||
|
||||
// airbnb
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-console': 'off',
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
'import/no-extraneous-dependencies': ['error', { devDependencies: true }],
|
||||
'jsx-a11y/label-has-associated-control': [
|
||||
'error',
|
||||
{
|
||||
required: {
|
||||
some: ['nesting', 'id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
'jsx-a11y/label-has-for': [
|
||||
'error',
|
||||
{
|
||||
required: {
|
||||
some: ['nesting', 'id'],
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
// eslint rules need to remove
|
||||
'no-shadow': 'off',
|
||||
'@typescript-eslint/no-shadow': 'off',
|
||||
'global-require': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
|
||||
4
frontend/.husky/pre-commit
Executable file
4
frontend/.husky/pre-commit
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
cd frontend && yarn lint-staged
|
||||
@@ -1,5 +1,5 @@
|
||||
# stage1 as builder
|
||||
FROM node:12.18.0 as builder
|
||||
FROM node:12.22.0 as builder
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
||||
6
frontend/babel.config.js
Normal file
6
frontend/babel.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
11
frontend/cypress/CustomFunctions/uncaughtExpection.ts
Normal file
11
frontend/cypress/CustomFunctions/uncaughtExpection.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
const resizeObserverLoopErrRe = /ResizeObserver loop limit exceeded/;
|
||||
|
||||
const unCaughtExpection = (): void => {
|
||||
cy.on('uncaught:exception', (err) => {
|
||||
// returning false here prevents Cypress from
|
||||
// failing the test
|
||||
return !resizeObserverLoopErrRe.test(err.message);
|
||||
});
|
||||
};
|
||||
|
||||
export default unCaughtExpection;
|
||||
35
frontend/cypress/fixtures/trace/initialAggregates.json
Normal file
35
frontend/cypress/fixtures/trace/initialAggregates.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"items": {
|
||||
"1644926280000000000": { "timestamp": 1644926280000000000, "value": 787 },
|
||||
"1644926340000000000": { "timestamp": 1644926340000000000, "value": 2798 },
|
||||
"1644926400000000000": { "timestamp": 1644926400000000000, "value": 2828 },
|
||||
"1644926460000000000": { "timestamp": 1644926460000000000, "value": 2926 },
|
||||
"1644926520000000000": { "timestamp": 1644926520000000000, "value": 2932 },
|
||||
"1644926580000000000": { "timestamp": 1644926580000000000, "value": 2842 },
|
||||
"1644926640000000000": { "timestamp": 1644926640000000000, "value": 2966 },
|
||||
"1644926700000000000": { "timestamp": 1644926700000000000, "value": 2782 },
|
||||
"1644926760000000000": { "timestamp": 1644926760000000000, "value": 2843 },
|
||||
"1644926820000000000": { "timestamp": 1644926820000000000, "value": 2864 },
|
||||
"1644926880000000000": { "timestamp": 1644926880000000000, "value": 2777 },
|
||||
"1644926940000000000": { "timestamp": 1644926940000000000, "value": 2820 },
|
||||
"1644927000000000000": { "timestamp": 1644927000000000000, "value": 2579 },
|
||||
"1644927060000000000": { "timestamp": 1644927060000000000, "value": 2681 },
|
||||
"1644927120000000000": { "timestamp": 1644927120000000000, "value": 2828 },
|
||||
"1644927180000000000": { "timestamp": 1644927180000000000, "value": 2975 },
|
||||
"1644927240000000000": { "timestamp": 1644927240000000000, "value": 2934 },
|
||||
"1644927300000000000": { "timestamp": 1644927300000000000, "value": 2793 },
|
||||
"1644927360000000000": { "timestamp": 1644927360000000000, "value": 2913 },
|
||||
"1644927420000000000": { "timestamp": 1644927420000000000, "value": 2621 },
|
||||
"1644927480000000000": { "timestamp": 1644927480000000000, "value": 2631 },
|
||||
"1644927540000000000": { "timestamp": 1644927540000000000, "value": 2924 },
|
||||
"1644927600000000000": { "timestamp": 1644927600000000000, "value": 2576 },
|
||||
"1644927660000000000": { "timestamp": 1644927660000000000, "value": 2878 },
|
||||
"1644927720000000000": { "timestamp": 1644927720000000000, "value": 2737 },
|
||||
"1644927780000000000": { "timestamp": 1644927780000000000, "value": 2621 },
|
||||
"1644927840000000000": { "timestamp": 1644927840000000000, "value": 2823 },
|
||||
"1644927900000000000": { "timestamp": 1644927900000000000, "value": 3081 },
|
||||
"1644927960000000000": { "timestamp": 1644927960000000000, "value": 2883 },
|
||||
"1644928020000000000": { "timestamp": 1644928020000000000, "value": 2823 },
|
||||
"1644928080000000000": { "timestamp": 1644928080000000000, "value": 455 }
|
||||
}
|
||||
}
|
||||
19
frontend/cypress/fixtures/trace/initialSpanFilter.json
Normal file
19
frontend/cypress/fixtures/trace/initialSpanFilter.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"serviceName": {
|
||||
"customer": 1642,
|
||||
"driver": 1642,
|
||||
"frontend": 39408,
|
||||
"mysql": 1642,
|
||||
"redis": 22167,
|
||||
"route": 16420
|
||||
},
|
||||
"status": { "error": 4105, "ok": 78816 },
|
||||
"duration": { "maxDuration": 1253979000, "minDuration": 415000 },
|
||||
"operation": {},
|
||||
"httpCode": {},
|
||||
"httpUrl": {},
|
||||
"httpMethod": {},
|
||||
"httpRoute": {},
|
||||
"httpHost": {},
|
||||
"component": {}
|
||||
}
|
||||
105
frontend/cypress/fixtures/trace/initialSpans.json
Normal file
105
frontend/cypress/fixtures/trace/initialSpans.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"spans": [
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:09.542074Z",
|
||||
"spanID": "303b39065c6f5df5",
|
||||
"traceID": "00000000000000007fc49fab3cb75958",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 313418000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:08.84038Z",
|
||||
"spanID": "557e8303bc802992",
|
||||
"traceID": "000000000000000079310bd1d435a92b",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 318203000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:08.867689Z",
|
||||
"spanID": "347113dd916dd20e",
|
||||
"traceID": "00000000000000004c22c0409cee0f66",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 512810000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:07.060882Z",
|
||||
"spanID": "0a8d07f72aa1339b",
|
||||
"traceID": "0000000000000000488e11a35959de96",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 588705000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:07.134107Z",
|
||||
"spanID": "0acd4ec344675998",
|
||||
"traceID": "00000000000000000292efc7945d9bfa",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 801632000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:06.474095Z",
|
||||
"spanID": "3ae72e433301822a",
|
||||
"traceID": "00000000000000001ac3004ff1b7eefe",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 306650000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:06.996246Z",
|
||||
"spanID": "1d765427af673039",
|
||||
"traceID": "00000000000000002e78f59fabbcdecf",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 311469000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:05.324296Z",
|
||||
"spanID": "0987c90d83298a1d",
|
||||
"traceID": "0000000000000000077bcb960609a350",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 290680000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:02.458221Z",
|
||||
"spanID": "5b0d0d403dd9acf4",
|
||||
"traceID": "00000000000000007ae5b0aa69242556",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 262763000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
},
|
||||
{
|
||||
"timestamp": "2022-02-15T12:16:00.584939Z",
|
||||
"spanID": "3beafb277a76b9b4",
|
||||
"traceID": "00000000000000000ab44953c2fd949e",
|
||||
"serviceName": "customer",
|
||||
"operation": "HTTP GET /customer",
|
||||
"durationNano": 302851000,
|
||||
"httpCode": "200",
|
||||
"httpMethod": "GET"
|
||||
}
|
||||
],
|
||||
"totalSpans": 82921
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import ROUTES from 'constants/routes';
|
||||
|
||||
import defaultRules from '../../fixtures/defaultRules.json';
|
||||
|
||||
const defaultRuleRoutes = `**/rules/**`;
|
||||
|
||||
describe('Alerts', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
@@ -21,7 +23,7 @@ describe('Alerts', () => {
|
||||
|
||||
it('Edit Rules Page Failure', async () => {
|
||||
cy
|
||||
.intercept('**/rules/**', {
|
||||
.intercept(defaultRuleRoutes, {
|
||||
statusCode: 500,
|
||||
})
|
||||
.as('Get Rules Error');
|
||||
@@ -49,7 +51,7 @@ describe('Alerts', () => {
|
||||
const text = 'this is the sample value';
|
||||
|
||||
cy
|
||||
.intercept('**/rules/**', {
|
||||
.intercept(defaultRuleRoutes, {
|
||||
statusCode: 200,
|
||||
body: {
|
||||
data: {
|
||||
@@ -103,7 +105,7 @@ describe('Alerts', () => {
|
||||
|
||||
it('Rules are Deleted', async () => {
|
||||
cy
|
||||
.intercept('**/rules/**', {
|
||||
.intercept(defaultRuleRoutes, {
|
||||
body: {
|
||||
data: 'Deleted',
|
||||
message: 'Success',
|
||||
|
||||
160
frontend/cypress/integration/trace/index.spec.ts
Normal file
160
frontend/cypress/integration/trace/index.spec.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/* eslint-disable sonarjs/no-duplicate-string */
|
||||
import ROUTES from 'constants/routes';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { TraceFilterEnum } from 'types/reducer/trace';
|
||||
|
||||
import GraphInitialResponse from '../../fixtures/trace/initialAggregates.json';
|
||||
import FilterInitialResponse from '../../fixtures/trace/initialSpanFilter.json';
|
||||
import TableInitialResponse from '../../fixtures/trace/initialSpans.json';
|
||||
|
||||
const allFilters = '@Filters.all';
|
||||
const allGraphs = '@Graph.all';
|
||||
const allTable = '@Table.all';
|
||||
|
||||
describe('Trace', () => {
|
||||
beforeEach(() => {
|
||||
window.localStorage.setItem('isLoggedIn', 'yes');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/aggregates', {
|
||||
fixture: 'trace/initialAggregates',
|
||||
})
|
||||
.as('Graph');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/getFilteredSpans', {
|
||||
fixture: 'trace/initialSpans',
|
||||
})
|
||||
.as('Table');
|
||||
|
||||
cy
|
||||
.intercept('POST', '**/api/v1/getSpanFilters', {
|
||||
fixture: 'trace/initialSpanFilter',
|
||||
})
|
||||
.as('Filters');
|
||||
|
||||
cy.visit(Cypress.env('baseUrl') + `${ROUTES.TRACE}`);
|
||||
});
|
||||
|
||||
it('First Initial Load should go with 3 AJAX request', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
|
||||
const { body: filterBody } = filter.request;
|
||||
const { body: graphBody } = graph.request;
|
||||
const { body: tableBody } = table.request;
|
||||
|
||||
expect(filterBody.exclude.length).to.equal(0);
|
||||
expect(filterBody.getFilters.length).to.equal(3);
|
||||
filterBody.getFilters.forEach((filter: TraceFilterEnum) => {
|
||||
expect(filter).to.be.oneOf(['duration', 'status', 'serviceName']);
|
||||
});
|
||||
|
||||
expect(graphBody.function).to.be.equal('count');
|
||||
expect(graphBody.exclude.length).to.be.equal(0);
|
||||
expect(typeof graphBody.exclude).to.be.equal('object');
|
||||
|
||||
expect(tableBody.tags.length).to.be.equal(0);
|
||||
expect(typeof tableBody.tags).equal('object');
|
||||
|
||||
expect(tableBody.exclude.length).equals(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('Render Time Request Response In All 3 Request', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
|
||||
expect(filter.response?.body).to.be.not.undefined;
|
||||
expect(filter.response?.body).to.be.not.NaN;
|
||||
|
||||
expect(JSON.stringify(filter.response?.body)).to.be.equals(
|
||||
JSON.stringify(FilterInitialResponse),
|
||||
);
|
||||
|
||||
expect(JSON.stringify(graph.response?.body)).to.be.equals(
|
||||
JSON.stringify(GraphInitialResponse),
|
||||
);
|
||||
|
||||
expect(JSON.stringify(table.response?.body)).to.be.equals(
|
||||
JSON.stringify(TableInitialResponse),
|
||||
);
|
||||
});
|
||||
cy.get(allFilters).should('have.length', 1);
|
||||
cy.get(allGraphs).should('have.length', 1);
|
||||
cy.get(allTable).should('have.length', 1);
|
||||
});
|
||||
|
||||
it('Clear All', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
expect(cy.findAllByText('Clear All')).not.to.be.undefined;
|
||||
|
||||
cy
|
||||
.window()
|
||||
.its('store')
|
||||
.invoke('getState')
|
||||
.then((e: AppState) => {
|
||||
const { traces } = e;
|
||||
expect(traces.isFilterExclude.get('status')).to.be.undefined;
|
||||
expect(traces.selectedFilter.size).to.be.equals(0);
|
||||
});
|
||||
|
||||
cy.findAllByText('Clear All').then((e) => {
|
||||
const [firstStatusClear] = e;
|
||||
|
||||
firstStatusClear.click();
|
||||
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
// insuring the api get call
|
||||
cy.get(allFilters).should('have.length', 2);
|
||||
cy.get(allGraphs).should('have.length', 2);
|
||||
cy.get(allTable).should('have.length', 2);
|
||||
|
||||
cy
|
||||
.window()
|
||||
.its('store')
|
||||
.invoke('getState')
|
||||
.then((e: AppState) => {
|
||||
const { traces } = e;
|
||||
|
||||
expect(traces.isFilterExclude.get('status')).to.be.equals(false);
|
||||
expect(traces.userSelectedFilter.get('status')).to.be.undefined;
|
||||
expect(traces.selectedFilter.size).to.be.equals(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('Un Selecting one option from status', () => {
|
||||
cy.wait(['@Filters', '@Graph', '@Table']);
|
||||
|
||||
cy.get('input[type="checkbox"]').then((e) => {
|
||||
const [errorCheckbox] = e;
|
||||
errorCheckbox.click();
|
||||
|
||||
cy.wait(['@Filters', '@Graph', '@Table']).then((e) => {
|
||||
const [filter, graph, table] = e;
|
||||
const filterBody = filter.request.body;
|
||||
const graphBody = graph.request.body;
|
||||
const tableBody = table.request.body;
|
||||
|
||||
expect(filterBody.exclude).not.to.be.undefined;
|
||||
expect(filterBody.exclude.length).not.to.be.equal(0);
|
||||
expect(filterBody.exclude[0] === 'status').to.be.true;
|
||||
|
||||
expect(graphBody.exclude).not.to.be.undefined;
|
||||
expect(graphBody.exclude.length).not.to.be.equal(0);
|
||||
expect(graphBody.exclude[0] === 'status').to.be.true;
|
||||
|
||||
expect(tableBody.exclude).not.to.be.undefined;
|
||||
expect(tableBody.exclude.length).not.to.be.equal(0);
|
||||
expect(tableBody.exclude[0] === 'status').to.be.true;
|
||||
});
|
||||
|
||||
cy.get(allFilters).should('have.length', 2);
|
||||
cy.get(allGraphs).should('have.length', 2);
|
||||
cy.get(allTable).should('have.length', 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -9,12 +9,19 @@ const config: Config.InitialOptions = {
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
},
|
||||
notify: true,
|
||||
notifyMode: 'always',
|
||||
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'],
|
||||
transform: {
|
||||
'\\.(js|jsx|ts|tsx)?$': 'babel-jest',
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
'ts-jest': {
|
||||
useESM: true,
|
||||
},
|
||||
},
|
||||
testMatch: ['<rootDir>/src/**/?(*.)(test).(ts|js)?(x)'],
|
||||
preset: 'ts-jest/presets/js-with-ts-esm',
|
||||
transform: {
|
||||
'^.+\\.(ts|tsx)?$': 'ts-jest',
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: ['node_modules/(?!(lodash-es)/)'],
|
||||
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
moduleDirectories: ['node_modules', 'src'],
|
||||
|
||||
@@ -7,14 +7,15 @@
|
||||
"dev": "cross-env NODE_ENV=development webpack serve --progress",
|
||||
"build": "webpack --config=webpack.config.prod.js --progress",
|
||||
"prettify": "prettier --write .",
|
||||
"lint": "eslint . --debug",
|
||||
"lint:fix": "eslint . --fix --debug",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"cypress:open": "cypress open",
|
||||
"cypress:run": "cypress run",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
"bundle:size": "bundlesize"
|
||||
"postinstall": "yarn husky:configure",
|
||||
"husky:configure": "cd .. && husky install frontend/.husky"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
@@ -22,11 +23,15 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "^6.0.0",
|
||||
"@ant-design/icons": "^4.6.2",
|
||||
"@grafana/data": "^8.4.3",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@testing-library/jest-dom": "^5.11.4",
|
||||
"@testing-library/react": "^11.1.0",
|
||||
"@testing-library/user-event": "^12.1.10",
|
||||
"antd": "^4.16.13",
|
||||
"@welldone-software/why-did-you-render": "^6.2.1",
|
||||
"antd": "4.19.2",
|
||||
"axios": "^0.21.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.0",
|
||||
@@ -36,9 +41,11 @@
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"chart.js": "^3.4.0",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"color": "^4.2.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "4.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
"cypress": "^8.3.0",
|
||||
"d3": "^6.2.0",
|
||||
"d3-flame-graph": "^3.1.1",
|
||||
"d3-tip": "^0.9.1",
|
||||
@@ -47,21 +54,28 @@
|
||||
"file-loader": "6.1.1",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.1.0",
|
||||
"jest": "26.6.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
"jest": "^27.5.1",
|
||||
"less": "^4.1.2",
|
||||
"less-loader": "^10.2.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mini-css-extract-plugin": "2.4.5",
|
||||
"monaco-editor": "^0.30.0",
|
||||
"react": "17.0.0",
|
||||
"react-dom": "17.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-graph-vis": "^1.0.5",
|
||||
"react-grid-layout": "^1.2.5",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-vis": "^1.11.7",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"stream": "^0.0.2",
|
||||
"style-loader": "1.3.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
@@ -92,13 +106,17 @@
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@testing-library/cypress": "^8.0.0",
|
||||
"@testing-library/react-hooks": "^7.0.2",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/compression-webpack-plugin": "^9.0.0",
|
||||
"@types/copy-webpack-plugin": "^8.0.1",
|
||||
"@types/d3": "^6.2.0",
|
||||
"@types/d3-tip": "^3.5.5",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/lodash-es": "^4.17.4",
|
||||
"@types/mini-css-extract-plugin": "^2.5.1",
|
||||
"@types/node": "^16.10.3",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^16.9.9",
|
||||
@@ -113,32 +131,41 @@
|
||||
"@types/webpack-dev-server": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"@welldone-software/why-did-you-render": "^6.2.1",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"bundlesize": "^0.18.1",
|
||||
"compression-webpack-plugin": "^9.0.0",
|
||||
"copy-webpack-plugin": "^8.1.0",
|
||||
"critters-webpack-plugin": "^3.0.1",
|
||||
"cypress": "^8.3.0",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-config-airbnb-typescript": "^16.1.4",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.23.4",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jest": "^26.1.2",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"husky": "4.3.8",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"less-plugin-npm-import": "^2.1.0",
|
||||
"lint-staged": "10.5.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"lint-staged": "^12.3.7",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"ts-jest": "^27.1.4",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.5.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.(js|jsx|ts|tsx)": [
|
||||
"eslint --fix"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
27
frontend/public/locales/en/translation.json
Normal file
27
frontend/public/locales/en/translation.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"monitor_signup": "Monitor your applications. Find what is causing issues.",
|
||||
"version": "Version",
|
||||
"latest_version": "Latest version",
|
||||
"current_version": "Current version",
|
||||
"release_notes": "Release Notes",
|
||||
"read_how_to_upgrade": "Read instructions on how to upgrade",
|
||||
"latest_version_signoz": "You are running the latest version of SigNoz.",
|
||||
"stale_version": "You are on an older version and may be loosing on the latest features we have shipped. We recommend to upgrade to the latest version",
|
||||
"oops_something_went_wrong_version": "Oops.. facing issues with fetching updated version information",
|
||||
"n_a": "N/A",
|
||||
"routes": {
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels"
|
||||
},
|
||||
"settings": {
|
||||
"total_retention_period": "Total Retention Period",
|
||||
"move_to_s3": "Move to S3\n(should be lower than total retention period)",
|
||||
"retention_success_message": "Congrats. The retention periods for {{name}} has been updated successfully.",
|
||||
"retention_error_message": "There was an issue in changing the retention period for {{name}}. Please try again or reach out to support@signoz.io",
|
||||
"retention_failed_message": "There was an issue in changing the retention period. Please try again or reach out to support@signoz.io",
|
||||
"retention_comparison_error": "Total retention period for {{name}} can’t be lower or equal to the period after which data is moved to s3.",
|
||||
"retention_null_value_error": "Retention Period for {{name}} is not set yet. Please set by choosing below",
|
||||
"retention_confirmation": "Are you sure you want to change the retention period?",
|
||||
"retention_confirmation_description": "This will change the amount of storage needed for saving metrics & traces."
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,7 @@ import AppReducer from 'types/reducer/app';
|
||||
|
||||
import routes from './routes';
|
||||
|
||||
|
||||
const App = (): JSX.Element => {
|
||||
function App(): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
|
||||
return (
|
||||
@@ -20,8 +19,8 @@ const App = (): JSX.Element => {
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }, index) => (
|
||||
<Route key={index} exact={exact} path={path} component={component} />
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route key={`${path}`} exact={exact} path={path} component={component} />
|
||||
))}
|
||||
<Route
|
||||
path="/"
|
||||
@@ -40,7 +39,6 @@ const App = (): JSX.Element => {
|
||||
</AppLayout>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
@@ -18,15 +18,12 @@ export const ServiceMapPage = Loadable(
|
||||
),
|
||||
);
|
||||
|
||||
export const TraceDetailPages = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetailPage" */ 'pages/Trace'),
|
||||
export const TraceFilter = Loadable(
|
||||
() => import(/* webpackChunkName: "Trace Filter Page" */ 'pages/Trace'),
|
||||
);
|
||||
|
||||
export const TraceGraphPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "TraceGraphPage" */ 'modules/Traces/TraceGraphDef'
|
||||
),
|
||||
export const TraceDetail = Loadable(
|
||||
() => import(/* webpackChunkName: "TraceDetail Page" */ 'pages/TraceDetail'),
|
||||
);
|
||||
|
||||
export const UsageExplorerPage = Loadable(
|
||||
@@ -88,3 +85,7 @@ export const EditAlertChannelsAlerts = Loadable(
|
||||
export const AllAlertChannels = Loadable(
|
||||
() => import(/* webpackChunkName: "All Channels" */ 'pages/AllAlertChannels'),
|
||||
);
|
||||
|
||||
export const StatusPage = Loadable(
|
||||
() => import(/* webpackChunkName: "All Status" */ 'pages/Status'),
|
||||
);
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
ServicesTablePage,
|
||||
SettingsPage,
|
||||
SignupPage,
|
||||
TraceDetailPages,
|
||||
TraceGraphPage,
|
||||
StatusPage,
|
||||
TraceDetail,
|
||||
TraceFilter,
|
||||
UsageExplorerPage,
|
||||
} from './pageComponents';
|
||||
|
||||
@@ -44,9 +45,9 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.TRACE_GRAPH,
|
||||
path: ROUTES.TRACE_DETAIL,
|
||||
exact: true,
|
||||
component: TraceGraphPage,
|
||||
component: TraceDetail,
|
||||
},
|
||||
{
|
||||
path: ROUTES.SETTINGS,
|
||||
@@ -96,7 +97,7 @@ const routes: AppRoutes[] = [
|
||||
{
|
||||
path: ROUTES.TRACE,
|
||||
exact: true,
|
||||
component: TraceDetailPages,
|
||||
component: TraceFilter,
|
||||
},
|
||||
{
|
||||
path: ROUTES.CHANNELS_NEW,
|
||||
@@ -113,6 +114,11 @@ const routes: AppRoutes[] = [
|
||||
exact: true,
|
||||
component: AllAlertChannels,
|
||||
},
|
||||
{
|
||||
path: ROUTES.VERSION,
|
||||
exact: true,
|
||||
component: StatusPage,
|
||||
},
|
||||
];
|
||||
|
||||
interface AppRoutes {
|
||||
|
||||
26
frontend/src/ReactI18/index.tsx
Normal file
26
frontend/src/ReactI18/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import i18n from 'i18next';
|
||||
import LanguageDetector from 'i18next-browser-languagedetector';
|
||||
import Backend from 'i18next-http-backend';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
i18n
|
||||
// load translation using http -> see /public/locales
|
||||
.use(Backend)
|
||||
// detect user language
|
||||
.use(LanguageDetector)
|
||||
// pass the i18n instance to react-i18next.
|
||||
.use(initReactI18next)
|
||||
// init i18next
|
||||
.init({
|
||||
debug: true,
|
||||
fallbackLng: 'en',
|
||||
interpolation: {
|
||||
escapeValue: false, // not needed for react as it escapes by default
|
||||
},
|
||||
|
||||
react: {
|
||||
useSuspense: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
@@ -2,14 +2,15 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse } from 'types/api';
|
||||
import { ErrorStatusCode } from 'types/common';
|
||||
|
||||
export const ErrorResponseHandler = (error: AxiosError): ErrorResponse => {
|
||||
if (error.response) {
|
||||
export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
|
||||
const { response, request } = error;
|
||||
if (response) {
|
||||
// client received an error response (5xx, 4xx)
|
||||
// making the error status code as standard Error Status Code
|
||||
const statusCode = error.response.status as ErrorStatusCode;
|
||||
const statusCode = response.status as ErrorStatusCode;
|
||||
|
||||
if (statusCode >= 400 && statusCode < 500) {
|
||||
const { data } = error.response;
|
||||
const { data } = response;
|
||||
|
||||
if (statusCode === 404) {
|
||||
return {
|
||||
@@ -35,7 +36,7 @@ export const ErrorResponseHandler = (error: AxiosError): ErrorResponse => {
|
||||
message: null,
|
||||
};
|
||||
}
|
||||
if (error.request) {
|
||||
if (request) {
|
||||
// client never received a response, or request never left
|
||||
console.error('client never received a response, or request never left');
|
||||
|
||||
@@ -51,7 +52,7 @@ export const ErrorResponseHandler = (error: AxiosError): ErrorResponse => {
|
||||
return {
|
||||
statusCode: 500,
|
||||
payload: null,
|
||||
error: error.toString(),
|
||||
error: String(error),
|
||||
message: null,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getGroups';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
|
||||
const getGroups = async (
|
||||
props: Props,
|
||||
|
||||
29
frontend/src/api/alerts/getTriggered.ts
Normal file
29
frontend/src/api/alerts/getTriggered.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/alerts/getTriggered';
|
||||
|
||||
const getTriggered = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts?${queryParams}`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTriggered;
|
||||
@@ -1,7 +1,6 @@
|
||||
const get = (key: string): string | null => {
|
||||
try {
|
||||
const value = localStorage.getItem(key);
|
||||
return value;
|
||||
return localStorage.getItem(key);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
|
||||
51
frontend/src/api/channels/createWebhook.ts
Normal file
51
frontend/src/api/channels/createWebhook.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/createWebhook';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
|
||||
if (props.username !== '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
50
frontend/src/api/channels/editWebhook.ts
Normal file
50
frontend/src/api/channels/editWebhook.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/channels/editWebhook';
|
||||
|
||||
const editWebhook = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
let httpConfig = {};
|
||||
if (props.username !== '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
basic_auth: {
|
||||
username: props.username,
|
||||
password: props.password,
|
||||
},
|
||||
};
|
||||
} else if (props.username === '' && props.password !== '') {
|
||||
httpConfig = {
|
||||
authorization: {
|
||||
type: 'bearer',
|
||||
credentials: props.password,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
webhook_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
url: props.api_url,
|
||||
http_config: httpConfig,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editWebhook;
|
||||
24
frontend/src/api/disks/getDisks.ts
Normal file
24
frontend/src/api/disks/getDisks.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/disks/getDisks';
|
||||
|
||||
const getDisks = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/disks`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getDisks;
|
||||
@@ -9,7 +9,11 @@ const setRetention = async (
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(
|
||||
`/settings/ttl?duration=${props.duration}&type=${props.type}`,
|
||||
`/settings/ttl?duration=${props.totalDuration}&type=${props.type}${
|
||||
props.coldStorage
|
||||
? `&coldStorage=${props.coldStorage};toColdDuration=${props.toColdDuration}`
|
||||
: ''
|
||||
}`,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getFilters';
|
||||
import omitBy from 'lodash-es/omitBy';
|
||||
|
||||
const getFilters = async (
|
||||
props: Props,
|
||||
@@ -29,9 +29,9 @@ const getFilters = async (
|
||||
end: props.end,
|
||||
getFilters: props.getFilters,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
exclude: exclude,
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@@ -39,8 +39,8 @@ const getSpans = async (
|
||||
step: props.step,
|
||||
tags: updatedSelectedTags,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
},
|
||||
);
|
||||
|
||||
@@ -15,6 +15,7 @@ const getSpanAggregate = async (
|
||||
end: String(props.end),
|
||||
limit: props.limit,
|
||||
offset: props.offset,
|
||||
order: props.order,
|
||||
};
|
||||
|
||||
const exclude: TraceFilterEnum[] = [];
|
||||
@@ -41,8 +42,8 @@ const getSpanAggregate = async (
|
||||
...preProps,
|
||||
tags: updatedSelectedTags,
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
exclude,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ const getTagFilters = async (
|
||||
start: String(props.start),
|
||||
end: String(props.end),
|
||||
...nonDuration,
|
||||
maxDuration: String((duration['duration'] || [])[0] || ''),
|
||||
minDuration: String((duration['duration'] || [])[1] || ''),
|
||||
maxDuration: String((duration.duration || [])[0] || ''),
|
||||
minDuration: String((duration.duration || [])[1] || ''),
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
28
frontend/src/api/trace/getTagValue.ts
Normal file
28
frontend/src/api/trace/getTagValue.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getTagValue';
|
||||
|
||||
const getTagValue = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post<PayloadProps>(`/getTagValues`, {
|
||||
start: props.start.toString(),
|
||||
end: props.end.toString(),
|
||||
tagKey: props.tagKey,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTagValue;
|
||||
27
frontend/src/api/trace/getTraceItem.ts
Normal file
27
frontend/src/api/trace/getTraceItem.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/trace/getTraceItem';
|
||||
|
||||
const getTraceItem = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.request<PayloadProps>({
|
||||
url: `/traces/${props.id}`,
|
||||
method: 'get',
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getTraceItem;
|
||||
25
frontend/src/api/user/getLatestVersion.ts
Normal file
25
frontend/src/api/user/getLatestVersion.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/user/getLatestVersion';
|
||||
|
||||
const getLatestVersion = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`https://api.github.com/repos/signoz/signoz/releases/latest`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getLatestVersion;
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
const TimeSeries = (props: TimeSeriesProps): JSX.Element => (
|
||||
<React.Fragment>
|
||||
function TimeSeries(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width="81"
|
||||
height="46"
|
||||
@@ -31,12 +31,7 @@ const TimeSeries = (props: TimeSeriesProps): JSX.Element => (
|
||||
/>
|
||||
</defs>
|
||||
</svg>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
export interface TimeSeriesProps{
|
||||
fillColor: React.CSSProperties['color'];
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default TimeSeries;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
const Value = (props: ValueProps): JSX.Element => {
|
||||
return(
|
||||
<React.Fragment>
|
||||
function Value(props: ValueProps): JSX.Element {
|
||||
const { fillColor } = props;
|
||||
|
||||
return (
|
||||
<svg
|
||||
width="78"
|
||||
height="32"
|
||||
@@ -12,13 +13,13 @@ const Value = (props: ValueProps): JSX.Element => {
|
||||
>
|
||||
<path
|
||||
d="M15.0215 17.875C14.2285 18.8184 13.2783 19.5771 12.1709 20.1514C11.0771 20.7256 9.87402 21.0127 8.56152 21.0127C6.83887 21.0127 5.33496 20.5889 4.0498 19.7412C2.77832 18.8936 1.79395 17.7041 1.09668 16.1729C0.399414 14.6279 0.0507812 12.9258 0.0507812 11.0664C0.0507812 9.07031 0.426758 7.27246 1.17871 5.67285C1.94434 4.07324 3.02441 2.84961 4.41895 2.00195C5.81348 1.1543 7.44043 0.730469 9.2998 0.730469C12.2529 0.730469 14.5771 1.83789 16.2725 4.05273C17.9814 6.25391 18.8359 9.26172 18.8359 13.0762V14.1836C18.8359 19.9941 17.6875 24.2393 15.3906 26.9189C13.0938 29.585 9.62793 30.9521 4.99316 31.0205H4.25488V27.8213H5.05469C8.18555 27.7666 10.5918 26.9531 12.2734 25.3809C13.9551 23.7949 14.8711 21.293 15.0215 17.875ZM9.17676 17.875C10.4482 17.875 11.6172 17.4854 12.6836 16.7061C13.7637 15.9268 14.5498 14.9629 15.042 13.8145V12.2969C15.042 9.80859 14.502 7.78516 13.4219 6.22656C12.3418 4.66797 10.9746 3.88867 9.32031 3.88867C7.65234 3.88867 6.3125 4.53125 5.30078 5.81641C4.28906 7.08789 3.7832 8.76953 3.7832 10.8613C3.7832 12.8984 4.26855 14.5801 5.23926 15.9062C6.22363 17.2188 7.53613 17.875 9.17676 17.875ZM24.5371 29.0107C24.5371 28.3545 24.7285 27.8076 25.1113 27.3701C25.5078 26.9326 26.0957 26.7139 26.875 26.7139C27.6543 26.7139 28.2422 26.9326 28.6387 27.3701C29.0488 27.8076 29.2539 28.3545 29.2539 29.0107C29.2539 29.6396 29.0488 30.166 28.6387 30.5898C28.2422 31.0137 27.6543 31.2256 26.875 31.2256C26.0957 31.2256 25.5078 31.0137 25.1113 30.5898C24.7285 30.166 24.5371 29.6396 24.5371 29.0107ZM51.1562 20.9717H55.2988V24.0684H51.1562V31H47.3418V24.0684H33.7451V21.833L47.1162 1.14062H51.1562V20.9717ZM38.0518 20.9717H47.3418V6.3291L46.8906 7.14941L38.0518 20.9717ZM73.6123 1.12012V4.33984H72.915C69.9619 4.39453 67.6104 5.26953 65.8604 6.96484C64.1104 8.66016 63.0986 11.0459 62.8252 14.1221C64.3975 12.3174 66.5439 11.415 69.2646 11.415C71.8623 11.415 73.9336 12.3311 75.4785 14.1631C77.0371 15.9951 77.8164 18.3604 77.8164 21.2588C77.8164 24.335 76.9756 26.7959 75.2939 28.6416C73.626 30.4873 71.3838 31.4102 68.5674 31.4102C65.71 31.4102 63.3926 30.3164 61.6152 28.1289C59.8379 25.9277 58.9492 23.0977 58.9492 19.6387V18.1826C58.9492 12.6865 60.1182 8.48926 62.4561 5.59082C64.8076 2.67871 68.3008 1.18848 72.9355 1.12012H73.6123ZM68.6289 14.5732C67.3301 14.5732 66.1338 14.9629 65.04 15.7422C63.9463 16.5215 63.1875 17.499 62.7637 18.6748V20.0693C62.7637 22.5303 63.3174 24.5127 64.4248 26.0166C65.5322 27.5205 66.9131 28.2725 68.5674 28.2725C70.2764 28.2725 71.6162 27.6436 72.5869 26.3857C73.5713 25.1279 74.0635 23.4805 74.0635 21.4434C74.0635 19.3926 73.5645 17.7383 72.5664 16.4805C71.582 15.209 70.2695 14.5732 68.6289 14.5732Z"
|
||||
fill={props.fillColor}
|
||||
fill={fillColor}
|
||||
/>
|
||||
</svg>
|
||||
</React.Fragment>
|
||||
)};
|
||||
);
|
||||
}
|
||||
|
||||
interface ValueProps{
|
||||
interface ValueProps {
|
||||
fillColor: React.CSSProperties['color'];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
|
||||
const NotFound = (): JSX.Element => {
|
||||
function NotFound(): JSX.Element {
|
||||
return (
|
||||
<svg
|
||||
width="360"
|
||||
@@ -261,6 +261,6 @@ const NotFound = (): JSX.Element => {
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Dayjs } from 'dayjs';
|
||||
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
|
||||
import generatePicker from 'antd/es/date-picker/generatePicker';
|
||||
import { Dayjs } from 'dayjs';
|
||||
// included in antd
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import dayjsGenerateConfig from 'rc-picker/lib/generate/dayjs';
|
||||
|
||||
const DatePicker = generatePicker<Dayjs>(dayjsGenerateConfig);
|
||||
|
||||
|
||||
@@ -1,42 +1,23 @@
|
||||
import * as monaco from 'monaco-editor';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import MEditor from '@monaco-editor/react';
|
||||
import React from 'react';
|
||||
|
||||
import { Container } from './styles';
|
||||
|
||||
const Editor = ({ value }: EditorProps): JSX.Element => {
|
||||
const divEl = useRef<HTMLDivElement>(null);
|
||||
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor>();
|
||||
|
||||
useEffect(() => {
|
||||
let editor = editorRef.current;
|
||||
|
||||
if (divEl.current) {
|
||||
editor = monaco.editor.create(divEl.current, {
|
||||
value: value.current || '',
|
||||
useShadowDOM: true,
|
||||
theme: 'vs-dark',
|
||||
automaticLayout: true,
|
||||
fontSize: 16,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
language: 'yaml',
|
||||
});
|
||||
}
|
||||
|
||||
editor?.getModel()?.onDidChangeContent(() => {
|
||||
value.current = editor?.getValue() || '';
|
||||
});
|
||||
|
||||
return (): void => {
|
||||
if (editor) {
|
||||
editor.dispose();
|
||||
}
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
return <Container ref={divEl} />;
|
||||
};
|
||||
function Editor({ value }: EditorProps): JSX.Element {
|
||||
return (
|
||||
<MEditor
|
||||
theme="vs-dark"
|
||||
defaultLanguage="yaml"
|
||||
value={value.current}
|
||||
options={{ fontSize: 16, automaticLayout: true }}
|
||||
height="40vh"
|
||||
onChange={(newValue): void => {
|
||||
if (value.current && newValue) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
value.current = newValue;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
interface EditorProps {
|
||||
value: React.MutableRefObject<string>;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
&&& {
|
||||
min-height: 40vh;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
17
frontend/src/components/Graph/Plugin/EmptyGraph.ts
Normal file
17
frontend/src/components/Graph/Plugin/EmptyGraph.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { grey } from '@ant-design/colors';
|
||||
import { Chart } from 'chart.js';
|
||||
|
||||
export const emptyGraph = {
|
||||
id: 'emptyChart',
|
||||
afterDraw(chart: Chart): void {
|
||||
const { height, width, ctx } = chart;
|
||||
chart.clear();
|
||||
ctx.save();
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = '1.5rem sans-serif';
|
||||
ctx.fillStyle = `${grey.primary}`;
|
||||
ctx.fillText('No data to display', width / 2, height / 2);
|
||||
ctx.restore();
|
||||
},
|
||||
};
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Plugin, ChartType, Chart, ChartOptions } from 'chart.js';
|
||||
import { Chart, ChartType, Plugin } from 'chart.js';
|
||||
import { colors } from 'lib/getRandomColor';
|
||||
import { get } from 'lodash-es';
|
||||
|
||||
const getOrCreateLegendList = (chart: Chart, id: string, isLonger: boolean) => {
|
||||
const getOrCreateLegendList = (
|
||||
chart: Chart,
|
||||
id: string,
|
||||
isLonger: boolean,
|
||||
): HTMLUListElement => {
|
||||
const legendContainer = document.getElementById(id);
|
||||
let listContainer = legendContainer?.querySelector('ul');
|
||||
|
||||
@@ -27,7 +32,7 @@ const getOrCreateLegendList = (chart: Chart, id: string, isLonger: boolean) => {
|
||||
export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
return {
|
||||
id: 'htmlLegend',
|
||||
afterUpdate(chart, args, options: ChartOptions) {
|
||||
afterUpdate(chart): void {
|
||||
const ul = getOrCreateLegendList(chart, id || 'legend', isLonger);
|
||||
|
||||
// Remove old legend items
|
||||
@@ -36,9 +41,20 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
}
|
||||
|
||||
// Reuse the built-in legendItems generator
|
||||
const items = chart?.options?.plugins?.legend?.labels?.generateLabels(chart);
|
||||
const items = get(chart, [
|
||||
'options',
|
||||
'plugins',
|
||||
'legend',
|
||||
'labels',
|
||||
'generateLabels',
|
||||
])
|
||||
? get(chart, ['options', 'plugins', 'legend', 'labels', 'generateLabels'])(
|
||||
chart,
|
||||
)
|
||||
: null;
|
||||
|
||||
items?.forEach((item, index) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
items?.forEach((item: Record<any, any>, index: number) => {
|
||||
const li = document.createElement('li');
|
||||
li.style.alignItems = 'center';
|
||||
li.style.cursor = 'pointer';
|
||||
@@ -46,7 +62,7 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
li.style.marginLeft = '10px';
|
||||
li.style.marginTop = '5px';
|
||||
|
||||
li.onclick = () => {
|
||||
li.onclick = (): void => {
|
||||
const { type } = chart.config;
|
||||
if (type === 'pie' || type === 'doughnut') {
|
||||
// Pie and doughnut charts only have a single dataset and visibility is per item
|
||||
@@ -62,9 +78,9 @@ export const legend = (id: string, isLonger: boolean): Plugin<ChartType> => {
|
||||
|
||||
// Color box
|
||||
const boxSpan = document.createElement('span');
|
||||
boxSpan.style.background = item.strokeStyle || colors[0];
|
||||
boxSpan.style.borderColor = item?.strokeStyle;
|
||||
boxSpan.style.borderWidth = item.lineWidth + 'px';
|
||||
boxSpan.style.background = `${item.strokeStyle}` || `${colors[0]}`;
|
||||
boxSpan.style.borderColor = `${item?.strokeStyle}`;
|
||||
boxSpan.style.borderWidth = `${item.lineWidth}px`;
|
||||
boxSpan.style.display = 'inline-block';
|
||||
boxSpan.style.minHeight = '20px';
|
||||
boxSpan.style.marginRight = '10px';
|
||||
|
||||
75
frontend/src/components/Graph/__tests__/xAxisConfig.test.ts
Normal file
75
frontend/src/components/Graph/__tests__/xAxisConfig.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { expect } from '@jest/globals';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import { convertTimeRange, TIME_UNITS } from '../xAxisConfig';
|
||||
|
||||
describe('xAxisConfig for Chart', () => {
|
||||
describe('convertTimeRange', () => {
|
||||
it('should return relevant time units for given range', () => {
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'millisecond');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.millisecond,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'second');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.second,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'minute');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.minute,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'hour');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.hour,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'day');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.day,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'week');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.week,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'month');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.month,
|
||||
);
|
||||
}
|
||||
{
|
||||
const start = dayjs();
|
||||
const end = start.add(10, 'year');
|
||||
|
||||
expect(convertTimeRange(start.valueOf(), end.valueOf()).unitName).toEqual(
|
||||
TIME_UNITS.year,
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
19
frontend/src/components/Graph/hasData.ts
Normal file
19
frontend/src/components/Graph/hasData.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import { ChartData } from 'chart.js';
|
||||
|
||||
export const hasData = (data: ChartData): boolean => {
|
||||
const { datasets = [] } = data;
|
||||
let hasData = false;
|
||||
try {
|
||||
for (const dataset of datasets) {
|
||||
if (dataset.data.length > 0 && dataset.data.some((item) => item !== 0)) {
|
||||
hasData = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
return hasData;
|
||||
};
|
||||
@@ -27,6 +27,13 @@ import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import { hasData } from './hasData';
|
||||
import { legend } from './Plugin';
|
||||
import { emptyGraph } from './Plugin/EmptyGraph';
|
||||
import { LegendsContainer } from './styles';
|
||||
import { useXAxisTimeUnit } from './xAxisConfig';
|
||||
import { getYAxisFormattedValue } from './yAxisConfig';
|
||||
|
||||
Chart.register(
|
||||
LineElement,
|
||||
PointElement,
|
||||
@@ -44,24 +51,24 @@ Chart.register(
|
||||
BarController,
|
||||
BarElement,
|
||||
);
|
||||
import { legend } from './Plugin';
|
||||
import { LegendsContainer } from './styles';
|
||||
|
||||
const Graph = ({
|
||||
function Graph({
|
||||
animate = true,
|
||||
data,
|
||||
type,
|
||||
title,
|
||||
isStacked,
|
||||
onClickHandler,
|
||||
name,
|
||||
}: GraphProps): JSX.Element => {
|
||||
yAxisUnit = 'short',
|
||||
forceReRender,
|
||||
}: GraphProps): JSX.Element {
|
||||
const { isDarkMode } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const chartRef = useRef<HTMLCanvasElement>(null);
|
||||
const currentTheme = isDarkMode ? 'dark' : 'light';
|
||||
const xAxisTimeUnit = useXAxisTimeUnit(data); // Computes the relevant time unit for x axis by analyzing the time stamp data
|
||||
|
||||
// const [tooltipVisible, setTooltipVisible] = useState<boolean>(false);
|
||||
const lineChartRef = useRef<Chart>();
|
||||
|
||||
const getGridColor = useCallback(() => {
|
||||
if (currentTheme === undefined) {
|
||||
return 'rgba(231,233,237,0.1)';
|
||||
@@ -74,6 +81,7 @@ const Graph = ({
|
||||
return 'rgba(231,233,237,0.8)';
|
||||
}, [currentTheme]);
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
const buildChart = useCallback(() => {
|
||||
if (lineChartRef.current !== undefined) {
|
||||
lineChartRef.current.destroy();
|
||||
@@ -81,6 +89,9 @@ const Graph = ({
|
||||
|
||||
if (chartRef.current !== null) {
|
||||
const options: ChartOptions = {
|
||||
animation: {
|
||||
duration: animate ? 200 : 0,
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
interaction: {
|
||||
@@ -89,12 +100,27 @@ const Graph = ({
|
||||
},
|
||||
plugins: {
|
||||
title: {
|
||||
display: title === undefined ? false : true,
|
||||
display: title !== undefined,
|
||||
text: title,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label(context) {
|
||||
let label = context.dataset.label || '';
|
||||
|
||||
if (label) {
|
||||
label += ': ';
|
||||
}
|
||||
if (context.parsed.y !== null) {
|
||||
label += getYAxisFormattedValue(context.parsed.y, yAxisUnit);
|
||||
}
|
||||
return label;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
padding: 0,
|
||||
@@ -104,12 +130,24 @@ const Graph = ({
|
||||
grid: {
|
||||
display: true,
|
||||
color: getGridColor(),
|
||||
drawTicks: true,
|
||||
},
|
||||
adapters: {
|
||||
date: chartjsAdapter,
|
||||
},
|
||||
time: {
|
||||
unit: 'minute',
|
||||
unit: xAxisTimeUnit?.unitName || 'minute',
|
||||
stepSize: xAxisTimeUnit?.stepSize || 1,
|
||||
displayFormats: {
|
||||
millisecond: 'HH:mm:ss',
|
||||
second: 'HH:mm:ss',
|
||||
minute: 'HH:mm',
|
||||
hour: 'MM/dd HH:mm',
|
||||
day: 'MM/dd',
|
||||
week: 'MM/dd',
|
||||
month: 'yy-MM',
|
||||
year: 'yy',
|
||||
},
|
||||
},
|
||||
type: 'time',
|
||||
},
|
||||
@@ -119,6 +157,15 @@ const Graph = ({
|
||||
display: true,
|
||||
color: getGridColor(),
|
||||
},
|
||||
ticks: {
|
||||
// Include a dollar sign in the ticks
|
||||
callback(value) {
|
||||
return getYAxisFormattedValue(
|
||||
parseInt(value.toString(), 10),
|
||||
yAxisUnit,
|
||||
);
|
||||
},
|
||||
},
|
||||
},
|
||||
stacked: {
|
||||
display: isStacked === undefined ? false : 'auto',
|
||||
@@ -136,19 +183,37 @@ const Graph = ({
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const chartHasData = hasData(data);
|
||||
const chartPlugins = [];
|
||||
if (chartHasData) {
|
||||
chartPlugins.push(legend(name, data.datasets.length > 3));
|
||||
} else {
|
||||
chartPlugins.push(emptyGraph);
|
||||
}
|
||||
lineChartRef.current = new Chart(chartRef.current, {
|
||||
type: type,
|
||||
data: data,
|
||||
type,
|
||||
data,
|
||||
options,
|
||||
plugins: [legend(name, data.datasets.length > 3)],
|
||||
plugins: chartPlugins,
|
||||
});
|
||||
}
|
||||
}, [chartRef, data, type, title, isStacked, getGridColor, onClickHandler]);
|
||||
}, [
|
||||
animate,
|
||||
title,
|
||||
getGridColor,
|
||||
xAxisTimeUnit?.unitName,
|
||||
xAxisTimeUnit?.stepSize,
|
||||
isStacked,
|
||||
type,
|
||||
data,
|
||||
name,
|
||||
yAxisUnit,
|
||||
onClickHandler,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
buildChart();
|
||||
}, [buildChart]);
|
||||
}, [buildChart, forceReRender]);
|
||||
|
||||
return (
|
||||
<div style={{ height: '85%' }}>
|
||||
@@ -156,23 +221,33 @@ const Graph = ({
|
||||
<LegendsContainer id={name} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface GraphProps {
|
||||
animate?: boolean;
|
||||
type: ChartType;
|
||||
data: Chart['data'];
|
||||
title?: string;
|
||||
isStacked?: boolean;
|
||||
label?: string[];
|
||||
onClickHandler?: graphOnClickHandler;
|
||||
onClickHandler?: GraphOnClickHandler;
|
||||
name: string;
|
||||
yAxisUnit?: string;
|
||||
forceReRender?: boolean | null | number;
|
||||
}
|
||||
|
||||
export type graphOnClickHandler = (
|
||||
export type GraphOnClickHandler = (
|
||||
event: ChartEvent,
|
||||
elements: ActiveElement[],
|
||||
chart: Chart,
|
||||
data: ChartData,
|
||||
) => void;
|
||||
|
||||
Graph.defaultProps = {
|
||||
animate: undefined,
|
||||
title: undefined,
|
||||
isStacked: undefined,
|
||||
onClickHandler: undefined,
|
||||
yAxisUnit: undefined,
|
||||
forceReRender: undefined,
|
||||
};
|
||||
export default Graph;
|
||||
|
||||
154
frontend/src/components/Graph/xAxisConfig.ts
Normal file
154
frontend/src/components/Graph/xAxisConfig.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { Chart, TimeUnit } from 'chart.js';
|
||||
import { useMemo } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
|
||||
interface ITimeUnit {
|
||||
[key: string]: TimeUnit;
|
||||
}
|
||||
interface IAxisTimeUintConfig {
|
||||
unitName: TimeUnit;
|
||||
multiplier: number;
|
||||
}
|
||||
|
||||
interface IAxisTimeConfig {
|
||||
unitName: TimeUnit;
|
||||
stepSize: number;
|
||||
}
|
||||
|
||||
export interface ITimeRange {
|
||||
minTime: number | null;
|
||||
maxTime: number | null;
|
||||
}
|
||||
|
||||
export const TIME_UNITS: ITimeUnit = {
|
||||
millisecond: 'millisecond',
|
||||
second: 'second',
|
||||
minute: 'minute',
|
||||
hour: 'hour',
|
||||
day: 'day',
|
||||
week: 'week',
|
||||
month: 'month',
|
||||
year: 'year',
|
||||
};
|
||||
|
||||
const TIME_UNITS_CONFIG: IAxisTimeUintConfig[] = [
|
||||
{
|
||||
unitName: TIME_UNITS.millisecond,
|
||||
multiplier: 1,
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.second,
|
||||
multiplier: 1 / 1e3,
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.minute,
|
||||
multiplier: 1 / (1e3 * 60),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.hour,
|
||||
multiplier: 1 / (1e3 * 60 * 60),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.day,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.week,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24 * 7),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.month,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24 * 30),
|
||||
},
|
||||
{
|
||||
unitName: TIME_UNITS.year,
|
||||
multiplier: 1 / (1e3 * 60 * 60 * 24 * 365),
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Finds the relevant time unit based on the input time stamps (in ms)
|
||||
*/
|
||||
export const convertTimeRange = (
|
||||
start: number,
|
||||
end: number,
|
||||
): IAxisTimeConfig => {
|
||||
const MIN_INTERVALS = 6;
|
||||
const range = end - start;
|
||||
let relevantTimeUnit = TIME_UNITS_CONFIG[1];
|
||||
let stepSize = 1;
|
||||
try {
|
||||
for (let idx = TIME_UNITS_CONFIG.length - 1; idx >= 0; idx -= 1) {
|
||||
const timeUnit = TIME_UNITS_CONFIG[idx];
|
||||
const units = range * timeUnit.multiplier;
|
||||
const steps = units / MIN_INTERVALS;
|
||||
if (steps >= 1) {
|
||||
relevantTimeUnit = timeUnit;
|
||||
stepSize = steps;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return {
|
||||
unitName: relevantTimeUnit.unitName,
|
||||
stepSize: Math.floor(stepSize) || 1,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepts Chart.js data's data-structure and returns the relevant time unit for the axis based on the range of the data.
|
||||
*/
|
||||
export const useXAxisTimeUnit = (data: Chart['data']): IAxisTimeConfig => {
|
||||
// Local time is the time range inferred from the input chart data.
|
||||
let localTime: ITimeRange | null;
|
||||
try {
|
||||
let minTime = Number.POSITIVE_INFINITY;
|
||||
let maxTime = Number.NEGATIVE_INFINITY;
|
||||
data?.labels?.forEach((timeStamp: unknown): void => {
|
||||
const getTimeStamp = (time: string | number): Date | number | string => {
|
||||
if (typeof timeStamp === 'string') {
|
||||
return Date.parse(timeStamp);
|
||||
}
|
||||
|
||||
return time;
|
||||
};
|
||||
const time = getTimeStamp(timeStamp as string | number);
|
||||
|
||||
minTime = Math.min(parseInt(time.toString(), 10), minTime);
|
||||
maxTime = Math.max(parseInt(time.toString(), 10), maxTime);
|
||||
});
|
||||
|
||||
localTime = {
|
||||
minTime: minTime === Number.POSITIVE_INFINITY ? null : minTime,
|
||||
maxTime: maxTime === Number.NEGATIVE_INFINITY ? null : maxTime,
|
||||
};
|
||||
} catch (error) {
|
||||
localTime = null;
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
// Global time is the time selected from the global time selector menu.
|
||||
const globalTime = useSelector<AppState, GlobalReducer>(
|
||||
(state) => state.globalTime,
|
||||
);
|
||||
|
||||
// Use local time if valid else use the global time range
|
||||
const { maxTime, minTime } = useMemo(() => {
|
||||
if (localTime && localTime.maxTime && localTime.minTime) {
|
||||
return {
|
||||
minTime: localTime.minTime,
|
||||
maxTime: localTime.maxTime,
|
||||
};
|
||||
}
|
||||
return {
|
||||
minTime: globalTime.minTime / 1e6,
|
||||
maxTime: globalTime.maxTime / 1e6,
|
||||
};
|
||||
}, [globalTime, localTime]);
|
||||
|
||||
return convertTimeRange(minTime, maxTime);
|
||||
};
|
||||
15
frontend/src/components/Graph/yAxisConfig.ts
Normal file
15
frontend/src/components/Graph/yAxisConfig.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { formattedValueToString, getValueFormat } from '@grafana/data';
|
||||
|
||||
export const getYAxisFormattedValue = (
|
||||
value: number,
|
||||
format: string,
|
||||
): string => {
|
||||
try {
|
||||
return formattedValueToString(
|
||||
getValueFormat(format)(value, undefined, undefined, undefined),
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
return `${value}`;
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Form, Input, InputProps } from 'antd';
|
||||
import { Form, Input, InputProps, InputRef } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const InputComponent = ({
|
||||
function InputComponent({
|
||||
value,
|
||||
type = 'text',
|
||||
onChangeHandler,
|
||||
@@ -14,29 +14,32 @@ const InputComponent = ({
|
||||
labelOnTop,
|
||||
addonBefore,
|
||||
...props
|
||||
}: InputComponentProps): JSX.Element => (
|
||||
<Form.Item labelCol={{ span: labelOnTop ? 24 : 4 }} label={label}>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
onChange={onChangeHandler}
|
||||
value={value}
|
||||
ref={ref}
|
||||
size={size}
|
||||
addonBefore={addonBefore}
|
||||
onBlur={onBlurHandler}
|
||||
onPressEnter={onPressEnterHandler}
|
||||
{...props}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}: InputComponentProps): JSX.Element {
|
||||
return (
|
||||
<Form.Item labelCol={{ span: labelOnTop ? 24 : 4 }} label={label}>
|
||||
<Input
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
onChange={onChangeHandler}
|
||||
value={value}
|
||||
ref={ref as React.Ref<InputRef>}
|
||||
size={size}
|
||||
addonBefore={addonBefore}
|
||||
onBlur={onBlurHandler}
|
||||
onPressEnter={onPressEnterHandler}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
interface InputComponentProps extends InputProps {
|
||||
value: InputProps['value'];
|
||||
type?: InputProps['type'];
|
||||
onChangeHandler?: React.ChangeEventHandler<HTMLInputElement>;
|
||||
placeholder?: InputProps['placeholder'];
|
||||
ref?: React.LegacyRef<Input>;
|
||||
ref?: React.LegacyRef<InputRef>;
|
||||
size?: InputProps['size'];
|
||||
onBlurHandler?: React.FocusEventHandler<HTMLInputElement>;
|
||||
onPressEnterHandler?: React.KeyboardEventHandler<HTMLInputElement>;
|
||||
@@ -45,4 +48,17 @@ interface InputComponentProps extends InputProps {
|
||||
addonBefore?: React.ReactNode;
|
||||
}
|
||||
|
||||
InputComponent.defaultProps = {
|
||||
type: undefined,
|
||||
onChangeHandler: undefined,
|
||||
placeholder: undefined,
|
||||
ref: undefined,
|
||||
size: undefined,
|
||||
onBlurHandler: undefined,
|
||||
onPressEnterHandler: undefined,
|
||||
label: undefined,
|
||||
labelOnTop: undefined,
|
||||
addonBefore: undefined,
|
||||
};
|
||||
|
||||
export default InputComponent;
|
||||
|
||||
@@ -3,9 +3,7 @@ import { ComponentType, lazy } from 'react';
|
||||
function Loadable(importPath: {
|
||||
(): LoadableProps;
|
||||
}): React.LazyExoticComponent<LazyComponent> {
|
||||
const LazyComponent = lazy(() => importPath());
|
||||
|
||||
return LazyComponent;
|
||||
return lazy(() => importPath());
|
||||
}
|
||||
|
||||
type LazyComponent = ComponentType<Record<string, unknown>>;
|
||||
|
||||
@@ -1,26 +1,24 @@
|
||||
import { Modal, ModalProps as Props } from 'antd';
|
||||
import React, { ReactElement } from 'react';
|
||||
|
||||
const CustomModal = ({
|
||||
function CustomModal({
|
||||
title,
|
||||
children,
|
||||
isModalVisible,
|
||||
footer,
|
||||
closable = true,
|
||||
}: ModalProps): JSX.Element => {
|
||||
}: ModalProps): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
<Modal
|
||||
title={title}
|
||||
visible={isModalVisible}
|
||||
footer={footer}
|
||||
closable={closable}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
</>
|
||||
<Modal
|
||||
title={title}
|
||||
visible={isModalVisible}
|
||||
footer={footer}
|
||||
closable={closable}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface ModalProps {
|
||||
isModalVisible: boolean;
|
||||
@@ -30,4 +28,9 @@ interface ModalProps {
|
||||
children: ReactElement;
|
||||
}
|
||||
|
||||
CustomModal.defaultProps = {
|
||||
closable: undefined,
|
||||
footer: undefined,
|
||||
};
|
||||
|
||||
export default CustomModal;
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @jest-environment jsdom
|
||||
*/
|
||||
|
||||
import { expect } from '@jest/globals';
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
<DocumentFragment>
|
||||
<div
|
||||
class="sc-gtsrHT VomVY"
|
||||
class="sc-gsDKAQ cLXpIa"
|
||||
>
|
||||
<svg
|
||||
fill="none"
|
||||
@@ -272,21 +272,21 @@ exports[`Not Found page test should render Not Found page without errors 1`] = `
|
||||
</defs>
|
||||
</svg>
|
||||
<div
|
||||
class="sc-hKFxyN dunFuJ"
|
||||
class="sc-hKwDye foaleg"
|
||||
>
|
||||
<p
|
||||
class="sc-dlnjwi cydxLA"
|
||||
class="sc-dkPtRN fcyVIq"
|
||||
>
|
||||
Ah, seems like we reached a dead end!
|
||||
</p>
|
||||
<p
|
||||
class="sc-dlnjwi cydxLA"
|
||||
class="sc-dkPtRN fcyVIq"
|
||||
>
|
||||
Page Not Found
|
||||
</p>
|
||||
</div>
|
||||
<a
|
||||
class="sc-bdnxRM bYqcho"
|
||||
class="sc-bdvvtL dbTZkj"
|
||||
href="/application"
|
||||
tabindex="0"
|
||||
>
|
||||
|
||||
@@ -4,7 +4,7 @@ import React from 'react';
|
||||
|
||||
import { Button, Container, Text, TextContainer } from './styles';
|
||||
|
||||
const NotFound = (): JSX.Element => {
|
||||
function NotFound(): JSX.Element {
|
||||
return (
|
||||
<Container>
|
||||
<NotFoundImage />
|
||||
@@ -19,6 +19,6 @@ const NotFound = (): JSX.Element => {
|
||||
</Button>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default NotFound;
|
||||
|
||||
@@ -2,7 +2,6 @@ import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled(Link)`
|
||||
height: 100%;
|
||||
border: 2px solid #2f80ed;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10px;
|
||||
|
||||
58
frontend/src/components/RouteTab/index.tsx
Normal file
58
frontend/src/components/RouteTab/index.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Tabs, TabsProps } from 'antd';
|
||||
import history from 'lib/history';
|
||||
import React from 'react';
|
||||
|
||||
const { TabPane } = Tabs;
|
||||
|
||||
function RouteTab({
|
||||
routes,
|
||||
activeKey,
|
||||
onChangeHandler,
|
||||
...rest
|
||||
}: RouteTabProps & TabsProps): JSX.Element {
|
||||
const onChange = (activeRoute: string): void => {
|
||||
if (onChangeHandler) {
|
||||
onChangeHandler();
|
||||
}
|
||||
|
||||
const selectedRoute = routes.find((e) => e.name === activeRoute);
|
||||
|
||||
if (selectedRoute) {
|
||||
history.push(selectedRoute.route);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
onChange={onChange}
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeKey}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...rest}
|
||||
>
|
||||
{routes.map(
|
||||
({ Component, name }): JSX.Element => (
|
||||
<TabPane tab={name} key={name}>
|
||||
<Component />
|
||||
</TabPane>
|
||||
),
|
||||
)}
|
||||
</Tabs>
|
||||
);
|
||||
}
|
||||
|
||||
interface RouteTabProps {
|
||||
routes: {
|
||||
name: string;
|
||||
route: string;
|
||||
Component: () => JSX.Element;
|
||||
}[];
|
||||
activeKey: TabsProps['activeKey'];
|
||||
onChangeHandler?: VoidFunction;
|
||||
}
|
||||
|
||||
RouteTab.defaultProps = {
|
||||
onChangeHandler: undefined,
|
||||
};
|
||||
|
||||
export default RouteTab;
|
||||
@@ -4,16 +4,23 @@ import React from 'react';
|
||||
|
||||
import { SpinerStyle } from './styles';
|
||||
|
||||
const Spinner = ({ size, tip, height }: SpinnerProps): JSX.Element => (
|
||||
<SpinerStyle height={height}>
|
||||
<Spin spinning size={size} tip={tip} indicator={<LoadingOutlined spin />} />
|
||||
</SpinerStyle>
|
||||
);
|
||||
function Spinner({ size, tip, height }: SpinnerProps): JSX.Element {
|
||||
return (
|
||||
<SpinerStyle height={height}>
|
||||
<Spin spinning size={size} tip={tip} indicator={<LoadingOutlined spin />} />
|
||||
</SpinerStyle>
|
||||
);
|
||||
}
|
||||
|
||||
interface SpinnerProps {
|
||||
size?: SpinProps['size'];
|
||||
tip?: SpinProps['tip'];
|
||||
height?: React.CSSProperties['height'];
|
||||
}
|
||||
Spinner.defaultProps = {
|
||||
size: undefined,
|
||||
tip: undefined,
|
||||
height: undefined,
|
||||
};
|
||||
|
||||
export default Spinner;
|
||||
|
||||
72
frontend/src/components/Styled/index.ts
Normal file
72
frontend/src/components/Styled/index.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import * as AntD from 'antd';
|
||||
import { TextProps } from 'antd/lib/typography/Text';
|
||||
import { TitleProps } from 'antd/lib/typography/Title';
|
||||
import React from 'react';
|
||||
import styled, { FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
import { IStyledClass } from './types';
|
||||
|
||||
const styledClass = (props: IStyledClass): FlattenSimpleInterpolation | null =>
|
||||
props.styledclass || null;
|
||||
|
||||
type TStyledCol = AntD.ColProps & IStyledClass;
|
||||
const StyledCol = styled(AntD.Col)<TStyledCol>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledRow = AntD.RowProps & IStyledClass;
|
||||
const StyledRow = styled(AntD.Row)<TStyledRow>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledDivider = AntD.DividerProps & IStyledClass;
|
||||
const StyledDivider = styled(AntD.Divider)<TStyledDivider>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledSpace = AntD.SpaceProps & IStyledClass;
|
||||
const StyledSpace = styled(AntD.Space)<TStyledSpace>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledTabs = AntD.TabsProps & IStyledClass;
|
||||
const StyledTabs = styled(AntD.Divider)<TStyledTabs>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledButton = AntD.ButtonProps & IStyledClass;
|
||||
const StyledButton = styled(AntD.Button)<TStyledButton>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
const { Text } = AntD.Typography;
|
||||
type TStyledTypographyText = TextProps & IStyledClass;
|
||||
const StyledTypographyText = styled(Text)<TStyledTypographyText>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
const { Title } = AntD.Typography;
|
||||
type TStyledTypographyTitle = TitleProps & IStyledClass;
|
||||
const StyledTypographyTitle = styled(Title)<TStyledTypographyTitle>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
type TStyledDiv = React.HTMLAttributes<HTMLDivElement> & IStyledClass;
|
||||
const StyledDiv = styled.div<TStyledDiv>`
|
||||
${styledClass}
|
||||
`;
|
||||
|
||||
const StyledTypography = {
|
||||
Text: StyledTypographyText,
|
||||
Title: StyledTypographyTitle,
|
||||
};
|
||||
export {
|
||||
StyledButton,
|
||||
StyledCol,
|
||||
StyledDiv,
|
||||
StyledDivider,
|
||||
StyledRow,
|
||||
StyledSpace,
|
||||
StyledTabs,
|
||||
StyledTypography,
|
||||
};
|
||||
42
frontend/src/components/Styled/styles.ts
Normal file
42
frontend/src/components/Styled/styles.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { css, FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cssProperty = (key: any, value: any): FlattenSimpleInterpolation =>
|
||||
key &&
|
||||
value &&
|
||||
css`
|
||||
${key}: ${value};
|
||||
`;
|
||||
|
||||
interface IFlexProps {
|
||||
flexDirection?: string; // Need to replace this with exact css props. Not able to find any :(
|
||||
flex?: number | string;
|
||||
}
|
||||
export const Flex = ({
|
||||
flexDirection,
|
||||
flex,
|
||||
}: IFlexProps): FlattenSimpleInterpolation => css`
|
||||
${cssProperty('flex-direction', flexDirection)}
|
||||
${cssProperty('flex', flex)}
|
||||
`;
|
||||
|
||||
interface IDisplayProps {
|
||||
display?: string;
|
||||
}
|
||||
export const Display = ({
|
||||
display,
|
||||
}: IDisplayProps): FlattenSimpleInterpolation => css`
|
||||
${cssProperty('display', display)}
|
||||
`;
|
||||
|
||||
interface ISpacingProps {
|
||||
margin?: string;
|
||||
padding?: string;
|
||||
}
|
||||
export const Spacing = ({
|
||||
margin,
|
||||
padding,
|
||||
}: ISpacingProps): FlattenSimpleInterpolation => css`
|
||||
${cssProperty('margin', margin)}
|
||||
${cssProperty('padding', padding)}
|
||||
`;
|
||||
5
frontend/src/components/Styled/types.ts
Normal file
5
frontend/src/components/Styled/types.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { FlattenSimpleInterpolation } from 'styled-components';
|
||||
|
||||
export interface IStyledClass {
|
||||
styledclass?: FlattenSimpleInterpolation[];
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
/* eslint-disable react/no-unstable-nested-components */
|
||||
import { QuestionCircleFilled } from '@ant-design/icons';
|
||||
import { Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const TextToolTip = ({ text, url }: TextToolTipProps) => (
|
||||
<Tooltip
|
||||
overlay={() => {
|
||||
return (
|
||||
<div>
|
||||
{`${text} `}
|
||||
<a href={url} target={'_blank'}>
|
||||
here
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<QuestionCircleFilled style={{ fontSize: '1.3125rem' }} />
|
||||
</Tooltip>
|
||||
);
|
||||
function TextToolTip({ text, url }: TextToolTipProps): JSX.Element {
|
||||
return (
|
||||
<Tooltip
|
||||
overlay={(): JSX.Element => {
|
||||
return (
|
||||
<div>
|
||||
{`${text} `}
|
||||
<a href={url} rel="noopener noreferrer" target="_blank">
|
||||
here
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<QuestionCircleFilled style={{ fontSize: '1.3125rem' }} />
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
interface TextToolTipProps {
|
||||
url: string;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Dropdown, Menu, Typography } from 'antd';
|
||||
import timeItems, {
|
||||
import TimeItems, {
|
||||
timePreferance,
|
||||
timePreferenceType,
|
||||
} from 'container/NewWidget/RightContainer/timeItems';
|
||||
@@ -7,13 +7,13 @@ import React, { useCallback } from 'react';
|
||||
|
||||
import { TextContainer } from './styles';
|
||||
|
||||
const TimePreference = ({
|
||||
function TimePreference({
|
||||
setSelectedTime,
|
||||
selectedTime,
|
||||
}: TimePreferenceDropDownProps): JSX.Element => {
|
||||
}: TimePreferenceDropDownProps): JSX.Element {
|
||||
const timeMenuItemOnChangeHandler = useCallback(
|
||||
(event: TimeMenuItemOnChangeHandlerEvent) => {
|
||||
const selectedTime = timeItems.find((e) => e.enum === event.key);
|
||||
const selectedTime = TimeItems.find((e) => e.enum === event.key);
|
||||
if (selectedTime !== undefined) {
|
||||
setSelectedTime(selectedTime);
|
||||
}
|
||||
@@ -26,7 +26,7 @@ const TimePreference = ({
|
||||
<Dropdown
|
||||
overlay={
|
||||
<Menu>
|
||||
{timeItems.map((item) => (
|
||||
{TimeItems.map((item) => (
|
||||
<Menu.Item onClick={timeMenuItemOnChangeHandler} key={item.enum}>
|
||||
<Typography>{item.name}</Typography>
|
||||
</Menu.Item>
|
||||
@@ -38,7 +38,7 @@ const TimePreference = ({
|
||||
</Dropdown>
|
||||
</TextContainer>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface TimeMenuItemOnChangeHandlerEvent {
|
||||
key: timePreferenceType | string;
|
||||
|
||||
@@ -2,9 +2,9 @@ import React from 'react';
|
||||
|
||||
import { Value } from './styles';
|
||||
|
||||
const ValueGraph = ({ value }: ValueGraphProps): JSX.Element => (
|
||||
<Value>{value}</Value>
|
||||
);
|
||||
function ValueGraph({ value }: ValueGraphProps): JSX.Element {
|
||||
return <Value>{value}</Value>;
|
||||
}
|
||||
|
||||
interface ValueGraphProps {
|
||||
value: string;
|
||||
|
||||
@@ -5,3 +5,5 @@ export const WITHOUT_SESSION_PATH = ['/redirect'];
|
||||
export const AUTH0_REDIRECT_PATH = '/redirect';
|
||||
|
||||
export const DEFAULT_AUTH0_APP_REDIRECTION_PATH = ROUTES.APPLICATION;
|
||||
|
||||
export const IS_SIDEBAR_COLLAPSED = 'isSideBarCollapsed';
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export enum LOCAL_STORAGE {
|
||||
export enum LOCALSTORAGE {
|
||||
METRICS_TIME_IN_DURATION = 'metricsTimeDurations',
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
export enum METRICS_PAGE_QUERY_PARAM {
|
||||
interval = 'interval',
|
||||
startTime = 'startTime',
|
||||
|
||||
@@ -3,7 +3,7 @@ const ROUTES = {
|
||||
SERVICE_METRICS: '/application/:servicename',
|
||||
SERVICE_MAP: '/service-map',
|
||||
TRACE: '/trace',
|
||||
TRACE_GRAPH: '/trace/:id',
|
||||
TRACE_DETAIL: '/trace/:id',
|
||||
SETTINGS: '/settings',
|
||||
INSTRUMENTATION: '/add-instrumentation',
|
||||
USAGE_EXPLORER: '/usage-explorer',
|
||||
@@ -17,6 +17,7 @@ const ROUTES = {
|
||||
ALL_CHANNELS: '/settings/channels',
|
||||
CHANNELS_NEW: '/setting/channels/new',
|
||||
CHANNELS_EDIT: '/setting/channels/edit/:id',
|
||||
VERSION: '/status',
|
||||
};
|
||||
|
||||
export default ROUTES;
|
||||
|
||||
@@ -4,12 +4,12 @@ import { ColumnsType } from 'antd/lib/table';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { generatePath } from 'react-router';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { Channels, PayloadProps } from 'types/api/channels/getAll';
|
||||
|
||||
import Delete from './Delete';
|
||||
|
||||
const AlertChannels = ({ allChannels }: AlertChannelsProps): JSX.Element => {
|
||||
function AlertChannels({ allChannels }: AlertChannelsProps): JSX.Element {
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
const [channels, setChannels] = useState<Channels[]>(allChannels);
|
||||
|
||||
@@ -55,7 +55,7 @@ const AlertChannels = ({ allChannels }: AlertChannelsProps): JSX.Element => {
|
||||
<Table rowKey="id" dataSource={channels} columns={columns} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface AlertChannelsProps {
|
||||
allChannels: PayloadProps;
|
||||
|
||||
@@ -4,11 +4,7 @@ import deleteAlert from 'api/channels/delete';
|
||||
import React, { useState } from 'react';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
|
||||
const Delete = ({
|
||||
notifications,
|
||||
setChannels,
|
||||
id,
|
||||
}: DeleteProps): JSX.Element => {
|
||||
function Delete({ notifications, setChannels, id }: DeleteProps): JSX.Element {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const onClickHandler = async (): Promise<void> => {
|
||||
@@ -34,7 +30,8 @@ const Delete = ({
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Error',
|
||||
description: error.toString() || 'Something went wrong',
|
||||
description:
|
||||
error instanceof Error ? error.toString() : 'Something went wrong',
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -50,7 +47,7 @@ const Delete = ({
|
||||
Delete
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface DeleteProps {
|
||||
notifications: NotificationInstance;
|
||||
|
||||
@@ -7,12 +7,13 @@ import ROUTES from 'constants/routes';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import history from 'lib/history';
|
||||
import React, { useCallback } from 'react';
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
import AlertChannlesComponent from './AlertChannels';
|
||||
import { ButtonContainer, Button } from './styles';
|
||||
import { Button, ButtonContainer } from './styles';
|
||||
|
||||
const AlertChannels = (): JSX.Element => {
|
||||
const { Paragraph } = Typography;
|
||||
|
||||
function AlertChannels(): JSX.Element {
|
||||
const onToggleHandler = useCallback(() => {
|
||||
history.push(ROUTES.CHANNELS_NEW);
|
||||
}, []);
|
||||
@@ -24,7 +25,7 @@ const AlertChannels = (): JSX.Element => {
|
||||
}
|
||||
|
||||
if (loading || payload === undefined) {
|
||||
return <Spinner tip="Loading Channels.." height={'90vh'} />;
|
||||
return <Spinner tip="Loading Channels.." height="90vh" />;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -36,7 +37,7 @@ const AlertChannels = (): JSX.Element => {
|
||||
|
||||
<div>
|
||||
<TextToolTip
|
||||
text={`More details on how to setting notification channels`}
|
||||
text="More details on how to setting notification channels"
|
||||
url="https://signoz.io/docs/userguide/alerts-management/#setting-notification-channel"
|
||||
/>
|
||||
|
||||
@@ -49,6 +50,6 @@ const AlertChannels = (): JSX.Element => {
|
||||
<AlertChannlesComponent allChannels={payload} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default AlertChannels;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import styled from 'styled-components';
|
||||
import { Button as ButtonComponent } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
&&& {
|
||||
|
||||
@@ -1,127 +0,0 @@
|
||||
import { CloseCircleOutlined, CommentOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider, Form, Input, notification, Typography } from 'antd';
|
||||
import { Callbacks } from 'rc-field-form/lib/interface';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import {
|
||||
Button as IconButton,
|
||||
ButtonContainer,
|
||||
Card,
|
||||
CenterText,
|
||||
Container,
|
||||
TitleContainer,
|
||||
FormItem,
|
||||
} from './styles';
|
||||
const { Title } = Typography;
|
||||
const { TextArea } = Input;
|
||||
import sendFeedbackApi from 'api/userFeedback/sendFeedback';
|
||||
|
||||
const Feedback = (): JSX.Element => {
|
||||
const [isOpen, setisOpen] = useState<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const [notifications, Element] = notification.useNotification();
|
||||
|
||||
const isToggleHandler = useCallback(() => {
|
||||
setisOpen((state) => !state);
|
||||
}, []);
|
||||
|
||||
const onFinishHandler: Callbacks<Feedback>['onFinish'] = async (
|
||||
value: Feedback,
|
||||
): Promise<void> => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const { feedback, email = '' } = value;
|
||||
|
||||
const response = await sendFeedbackApi({
|
||||
email,
|
||||
message: feedback,
|
||||
});
|
||||
|
||||
if (response === 200) {
|
||||
notifications.success({
|
||||
message: 'Thanks for your feedback!',
|
||||
description:
|
||||
'We have noted down your feedback and will work on improving SIgNoz based on that!',
|
||||
});
|
||||
|
||||
isToggleHandler();
|
||||
} else {
|
||||
notifications.error({
|
||||
message: 'Error!',
|
||||
description: 'Something went wrong',
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
notifications.error({
|
||||
message: 'Something went wrong',
|
||||
});
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container>
|
||||
{!isOpen && (
|
||||
<IconButton onClick={isToggleHandler} type="primary" size="large">
|
||||
<CommentOutlined />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
{Element}
|
||||
|
||||
{isOpen && (
|
||||
<Form onFinish={onFinishHandler} form={form}>
|
||||
<Card>
|
||||
<TitleContainer>
|
||||
<Title
|
||||
aria-label="How can we improve SigNoz?"
|
||||
style={{ margin: 0 }}
|
||||
level={5}
|
||||
>
|
||||
How can we improve SigNoz?
|
||||
</Title>
|
||||
<CloseCircleOutlined onClick={isToggleHandler} />
|
||||
</TitleContainer>
|
||||
|
||||
<Divider />
|
||||
|
||||
<FormItem name="feedback" required>
|
||||
<TextArea
|
||||
required
|
||||
rows={3}
|
||||
placeholder="Share what can we improve ( e.g. Not able to find how to see metrics... )"
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<FormItem name="email">
|
||||
<Input type="email" placeholder="Email (optional)" />
|
||||
</FormItem>
|
||||
|
||||
<CenterText>This will just be visible to our maintainers</CenterText>
|
||||
|
||||
<ButtonContainer>
|
||||
<Button
|
||||
disabled={isLoading}
|
||||
loading={isLoading}
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
>
|
||||
Share
|
||||
</Button>
|
||||
</ButtonContainer>
|
||||
</Card>
|
||||
</Form>
|
||||
)}
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
interface Feedback {
|
||||
email?: string;
|
||||
feedback: string;
|
||||
}
|
||||
|
||||
export default Feedback;
|
||||
@@ -1,64 +0,0 @@
|
||||
import {
|
||||
Button as ButtonComponent,
|
||||
Card as CardComponent,
|
||||
Typography,
|
||||
Form,
|
||||
} from 'antd';
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Container = styled.div`
|
||||
position: fixed;
|
||||
bottom: 5%;
|
||||
right: 4%;
|
||||
z-index: 999999;
|
||||
`;
|
||||
|
||||
export const CenterText = styled(Typography)`
|
||||
&&& {
|
||||
font-size: 0.75rem;
|
||||
text-align: center;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const TitleContainer = styled.div`
|
||||
&&& {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Card = styled(CardComponent)`
|
||||
&&& {
|
||||
min-width: 400px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const ButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const Button = styled(ButtonComponent)`
|
||||
height: 4rem !important;
|
||||
width: 4rem !important;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
border-radius: 25px !important;
|
||||
|
||||
background-color: #65b7f3;
|
||||
|
||||
svg {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const FormItem = styled(Form.Item)`
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
`;
|
||||
@@ -1,34 +1,123 @@
|
||||
import { notification } from 'antd';
|
||||
import getLatestVersion from 'api/user/getLatestVersion';
|
||||
import getVersion from 'api/user/getVersion';
|
||||
import ROUTES from 'constants/routes';
|
||||
import TopNav from 'container/Header';
|
||||
import SideNav from 'container/SideNav';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import history from 'lib/history';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import React, { ReactNode, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import {
|
||||
UPDATE_CURRENT_ERROR,
|
||||
UPDATE_CURRENT_VERSION,
|
||||
UPDATE_LATEST_VERSION,
|
||||
UPDATE_LATEST_VERSION_ERROR,
|
||||
} from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
|
||||
import Feedback from './FeedBack';
|
||||
import { Content, Footer, Layout } from './styles';
|
||||
import { Content, Layout } from './styles';
|
||||
|
||||
const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
const { isLoggedIn } = useSelector<AppState, AppReducer>((state) => state.app);
|
||||
const { pathname } = useLocation();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(
|
||||
ROUTES.SIGN_UP === location.pathname,
|
||||
const [isSignUpPage, setIsSignUpPage] = useState(ROUTES.SIGN_UP === pathname);
|
||||
|
||||
const { payload: versionPayload, loading, error: getVersionError } = useFetch(
|
||||
getVersion,
|
||||
);
|
||||
|
||||
const {
|
||||
payload: latestVersionPayload,
|
||||
loading: latestLoading,
|
||||
error: latestError,
|
||||
} = useFetch(getLatestVersion);
|
||||
|
||||
const { children } = props;
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
setIsSignUpPage(true);
|
||||
history.push(ROUTES.SIGN_UP);
|
||||
} else {
|
||||
if (isSignUpPage) {
|
||||
setIsSignUpPage(false);
|
||||
}
|
||||
} else if (isSignUpPage) {
|
||||
setIsSignUpPage(false);
|
||||
}
|
||||
}, [isLoggedIn, isSignUpPage]);
|
||||
|
||||
const currentYear = new Date().getFullYear();
|
||||
const latestCurrentCounter = useRef(0);
|
||||
const latestVersionCounter = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoggedIn && pathname === ROUTES.SIGN_UP) {
|
||||
history.push(ROUTES.APPLICATION);
|
||||
}
|
||||
|
||||
if (!latestLoading && latestError && latestCurrentCounter.current === 0) {
|
||||
latestCurrentCounter.current = 1;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_LATEST_VERSION_ERROR,
|
||||
payload: {
|
||||
isError: true,
|
||||
},
|
||||
});
|
||||
notification.error({
|
||||
message: t('oops_something_went_wrong_version'),
|
||||
});
|
||||
}
|
||||
|
||||
if (!loading && getVersionError && latestVersionCounter.current === 0) {
|
||||
latestVersionCounter.current = 1;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_ERROR,
|
||||
payload: {
|
||||
isError: true,
|
||||
},
|
||||
});
|
||||
notification.error({
|
||||
message: t('oops_something_went_wrong_version'),
|
||||
});
|
||||
}
|
||||
|
||||
if (!latestLoading && versionPayload) {
|
||||
dispatch({
|
||||
type: UPDATE_CURRENT_VERSION,
|
||||
payload: {
|
||||
currentVersion: versionPayload.version,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (!loading && latestVersionPayload) {
|
||||
dispatch({
|
||||
type: UPDATE_LATEST_VERSION,
|
||||
payload: {
|
||||
latestVersion: latestVersionPayload.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
loading,
|
||||
latestLoading,
|
||||
versionPayload,
|
||||
latestVersionPayload,
|
||||
isLoggedIn,
|
||||
pathname,
|
||||
getVersionError,
|
||||
latestError,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
@@ -38,13 +127,10 @@ const AppLayout: React.FC<AppLayoutProps> = ({ children }) => {
|
||||
{!isSignUpPage && <TopNav />}
|
||||
{children}
|
||||
</Content>
|
||||
<Footer>{`SigNoz Inc. © ${currentYear}`}</Footer>
|
||||
</Layout>
|
||||
|
||||
<Feedback />
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
interface AppLayoutProps {
|
||||
children: ReactNode;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user