Compare commits
211 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d6c5f43a1 | ||
|
|
9a433891f2 | ||
|
|
3c63d66591 | ||
|
|
5b69559762 | ||
|
|
d7a5c6d65b | ||
|
|
1588d3a199 | ||
|
|
d5df9a1f7f | ||
|
|
2be3d35952 | ||
|
|
7fa50070ce | ||
|
|
2494b64ccd | ||
|
|
ca3283fcad | ||
|
|
a912731cc7 | ||
|
|
1a855582a7 | ||
|
|
f3c00e1a57 | ||
|
|
0d3cbb1db2 | ||
|
|
2c96512a8a | ||
|
|
a84a70df14 | ||
|
|
dcea79cef3 | ||
|
|
b12365ba07 | ||
|
|
718eb7b381 | ||
|
|
503417719c | ||
|
|
e7a5eb7b22 | ||
|
|
b14f800fee | ||
|
|
9e91375632 | ||
|
|
d7d4000240 | ||
|
|
e12aef136a | ||
|
|
0e04b779a9 | ||
|
|
587034f573 | ||
|
|
321cba2af5 | ||
|
|
abed60bdfa | ||
|
|
a306fb64cb | ||
|
|
0ad5d67140 | ||
|
|
11863040bb | ||
|
|
a67a3837c8 | ||
|
|
81b10d126a | ||
|
|
9f751688cc | ||
|
|
3d0fbd0065 | ||
|
|
05ea814c61 | ||
|
|
92ba46b2f5 | ||
|
|
4bbe1ea614 | ||
|
|
e3a251ef29 | ||
|
|
a4e0d9c7df | ||
|
|
4076cd9847 | ||
|
|
e3f4fc2967 | ||
|
|
bccefc6a10 | ||
|
|
821471f4ab | ||
|
|
1e242b6d06 | ||
|
|
4ca5176836 | ||
|
|
7f397d529b | ||
|
|
656f354fdc | ||
|
|
4cc3ce224c | ||
|
|
a4a285c074 | ||
|
|
a8f8580606 | ||
|
|
e24918044e | ||
|
|
28d346eafb | ||
|
|
cbd2f4c643 | ||
|
|
fcedc9e445 | ||
|
|
d2d3c4bb36 | ||
|
|
dc4acc0730 | ||
|
|
043e5ca880 | ||
|
|
5c437dd8f9 | ||
|
|
31b898b2c6 | ||
|
|
e186474414 | ||
|
|
8bfb0b5088 | ||
|
|
045a31ac92 | ||
|
|
c9654a6b52 | ||
|
|
30e0924bfb | ||
|
|
ccada08db5 | ||
|
|
6654dd2672 | ||
|
|
4b0a7cc4d3 | ||
|
|
04acc49154 | ||
|
|
3db8a25eb9 | ||
|
|
8324d010ae | ||
|
|
f0022cd13f | ||
|
|
655c92cfef | ||
|
|
735ab8e118 | ||
|
|
b0861f4fe0 | ||
|
|
61b6779a31 | ||
|
|
416a058eab | ||
|
|
4227faa6b5 | ||
|
|
9d3c4598ac | ||
|
|
6aba701cca | ||
|
|
cf1b0c2f24 | ||
|
|
231c2fd281 | ||
|
|
e3f17b5420 | ||
|
|
56f1f71461 | ||
|
|
72f4578152 | ||
|
|
9c8125ffc1 | ||
|
|
85d7752350 | ||
|
|
63ba9fb5e0 | ||
|
|
14a59a26b2 | ||
|
|
178c154263 | ||
|
|
122488c2c1 | ||
|
|
ad3fbd7599 | ||
|
|
1ad1ca5385 | ||
|
|
2fc82ffa59 | ||
|
|
ed809474d6 | ||
|
|
a8c4a91001 | ||
|
|
1ffb1b4a5d | ||
|
|
9a00998930 | ||
|
|
c60f612e0e | ||
|
|
7839134532 | ||
|
|
714a2ef4fd | ||
|
|
7f98a65022 | ||
|
|
616e3af18f | ||
|
|
17ae197bc3 | ||
|
|
27cda7a437 | ||
|
|
54a2309d8f | ||
|
|
748c78e9c3 | ||
|
|
7ae0326f61 | ||
|
|
c15240abab | ||
|
|
2fe9e53766 | ||
|
|
7209ac0007 | ||
|
|
96adc7f61c | ||
|
|
7b4fd55aeb | ||
|
|
86c34bd87d | ||
|
|
41f7a7993d | ||
|
|
dfd94f67bd | ||
|
|
052c32ce78 | ||
|
|
004f10e73b | ||
|
|
f17608fa10 | ||
|
|
8de8a8a86a | ||
|
|
0486721b35 | ||
|
|
14bbc609d8 | ||
|
|
f450d71a25 | ||
|
|
921fca5e67 | ||
|
|
1f7e70fa16 | ||
|
|
3ca048bc76 | ||
|
|
37e36626ab | ||
|
|
07808a8664 | ||
|
|
16d490fbe3 | ||
|
|
b1cee71621 | ||
|
|
e55a4da2bc | ||
|
|
0fa0e64697 | ||
|
|
c54fffb51d | ||
|
|
294b6966bf | ||
|
|
9aa72f847c | ||
|
|
32a55f3c4f | ||
|
|
8e6a7f13a1 | ||
|
|
89e8fb715c | ||
|
|
21de4bbd1b | ||
|
|
d4cc7c88c3 | ||
|
|
0e9e29e650 | ||
|
|
176725a4b4 | ||
|
|
88560e7c43 | ||
|
|
2538899544 | ||
|
|
ae0fc32fe1 | ||
|
|
859875667a | ||
|
|
674dee5a1b | ||
|
|
0318e53fdc | ||
|
|
b768840d36 | ||
|
|
ccc8be009c | ||
|
|
59549c36de | ||
|
|
aeee8b4cb2 | ||
|
|
03acc33888 | ||
|
|
b2a943769b | ||
|
|
1501ed0c5d | ||
|
|
218eb5379e | ||
|
|
e596dd77bd | ||
|
|
1138c6e41a | ||
|
|
15f328eb9e | ||
|
|
01312ec286 | ||
|
|
0574350e6e | ||
|
|
7e297dcb75 | ||
|
|
d184486978 | ||
|
|
337e33eb8a | ||
|
|
988dd1bcf0 | ||
|
|
25fc7b83ec | ||
|
|
893fcfa6ee | ||
|
|
23f94538c8 | ||
|
|
7586b50c5a | ||
|
|
0bee0a6d90 | ||
|
|
f89f3c0b14 | ||
|
|
598e71eb8e | ||
|
|
4d14416a08 | ||
|
|
2657276a80 | ||
|
|
48538e6b96 | ||
|
|
9337ff4b41 | ||
|
|
02cd069bb2 | ||
|
|
6a71d311b3 | ||
|
|
0b8367c817 | ||
|
|
b20fc39e08 | ||
|
|
1dd7bdb100 | ||
|
|
591ea96285 | ||
|
|
ee6b290a0c | ||
|
|
a37476a09b | ||
|
|
d41805a3b0 | ||
|
|
5d6bb18679 | ||
|
|
a393ea4d68 | ||
|
|
26b95f1b9f | ||
|
|
34be2953d3 | ||
|
|
e04d5fa7e8 | ||
|
|
2df5a9d72d | ||
|
|
b6e111b835 | ||
|
|
6a4aa7e5f5 | ||
|
|
962fc5e9ff | ||
|
|
4d5ee861ec | ||
|
|
2aef4578b0 | ||
|
|
f8bfd1abc4 | ||
|
|
a75f4f02d6 | ||
|
|
5ae4a59060 | ||
|
|
896836b57d | ||
|
|
06bedc92dc | ||
|
|
8a05b32a30 | ||
|
|
5bca66fedf | ||
|
|
6fb071cf37 | ||
|
|
9488ce8585 | ||
|
|
458cff32fd | ||
|
|
c8bad4fc79 | ||
|
|
5ac105beca | ||
|
|
865409d725 |
38
.github/workflows/build.yaml
vendored
38
.github/workflows/build.yaml
vendored
@@ -12,7 +12,31 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
run: cd frontend && yarn install
|
||||
- name: Run ESLint
|
||||
run: cd frontend && npm run lint
|
||||
- name: Run Jest
|
||||
run: cd frontend && npm run jest
|
||||
- name: TSC
|
||||
run: yarn tsc
|
||||
working-directory: ./frontend
|
||||
- name: Build frontend docker image
|
||||
shell: bash
|
||||
run: |
|
||||
make build-frontend-amd64
|
||||
|
||||
build-frontend-ee:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Create .env file
|
||||
run: |
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
|
||||
- name: Install dependencies
|
||||
run: cd frontend && yarn install
|
||||
- name: Run ESLint
|
||||
@@ -31,7 +55,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -45,7 +73,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Build EE query-service image
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
2
.github/workflows/codeql.yaml
vendored
2
.github/workflows/codeql.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
2
.github/workflows/commitlint.yml
vendored
2
.github/workflows/commitlint.yml
vendored
@@ -7,7 +7,7 @@ jobs:
|
||||
lint-commits:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: wagoid/commitlint-github-action@v5
|
||||
|
||||
@@ -12,11 +12,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Codebase
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: signoz/gh-bot
|
||||
- name: Use Node v16
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Setup Cache & Install Dependencies
|
||||
|
||||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: 'Dependency Review'
|
||||
with:
|
||||
fail-on-severity: high
|
||||
|
||||
2
.github/workflows/e2e-k3s.yaml
vendored
2
.github/workflows/e2e-k3s.yaml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
DOCKER_TAG: pull-${{ github.event.number }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build query-service image
|
||||
env:
|
||||
|
||||
4
.github/workflows/playwright.yaml
vendored
4
.github/workflows/playwright.yaml
vendored
@@ -9,8 +9,8 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "16.x"
|
||||
- name: Install dependencies
|
||||
|
||||
73
.github/workflows/push.yaml
vendored
73
.github/workflows/push.yaml
vendored
@@ -14,7 +14,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
@@ -42,6 +46,11 @@ jobs:
|
||||
else
|
||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-oss" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Install cross-compilation tools
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||
- name: Build and push docker image
|
||||
run: make build-push-query-service
|
||||
|
||||
@@ -49,7 +58,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup golang
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.21"
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- name: Set up Docker Buildx
|
||||
@@ -77,6 +90,11 @@ jobs:
|
||||
else
|
||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Install cross-compilation tools
|
||||
run: |
|
||||
set -ex
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-aarch64-linux-gnu musl-tools
|
||||
- name: Build and push docker image
|
||||
run: make build-push-ee-query-service
|
||||
|
||||
@@ -84,7 +102,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
run: yarn install
|
||||
@@ -123,3 +141,52 @@ jobs:
|
||||
fi
|
||||
- name: Build and push docker image
|
||||
run: make build-push-frontend
|
||||
|
||||
image-build-and-push-frontend-ee:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Create .env file
|
||||
run: |
|
||||
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > frontend/.env
|
||||
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
|
||||
echo 'CLARITY_PROJECT_ID="${{ secrets.CLARITY_PROJECT_ID }}"' >> frontend/.env
|
||||
- name: Install dependencies
|
||||
working-directory: frontend
|
||||
run: yarn install
|
||||
- name: Run Prettier
|
||||
working-directory: frontend
|
||||
run: npm run prettify
|
||||
continue-on-error: true
|
||||
- name: Run ESLint
|
||||
working-directory: frontend
|
||||
run: npm run lint
|
||||
continue-on-error: true
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: latest
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- uses: benjlevesque/short-sha@v2.2
|
||||
id: short-sha
|
||||
- name: Get branch name
|
||||
id: branch-name
|
||||
uses: tj-actions/branch-names@v5.1
|
||||
- name: Set docker tag environment
|
||||
run: |
|
||||
if [ '${{ steps.branch-name.outputs.is_tag }}' == 'true' ]; then
|
||||
tag="${{ steps.branch-name.outputs.tag }}"
|
||||
tag="${tag:1}"
|
||||
echo "DOCKER_TAG=${tag}-ee" >> $GITHUB_ENV
|
||||
elif [ '${{ steps.branch-name.outputs.current_branch }}' == 'main' ]; then
|
||||
echo "DOCKER_TAG=latest-ee" >> $GITHUB_ENV
|
||||
else
|
||||
echo "DOCKER_TAG=${{ steps.branch-name.outputs.current_branch }}-ee" >> $GITHUB_ENV
|
||||
fi
|
||||
- name: Build and push docker image
|
||||
run: make build-push-frontend
|
||||
|
||||
2
.github/workflows/sonar.yml
vendored
2
.github/workflows/sonar.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Sonar analysis
|
||||
|
||||
1
.github/workflows/staging-deployment.yaml
vendored
1
.github/workflows/staging-deployment.yaml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export OTELCOL_TAG="main"
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
docker system prune --force
|
||||
docker pull signoz/signoz-otel-collector:main
|
||||
cd ~/signoz
|
||||
|
||||
1
.github/workflows/testing-deployment.yaml
vendored
1
.github/workflows/testing-deployment.yaml
vendored
@@ -26,6 +26,7 @@ jobs:
|
||||
echo "GITHUB_SHA: ${GITHUB_SHA}"
|
||||
export DOCKER_TAG="${GITHUB_SHA:0:7}" # needed for child process to access it
|
||||
export DEV_BUILD="1"
|
||||
export PATH="/usr/local/go/bin/:$PATH" # needed for Golang to work
|
||||
docker system prune --force
|
||||
cd ~/signoz
|
||||
git status
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,7 +37,7 @@ frontend/src/constants/env.ts
|
||||
**/locust-scripts/__pycache__/
|
||||
**/__debug_bin
|
||||
|
||||
frontend/*.env
|
||||
frontend/.env
|
||||
pkg/query-service/signoz.db
|
||||
|
||||
pkg/query-service/tests/test-deploy/data/
|
||||
|
||||
86
Makefile
86
Makefile
@@ -8,6 +8,7 @@ BUILD_HASH ?= $(shell git rev-parse --short HEAD)
|
||||
BUILD_TIME ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
BUILD_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
DEV_LICENSE_SIGNOZ_IO ?= https://staging-license.signoz.io/api/v1
|
||||
DEV_BUILD ?= "" # set to any non-empty value to enable dev build
|
||||
|
||||
# Internal variables or constants.
|
||||
FRONTEND_DIRECTORY ?= frontend
|
||||
@@ -15,15 +16,15 @@ QUERY_SERVICE_DIRECTORY ?= pkg/query-service
|
||||
EE_QUERY_SERVICE_DIRECTORY ?= ee/query-service
|
||||
STANDALONE_DIRECTORY ?= deploy/docker/clickhouse-setup
|
||||
SWARM_DIRECTORY ?= deploy/docker-swarm/clickhouse-setup
|
||||
LOCAL_GOOS ?= $(shell go env GOOS)
|
||||
LOCAL_GOARCH ?= $(shell go env GOARCH)
|
||||
|
||||
GOOS ?= $(shell go env GOOS)
|
||||
GOARCH ?= $(shell go env GOARCH)
|
||||
GOPATH ?= $(shell go env GOPATH)
|
||||
|
||||
REPONAME ?= signoz
|
||||
DOCKER_TAG ?= latest
|
||||
|
||||
DOCKER_TAG ?= $(subst v,,$(BUILD_VERSION))
|
||||
FRONTEND_DOCKER_IMAGE ?= frontend
|
||||
QUERY_SERVICE_DOCKER_IMAGE ?= query-service
|
||||
DEV_BUILD ?= ""
|
||||
|
||||
# Build-time Go variables
|
||||
PACKAGE?=go.signoz.io/signoz
|
||||
@@ -37,10 +38,22 @@ LD_FLAGS=-X ${buildHash}=${BUILD_HASH} -X ${buildTime}=${BUILD_TIME} -X ${buildV
|
||||
DEV_LD_FLAGS=-X ${licenseSignozIo}=${DEV_LICENSE_SIGNOZ_IO}
|
||||
|
||||
all: build-push-frontend build-push-query-service
|
||||
|
||||
# Steps to build static files of frontend
|
||||
build-frontend-static:
|
||||
@echo "------------------"
|
||||
@echo "--> Building frontend static files"
|
||||
@echo "------------------"
|
||||
@cd $(FRONTEND_DIRECTORY) && \
|
||||
rm -rf build && \
|
||||
CI=1 yarn install && \
|
||||
yarn build && \
|
||||
ls -l build
|
||||
|
||||
# Steps to build and push docker image of frontend
|
||||
.PHONY: build-frontend-amd64 build-push-frontend
|
||||
# Step to build docker image of frontend in amd64 (used in build pipeline)
|
||||
build-frontend-amd64:
|
||||
build-frontend-amd64: build-frontend-static
|
||||
@echo "------------------"
|
||||
@echo "--> Building frontend docker image for amd64"
|
||||
@echo "------------------"
|
||||
@@ -49,7 +62,7 @@ build-frontend-amd64:
|
||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||
|
||||
# Step to build and push docker image of frontend(used in push pipeline)
|
||||
build-push-frontend:
|
||||
build-push-frontend: build-frontend-static
|
||||
@echo "------------------"
|
||||
@echo "--> Building and pushing frontend docker image"
|
||||
@echo "------------------"
|
||||
@@ -57,24 +70,52 @@ build-push-frontend:
|
||||
docker buildx build --file Dockerfile --progress plain --push --platform linux/arm64,linux/amd64 \
|
||||
--tag $(REPONAME)/$(FRONTEND_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
# Steps to build static binary of query service
|
||||
.PHONY: build-query-service-static
|
||||
build-query-service-static:
|
||||
@echo "------------------"
|
||||
@echo "--> Building query-service static binary"
|
||||
@echo "------------------"
|
||||
@if [ $(DEV_BUILD) != "" ]; then \
|
||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS} ${DEV_LD_FLAGS}"; \
|
||||
else \
|
||||
cd $(QUERY_SERVICE_DIRECTORY) && \
|
||||
CGO_ENABLED=1 go build -tags timetzdata -a -o ./bin/query-service-${GOOS}-${GOARCH} \
|
||||
-ldflags "-linkmode external -extldflags '-static' -s -w ${LD_FLAGS}"; \
|
||||
fi
|
||||
|
||||
.PHONY: build-query-service-static-amd64
|
||||
build-query-service-static-amd64:
|
||||
make GOARCH=amd64 build-query-service-static
|
||||
|
||||
.PHONY: build-query-service-static-arm64
|
||||
build-query-service-static-arm64:
|
||||
make CC=aarch64-linux-gnu-gcc GOARCH=arm64 build-query-service-static
|
||||
|
||||
# Steps to build static binary of query service for all platforms
|
||||
.PHONY: build-query-service-static-all
|
||||
build-query-service-static-all: build-query-service-static-amd64 build-query-service-static-arm64
|
||||
|
||||
# Steps to build and push docker image of query service
|
||||
.PHONY: build-query-service-amd64 build-push-query-service
|
||||
.PHONY: build-query-service-amd64 build-push-query-service
|
||||
# Step to build docker image of query service in amd64 (used in build pipeline)
|
||||
build-query-service-amd64:
|
||||
build-query-service-amd64: build-query-service-static-amd64
|
||||
@echo "------------------"
|
||||
@echo "--> Building query-service docker image for amd64"
|
||||
@echo "------------------"
|
||||
@docker build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .
|
||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" .
|
||||
|
||||
# Step to build and push docker image of query in amd64 and arm64 (used in push pipeline)
|
||||
build-push-query-service:
|
||||
build-push-query-service: build-query-service-static-all
|
||||
@echo "------------------"
|
||||
@echo "--> Building and pushing query-service docker image"
|
||||
@echo "------------------"
|
||||
@docker buildx build --file $(QUERY_SERVICE_DIRECTORY)/Dockerfile --progress plain \
|
||||
--push --platform linux/arm64,linux/amd64 --build-arg LD_FLAGS="$(LD_FLAGS)" \
|
||||
--push --platform linux/arm64,linux/amd64 \
|
||||
--tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
|
||||
# Step to build EE docker image of query service in amd64 (used in build pipeline)
|
||||
@@ -82,24 +123,14 @@ build-ee-query-service-amd64:
|
||||
@echo "------------------"
|
||||
@echo "--> Building query-service docker image for amd64"
|
||||
@echo "------------------"
|
||||
@if [ $(DEV_BUILD) != "" ]; then \
|
||||
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="${LD_FLAGS} ${DEV_LD_FLAGS}" .; \
|
||||
else \
|
||||
docker build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||
-t $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) \
|
||||
--build-arg TARGETPLATFORM="linux/amd64" --build-arg LD_FLAGS="$(LD_FLAGS)" .; \
|
||||
fi
|
||||
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-query-service-amd64
|
||||
|
||||
# Step to build and push EE docker image of query in amd64 and arm64 (used in push pipeline)
|
||||
build-push-ee-query-service:
|
||||
@echo "------------------"
|
||||
@echo "--> Building and pushing query-service docker image"
|
||||
@echo "------------------"
|
||||
@docker buildx build --file $(EE_QUERY_SERVICE_DIRECTORY)/Dockerfile \
|
||||
--progress plain --push --platform linux/arm64,linux/amd64 \
|
||||
--build-arg LD_FLAGS="$(LD_FLAGS)" --tag $(REPONAME)/$(QUERY_SERVICE_DOCKER_IMAGE):$(DOCKER_TAG) .
|
||||
make QUERY_SERVICE_DIRECTORY=${EE_QUERY_SERVICE_DIRECTORY} build-push-query-service
|
||||
|
||||
dev-setup:
|
||||
mkdir -p /var/lib/signoz
|
||||
@@ -110,7 +141,7 @@ dev-setup:
|
||||
@echo "------------------"
|
||||
|
||||
run-local:
|
||||
@LOCAL_GOOS=$(LOCAL_GOOS) LOCAL_GOARCH=$(LOCAL_GOARCH) docker-compose -f \
|
||||
@docker-compose -f \
|
||||
$(STANDALONE_DIRECTORY)/docker-compose-core.yaml -f $(STANDALONE_DIRECTORY)/docker-compose-local.yaml \
|
||||
up --build -d
|
||||
|
||||
@@ -151,3 +182,6 @@ test:
|
||||
go test ./pkg/query-service/app/querier/...
|
||||
go test ./pkg/query-service/converter/...
|
||||
go test ./pkg/query-service/formatter/...
|
||||
go test ./pkg/query-service/tests/integration/...
|
||||
go test ./pkg/query-service/rules/...
|
||||
go test ./pkg/query-service/collectorsimulator/...
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Downloads"> </a>
|
||||
<img alt="Downloads" src="https://img.shields.io/docker/pulls/signoz/query-service?label=Docker 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>
|
||||
|
||||
203
README.zh-cn.md
203
README.zh-cn.md
@@ -1,170 +1,225 @@
|
||||
<p align="center">
|
||||
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
|
||||
<img src="https://res.cloudinary.com/dcv3epinx/image/upload/v1618904450/signoz-images/LogoGithub_sigfbu.svg" alt="SigNoz-logo" width="240" />
|
||||
|
||||
<p align="center">监视你的应用,并可排查已部署应用中的问题,这是一个开源的可替代DataDog、NewRelic的方案</p>
|
||||
<p align="center">监控你的应用,并且可排查已部署应用的问题,这是一个可替代 DataDog、NewRelic 的开源方案</p>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<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=Docker 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>
|
||||
</p>
|
||||
|
||||
##
|
||||
<h3 align="center">
|
||||
<a href="https://signoz.io/docs"><b>文档</b></a> •
|
||||
<a href="https://github.com/SigNoz/signoz/blob/develop/README.zh-cn.md"><b>中文ReadMe</b></a> •
|
||||
<a href="https://github.com/SigNoz/signoz/blob/develop/README.de-de.md"><b>德文ReadMe</b></a> •
|
||||
<a href="https://github.com/SigNoz/signoz/blob/develop/README.pt-br.md"><b>葡萄牙语ReadMe</b></a> •
|
||||
<a href="https://signoz.io/slack"><b>Slack 社区</b></a> •
|
||||
<a href="https://twitter.com/SigNozHq"><b>Twitter</b></a>
|
||||
</h3>
|
||||
|
||||
SigNoz帮助开发人员监控应用并排查已部署应用中的问题。SigNoz使用分布式追踪来增加软件技术栈的可见性。
|
||||
##
|
||||
|
||||
👉 你能看到一些性能指标,服务、外部api调用、每个终端(endpoint)的p99延迟和错误率。
|
||||
SigNoz 帮助开发人员监控应用并排查已部署应用的问题。你可以使用 SigNoz 实现如下能力:
|
||||
|
||||
👉 通过准确的追踪来确定是什么引起了问题,并且可以看到每个独立请求的帧图(framegraph),这样你就能找到根本原因。
|
||||
👉 在同一块面板上,可视化 Metrics, Traces 和 Logs 内容。
|
||||
|
||||
👉 聚合trace数据来获得业务相关指标。
|
||||
👉 你可以关注服务的 p99 延迟和错误率, 包括外部 API 调用和个别的端点。
|
||||
|
||||

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

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

|
||||
👉 你可以找到问题的根因,通过提取相关问题的 traces 日志、单独查看请求 traces 的火焰图详情。
|
||||
|
||||
👉 执行 trace 数据聚合,以获取业务相关的 metrics
|
||||
|
||||
👉 对日志过滤和查询,通过日志的属性建立看板和告警
|
||||
|
||||
👉 通过 Python,java,Ruby 和 Javascript 自动记录异常
|
||||
|
||||
👉 轻松的自定义查询和设置告警
|
||||
|
||||
### 应用 Metrics 展示
|
||||
|
||||

|
||||
|
||||
### 分布式追踪
|
||||
|
||||
<img width="2068" alt="distributed_tracing_2 2" src="https://user-images.githubusercontent.com/83692067/226536447-bae58321-6a22-4ed3-af80-e3e964cb3489.png">
|
||||
|
||||
<img width="2068" alt="distributed_tracing_1" src="https://user-images.githubusercontent.com/83692067/226536462-939745b6-4f9d-45a6-8016-814837e7f7b4.png">
|
||||
|
||||
### 日志管理
|
||||
|
||||
<img width="2068" alt="logs_management" src="https://user-images.githubusercontent.com/83692067/226536482-b8a5c4af-b69c-43d5-969c-338bd5eaf1a5.png">
|
||||
|
||||
### 基础设施监控
|
||||
|
||||
<img width="2068" alt="infrastructure_monitoring" src="https://user-images.githubusercontent.com/83692067/226536496-f38c4dbf-e03c-4158-8be0-32d4a61158c7.png">
|
||||
|
||||
### 异常监控
|
||||
|
||||

|
||||
|
||||
### 告警
|
||||
|
||||
<img width="2068" alt="alerts_management" src="https://user-images.githubusercontent.com/83692067/226536548-2c81e2e8-c12d-47e8-bad7-c6be79055def.png">
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
|
||||
## 加入我们 Slack 社区
|
||||
|
||||
## 加入我们的Slack社区
|
||||
|
||||
来[Slack](https://signoz.io/slack) 跟我们打声招呼👋
|
||||
来 [Slack](https://signoz.io/slack) 和我们打招呼吧 👋
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Features.svg" width="50px" />
|
||||
## 特性:
|
||||
|
||||
## 功能:
|
||||
- 为 metrics, traces and logs 制定统一的 UI。 无需切换 Prometheus 到 Jaeger 去查找问题,也无需使用想 Elastic 这样的日志工具分开你的 metrics 和 traces
|
||||
|
||||
- 应用概览指标(metrics),如RPS, p50/p90/p99延迟率分位值,错误率等。
|
||||
- 应用中最慢的终端(endpoint)
|
||||
- 查看特定请求的trace数据来分析下游服务问题、慢数据库查询问题 及调用第三方服务如支付网关的问题
|
||||
- 通过服务名称、操作、延迟、错误、标签来过滤traces。
|
||||
- 聚合trace数据(events/spans)来得到业务相关指标。比如,你可以通过过滤条件`customer_type: gold` or `deployment_version: v2` or `external_call: paypal` 来获取指定业务的错误率和p99延迟
|
||||
- 为metrics和trace提供统一的UI。排查问题不需要在Prometheus和Jaeger之间切换。
|
||||
- 默认统计应用的 metrics 数据,像 RPS (每秒请求数), 50th/90th/99th 的分位数延迟数据,还有相关的错误率
|
||||
|
||||
- 找到应用中最慢的端点
|
||||
|
||||
- 查看准确的请求跟踪数据,找到下游服务的问题了,比如 DB 慢查询,或者调用第三方的支付网关等
|
||||
|
||||
- 通过 服务名、操作方式、延迟、错误、标签/注释 过滤 traces 数据
|
||||
|
||||
- 通过聚合 trace 数据而获得业务相关的 metrics。 比如你可以通过 `customer_type: gold` 或者 `deployment_version: v2` 或者 `external_call: paypal` 获取错误率和 P99 延迟数据
|
||||
|
||||
- 原生支持 OpenTelemetry 日志,高级日志查询,自动收集 k8s 相关日志
|
||||
|
||||
- 快如闪电的日志分析 ([Logs Perf. Benchmark](https://signoz.io/blog/logs-performance-benchmark/))
|
||||
|
||||
- 可视化点到点的基础设施性能,提取有所有类型机器的 metrics 数据
|
||||
|
||||
- 轻易自定义告警查询
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/WhatsCool.svg" width="50px" />
|
||||
## 为什么使用 SigNoz?
|
||||
|
||||
## 为何选择SigNoz?
|
||||
作为开发者, 我们发现 SaaS 厂商对一些大家想要的小功能都是闭源的,这种行为真的让人有点恼火。 闭源厂商还会在月底给你一张没有明细的巨额账单。
|
||||
|
||||
作为开发人员,我们发现依赖闭源的SaaS厂商提供的每个小功能有些麻烦,闭源厂商通常会给你一份巨额月付账单,但不提供足够的透明度,你不知道你为哪些功能付费。
|
||||
我们想做一个自托管并且可开源的工具,像 DataDog 和 NewRelic 那样, 为那些担心数据隐私和安全的公司提供第三方服务。
|
||||
|
||||
我们想做一个自服务的开源版本的工具,类似于DataDog和NewRelic,用于那些对客户数据流入第三方有隐私和安全担忧的厂商。
|
||||
作为开源的项目,你完全可以自己掌控你的配置、样本和更新。你同样可以基于 SigNoz 拓展特定的业务模块。
|
||||
|
||||
开源也让你对配置、采样和正常运行时间有完整的控制,你可以在SigNoz基础上构建模块来满足特定的商业需求。
|
||||
### 支持的编程语言:
|
||||
|
||||
### 语言支持
|
||||
|
||||
我们支持[OpenTelemetry](https://opentelemetry.io)库,你可以使用它来装备应用。也就是说SigNoz支持任何支持OpenTelemetry库的框架和语言。 主要支持语言包括:
|
||||
我们支持 [OpenTelemetry](https://opentelemetry.io)。作为一个观测你应用的库文件。所以任何 OpenTelemetry 支持的框架和语言,对于 SigNoz 也同样支持。 一些主要支持的语言如下:
|
||||
|
||||
- Java
|
||||
- Python
|
||||
- NodeJS
|
||||
- Go
|
||||
- PHP
|
||||
- .NET
|
||||
- Ruby
|
||||
- Elixir
|
||||
- Rust
|
||||
|
||||
你可以在这个文档里找到完整的语言列表 - https://opentelemetry.io/docs/
|
||||
你可以在这里找到全部支持的语言列表 - https://opentelemetry.io/docs/
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Philosophy.svg" width="50px" />
|
||||
## 让我们开始吧
|
||||
|
||||
## 入门
|
||||
### 使用 Docker 部署
|
||||
|
||||
请一步步跟随 [这里](https://signoz.io/docs/install/docker/) 通过 docker 来安装。
|
||||
|
||||
### 使用Docker部署
|
||||
|
||||
请按照[这里](https://signoz.io/docs/install/docker/)列出的步骤使用Docker来安装
|
||||
|
||||
如果你遇到任何问题,这个[排查指南](https://signoz.io/docs/install/troubleshooting/)会对你有帮助。
|
||||
这个 [排障说明书](https://signoz.io/docs/install/troubleshooting/) 可以帮助你解决碰到的问题。
|
||||
|
||||
<p>  </p>
|
||||
|
||||
### 使用 Helm 在 Kubernetes 部署
|
||||
|
||||
### 使用Helm在Kubernetes上部署
|
||||
|
||||
请跟着[这里](https://signoz.io/docs/deployment/helm_chart)的步骤使用helm charts安装
|
||||
请一步步跟随 [这里](https://signoz.io/docs/deployment/helm_chart) 通过 helm 来安装
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/UseSigNoz.svg" width="50px" />
|
||||
|
||||
## 与其他方案的比较
|
||||
## 比较相似的工具
|
||||
|
||||
### SigNoz vs Prometheus
|
||||
|
||||
如果你只是需要监控指标(metrics),那Prometheus是不错的,但如果你要无缝的在metrics和traces之间切换,那目前把Prometheus & Jaeger串起来的体验并不好。
|
||||
Prometheus 是一个针对 metrics 监控的强大工具。但是如果你想无缝的切换 metrics 和 traces 查询,你当前大概率需要在 Prometheus 和 Jaeger 之间切换。
|
||||
|
||||
我们的目标是为metrics和traces提供统一的UI - 类似于Datadog这样的Saas厂提供的方案。并且能够对trace进行过滤和聚合,这是目前Jaeger缺失的功能。
|
||||
我们的目标是提供一个客户观测 metrics 和 traces 整合的 UI。就像 SaaS 供应商 DataDog,它提供很多 jaeger 缺失的功能,比如针对 traces 过滤功能和聚合功能。
|
||||
|
||||
<p>  </p>
|
||||
|
||||
### SigNoz vs Jaeger
|
||||
|
||||
Jaeger只做分布式追踪(distributed tracing),SigNoz则支持metrics,traces,logs ,即可视化的三大支柱。
|
||||
Jaeger 仅仅是一个分布式追踪系统。 但是 SigNoz 可以提供 metrics, traces 和 logs 所有的观测。
|
||||
|
||||
并且SigNoz有一些Jaeger没有的高级功能:
|
||||
而且, SigNoz 相较于 Jaeger 拥有更对的高级功能:
|
||||
|
||||
- Jaegar UI无法在traces或过滤的traces上展示metrics。
|
||||
- Jaeger不能对过滤的traces做聚合操作。例如,拥有tag为customer_type='premium'的所有请求的p99延迟。而这个功能在SigNoz这儿是很容易实现。
|
||||
- Jaegar UI 不能提供任何基于 traces 的 metrics 查询和过滤。
|
||||
|
||||
- Jaeger 不能针对过滤的 traces 做聚合。 比如, p99 延迟的请求有个标签是 customer_type='premium'。 而这些在 SigNoz 可以轻松做到。
|
||||
|
||||
<p>  </p>
|
||||
|
||||
### SigNoz vs Elastic
|
||||
|
||||
- SigNoz 的日志管理是基于 ClickHouse 实现的,可以使日志的聚合更加高效,因为它是基于 OLAP 的数据仓储。
|
||||
|
||||
- 与 Elastic 相比,可以节省 50% 的资源成本
|
||||
|
||||
我们已经公布了 Elastic 和 SigNoz 的性能对比。 请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||
|
||||
<p>  </p>
|
||||
|
||||
### SigNoz vs Loki
|
||||
|
||||
- SigNoz 支持大容量高基数的聚合,但是 loki 是不支持的。
|
||||
|
||||
- SigNoz 支持索引的高基数查询,并且对索引没有数量限制,而 Loki 会在添加部分索引后到达最大上限。
|
||||
|
||||
- 相较于 SigNoz,Loki 在搜索大量数据下既困难又缓慢。
|
||||
|
||||
我们已经发布了基准测试对比 Loki 和 SigNoz 性能。请点击 [这里](https://signoz.io/blog/logs-performance-benchmark/?utm_source=github-readme&utm_medium=logs-benchmark)
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributors.svg" width="50px" />
|
||||
|
||||
## 贡献
|
||||
|
||||
我们 ❤️ 你的贡献,无论大小。 请先阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 再开始给 SigNoz 做贡献。
|
||||
|
||||
我们 ❤️ 任何贡献无论大小。 请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 然后开始给Signoz做贡献。
|
||||
如果你不知道如何开始? 只需要在 [slack 社区](https://signoz.io/slack) 通过 `#contributing` 频道联系我们。
|
||||
|
||||
还不清楚怎么开始? 只需在[slack社区](https://signoz.io/slack)的`#contributing`频道里ping我们。
|
||||
### 项目维护人员
|
||||
|
||||
### Project maintainers
|
||||
|
||||
#### Backend
|
||||
#### 后端
|
||||
|
||||
- [Ankit Nayan](https://github.com/ankitnayan)
|
||||
- [Nityananda Gohain](https://github.com/nityanandagohain)
|
||||
- [Srikanth Chekuri](https://github.com/srikanthccv)
|
||||
- [Vishal Sharma](https://github.com/makeavish)
|
||||
|
||||
#### Frontend
|
||||
#### 前端
|
||||
|
||||
- [Palash Gupta](https://github.com/palashgdev)
|
||||
|
||||
#### DevOps
|
||||
#### 运维开发
|
||||
|
||||
- [Prashant Shahi](https://github.com/prashant-shahi)
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/DevelopingLocally.svg" width="50px" />
|
||||
|
||||
## 文档
|
||||
|
||||
文档在这里:https://signoz.io/docs/. 如果你觉得有任何不清楚或者有文档缺失,请在Github里发一个问题,并使用标签 `documentation` 或者在社区stack频道里告诉我们。
|
||||
你可以通过 https://signoz.io/docs/ 找到相关文档。如果你需要阐述问题或者发现一些确实的事件, 通过标签为 `documentation` 提交 Github 问题。或者通过 slack 社区频道。
|
||||
|
||||
<br /><br />
|
||||
|
||||
<img align="left" src="https://signoz-public.s3.us-east-2.amazonaws.com/Contributing.svg" width="50px" />
|
||||
|
||||
## 社区
|
||||
|
||||
加入[slack community](https://signoz.io/slack),了解更多关于分布式跟踪、可观察性(observability),以及SigNoz。同时与其他用户和贡献者一起交流。
|
||||
加入 [slack 社区](https://signoz.io/slack) 去了解更多关于分布式追踪、可观测性系统 。或者与 SigNoz 其他用户和贡献者交流。
|
||||
|
||||
如果你有任何想法、问题或者反馈,请在[Github Discussions](https://github.com/SigNoz/signoz/discussions)分享给我们。
|
||||
如果你有任何想法、问题、或者任何反馈, 请通过 [Github Discussions](https://github.com/SigNoz/signoz/discussions) 分享。
|
||||
|
||||
最后,感谢我们这些优秀的贡献者们。
|
||||
不管怎么样,感谢这个项目的所有贡献者!
|
||||
|
||||
<a href="https://github.com/signoz/signoz/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=signoz/signoz" />
|
||||
</a>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
version: "3.9"
|
||||
|
||||
x-clickhouse-defaults:
|
||||
&clickhouse-defaults
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
deploy:
|
||||
restart_policy:
|
||||
@@ -34,8 +33,7 @@ x-clickhouse-defaults:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
|
||||
x-clickhouse-depend:
|
||||
&clickhouse-depend
|
||||
x-clickhouse-depend: &clickhouse-depend
|
||||
depends_on:
|
||||
- clickhouse
|
||||
# - clickhouse-2
|
||||
@@ -133,7 +131,7 @@ services:
|
||||
# - ./data/clickhouse-3/:/var/lib/clickhouse/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:0.23.2
|
||||
image: signoz/alertmanager:0.23.4
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
command:
|
||||
@@ -146,8 +144,12 @@ services:
|
||||
condition: on-failure
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:0.26.0
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
image: signoz/query-service:0.31.1
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
@@ -182,7 +184,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:0.26.0
|
||||
image: signoz/frontend:0.31.1
|
||||
deploy:
|
||||
restart_policy:
|
||||
condition: on-failure
|
||||
@@ -195,7 +197,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -228,7 +230,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -24,8 +24,16 @@ server {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
location ~ ^/api/(v1|v3)/logs/(tail|livetail){
|
||||
proxy_pass http://query-service:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# connection will be closed if no data is read for 600s between successive read operations
|
||||
proxy_read_timeout 600s;
|
||||
|
||||
# dont buffer the data send it directly to client.
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
location /api {
|
||||
|
||||
@@ -2,7 +2,7 @@ version: "2.4"
|
||||
|
||||
services:
|
||||
clickhouse:
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
container_name: signoz-clickhouse
|
||||
# ports:
|
||||
# - "9000:9000"
|
||||
@@ -34,7 +34,7 @@ services:
|
||||
|
||||
alertmanager:
|
||||
container_name: signoz-alertmanager
|
||||
image: signoz/alertmanager:0.23.2
|
||||
image: signoz/alertmanager:0.23.4
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
depends_on:
|
||||
@@ -48,7 +48,7 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
otel-collector:
|
||||
container_name: signoz-otel-collector
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-config.yaml",
|
||||
@@ -78,7 +78,7 @@ services:
|
||||
|
||||
otel-collector-metrics:
|
||||
container_name: signoz-otel-collector-metrics
|
||||
image: signoz/signoz-otel-collector:0.79.5
|
||||
image: signoz/signoz-otel-collector:0.79.8
|
||||
command:
|
||||
[
|
||||
"--config=/etc/otel-collector-metrics-config.yaml",
|
||||
|
||||
@@ -8,7 +8,7 @@ services:
|
||||
dockerfile: "./Dockerfile"
|
||||
args:
|
||||
LDFLAGS: ""
|
||||
TARGETPLATFORM: "${LOCAL_GOOS}/${LOCAL_GOARCH}"
|
||||
TARGETPLATFORM: "${GOOS}/${GOARCH}"
|
||||
container_name: signoz-query-service
|
||||
environment:
|
||||
- ClickHouseUrl=tcp://clickhouse:9000
|
||||
@@ -22,7 +22,11 @@ services:
|
||||
- ./prometheus.yml:/root/config/prometheus.yml
|
||||
- ../dashboards:/root/config/dashboards
|
||||
- ./data/signoz/:/var/lib/signoz/
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
ports:
|
||||
- "6060:6060"
|
||||
- "8080:8080"
|
||||
@@ -48,8 +52,8 @@ services:
|
||||
context: "../../../frontend"
|
||||
dockerfile: "./Dockerfile"
|
||||
args:
|
||||
TARGETOS: "${LOCAL_GOOS}"
|
||||
TARGETPLATFORM: "${LOCAL_GOARCH}"
|
||||
TARGETOS: "${GOOS}"
|
||||
TARGETPLATFORM: "${GOARCH}"
|
||||
container_name: signoz-frontend
|
||||
environment:
|
||||
- FRONTEND_API_ENDPOINT=http://query-service:8080
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
version: "2.4"
|
||||
|
||||
x-clickhouse-defaults:
|
||||
&clickhouse-defaults
|
||||
x-clickhouse-defaults: &clickhouse-defaults
|
||||
restart: on-failure
|
||||
image: clickhouse/clickhouse-server:22.8.8-alpine
|
||||
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
|
||||
image: clickhouse/clickhouse-server:23.7.3-alpine
|
||||
tty: true
|
||||
depends_on:
|
||||
- zookeeper-1
|
||||
@@ -32,8 +32,7 @@ x-clickhouse-defaults:
|
||||
soft: 262144
|
||||
hard: 262144
|
||||
|
||||
x-clickhouse-depend:
|
||||
&clickhouse-depend
|
||||
x-clickhouse-depend: &clickhouse-depend
|
||||
depends_on:
|
||||
clickhouse:
|
||||
condition: service_healthy
|
||||
@@ -148,7 +147,7 @@ services:
|
||||
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
|
||||
|
||||
alertmanager:
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.2}
|
||||
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.4}
|
||||
container_name: signoz-alertmanager
|
||||
volumes:
|
||||
- ./data/alertmanager:/data
|
||||
@@ -163,9 +162,13 @@ services:
|
||||
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
|
||||
|
||||
query-service:
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.26.0}
|
||||
image: signoz/query-service:${DOCKER_TAG:-0.31.1}
|
||||
container_name: signoz-query-service
|
||||
command: [ "-config=/root/config/prometheus.yml" ]
|
||||
command:
|
||||
[
|
||||
"-config=/root/config/prometheus.yml",
|
||||
"--prefer-delta=true"
|
||||
]
|
||||
# ports:
|
||||
# - "6060:6060" # pprof port
|
||||
# - "8080:8080" # query-service port
|
||||
@@ -198,7 +201,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
frontend:
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.26.0}
|
||||
image: signoz/frontend:${DOCKER_TAG:-0.31.1}
|
||||
container_name: signoz-frontend
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
@@ -210,7 +213,7 @@ services:
|
||||
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
|
||||
|
||||
otel-collector:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
|
||||
container_name: signoz-otel-collector
|
||||
command:
|
||||
[
|
||||
@@ -241,7 +244,7 @@ services:
|
||||
<<: *clickhouse-depend
|
||||
|
||||
otel-collector-metrics:
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.5}
|
||||
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.79.8}
|
||||
container_name: signoz-otel-collector-metrics
|
||||
command:
|
||||
[
|
||||
|
||||
@@ -24,8 +24,16 @@ server {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/alertmanager {
|
||||
proxy_pass http://alertmanager:9093/api/v2;
|
||||
location ~ ^/api/(v1|v3)/logs/(tail|livetail){
|
||||
proxy_pass http://query-service:8080;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# connection will be closed if no data is read for 600s between successive read operations
|
||||
proxy_read_timeout 600s;
|
||||
|
||||
# dont buffer the data send it directly to client.
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
}
|
||||
|
||||
location /api {
|
||||
|
||||
@@ -1,40 +1,20 @@
|
||||
FROM golang:1.18-buster AS builder
|
||||
|
||||
# LD_FLAGS is passed as argument from Makefile. It will be empty, if no argument passed
|
||||
ARG LD_FLAGS
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
ENV CGO_ENABLED=1
|
||||
ENV GOPATH=/go
|
||||
|
||||
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
|
||||
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
|
||||
|
||||
# Prepare and enter src directory
|
||||
WORKDIR /go/src/github.com/signoz/signoz
|
||||
|
||||
# Add the sources and proceed with build
|
||||
ADD . .
|
||||
RUN cd ee/query-service \
|
||||
&& go build -tags timetzdata -a -o ./bin/query-service \
|
||||
-ldflags "-linkmode external -extldflags '-static' -s -w $LD_FLAGS" \
|
||||
&& chmod +x ./bin/query-service
|
||||
|
||||
|
||||
# use a minimal alpine image
|
||||
FROM alpine:3.7
|
||||
FROM alpine:3.17
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
||||
# define arguments that can be passed during build time
|
||||
ARG TARGETOS TARGETARCH
|
||||
|
||||
# add ca-certificates in case you need them
|
||||
RUN apk update && apk add ca-certificates && rm -rf /var/cache/apk/*
|
||||
|
||||
# set working directory
|
||||
WORKDIR /root
|
||||
|
||||
# copy the binary from builder
|
||||
COPY --from=builder /go/src/github.com/signoz/signoz/ee/query-service/bin/query-service .
|
||||
# copy the query-service binary
|
||||
COPY ee/query-service/bin/query-service-${TARGETOS}-${TARGETARCH} /root/query-service
|
||||
|
||||
# copy prometheus YAML config
|
||||
COPY pkg/query-service/config/prometheus.yml /root/config/prometheus.yml
|
||||
@@ -45,7 +25,6 @@ RUN chmod 755 /root /root/query-service
|
||||
# run the binary
|
||||
ENTRYPOINT ["./query-service"]
|
||||
|
||||
CMD ["-config", "../config/prometheus.yml"]
|
||||
# CMD ["./query-service -config /root/config/prometheus.yml"]
|
||||
CMD ["-config", "/root/config/prometheus.yml"]
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
@@ -8,8 +8,10 @@ import (
|
||||
"go.signoz.io/signoz/ee/query-service/dao"
|
||||
"go.signoz.io/signoz/ee/query-service/interfaces"
|
||||
"go.signoz.io/signoz/ee/query-service/license"
|
||||
"go.signoz.io/signoz/ee/query-service/usage"
|
||||
baseapp "go.signoz.io/signoz/pkg/query-service/app"
|
||||
"go.signoz.io/signoz/pkg/query-service/app/logparsingpipeline"
|
||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||
baseint "go.signoz.io/signoz/pkg/query-service/interfaces"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
rules "go.signoz.io/signoz/pkg/query-service/rules"
|
||||
@@ -26,9 +28,13 @@ type APIHandlerOptions struct {
|
||||
DialTimeout time.Duration
|
||||
AppDao dao.ModelDao
|
||||
RulesManager *rules.Manager
|
||||
UsageManager *usage.Manager
|
||||
FeatureFlags baseint.FeatureLookup
|
||||
LicenseManager *license.Manager
|
||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||
Cache cache.Cache
|
||||
// Querier Influx Interval
|
||||
FluxInterval time.Duration
|
||||
}
|
||||
|
||||
type APIHandler struct {
|
||||
@@ -51,6 +57,8 @@ func NewAPIHandler(opts APIHandlerOptions) (*APIHandler, error) {
|
||||
RuleManager: opts.RulesManager,
|
||||
FeatureFlags: opts.FeatureFlags,
|
||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||
Cache: opts.Cache,
|
||||
FluxInterval: opts.FluxInterval,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -76,6 +84,10 @@ func (ah *APIHandler) LM() *license.Manager {
|
||||
return ah.opts.LicenseManager
|
||||
}
|
||||
|
||||
func (ah *APIHandler) UM() *usage.Manager {
|
||||
return ah.opts.UsageManager
|
||||
}
|
||||
|
||||
func (ah *APIHandler) AppDao() dao.ModelDao {
|
||||
return ah.opts.AppDao
|
||||
}
|
||||
@@ -144,6 +156,13 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
|
||||
router.HandleFunc("/api/v1/pat", am.OpenAccess(ah.getPATs)).Methods(http.MethodGet)
|
||||
router.HandleFunc("/api/v1/pat/{id}", am.OpenAccess(ah.deletePAT)).Methods(http.MethodDelete)
|
||||
|
||||
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
|
||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||
|
||||
router.HandleFunc("/api/v2/licenses",
|
||||
am.ViewAccess(ah.listLicensesV2)).
|
||||
Methods(http.MethodGet)
|
||||
|
||||
ah.APIHandler.RegisterRoutes(router, am)
|
||||
|
||||
}
|
||||
|
||||
@@ -107,13 +107,13 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
|
||||
RespondError(w, model.InternalError(basemodel.ErrSignupFailed{}), nil)
|
||||
}
|
||||
|
||||
precheckResp := &model.PrecheckResponse{
|
||||
precheckResp := &basemodel.PrecheckResponse{
|
||||
SSO: false,
|
||||
IsUser: false,
|
||||
}
|
||||
|
||||
if domain != nil && domain.SsoEnabled {
|
||||
// so is enabled, create user and respond precheck data
|
||||
// sso is enabled, create user and respond precheck data
|
||||
user, apierr := baseauth.RegisterInvitedUser(ctx, req, true)
|
||||
if apierr != nil {
|
||||
RespondError(w, apierr, nil)
|
||||
|
||||
@@ -4,10 +4,44 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type tierBreakdown struct {
|
||||
UnitPrice float64 `json:"unitPrice"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
TierStart int64 `json:"tierStart"`
|
||||
TierEnd int64 `json:"tierEnd"`
|
||||
TierCost float64 `json:"tierCost"`
|
||||
}
|
||||
|
||||
type usageResponse struct {
|
||||
Type string `json:"type"`
|
||||
Unit string `json:"unit"`
|
||||
Tiers []tierBreakdown `json:"tiers"`
|
||||
}
|
||||
|
||||
type details struct {
|
||||
Total float64 `json:"total"`
|
||||
Breakdown []usageResponse `json:"breakdown"`
|
||||
BaseFee float64 `json:"baseFee"`
|
||||
}
|
||||
|
||||
type billingDetails struct {
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
BillingPeriodStart int64 `json:"billingPeriodStart"`
|
||||
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
|
||||
Details details `json:"details"`
|
||||
Discount float64 `json:"discount"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (ah *APIHandler) listLicenses(w http.ResponseWriter, r *http.Request) {
|
||||
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||
if apiError != nil {
|
||||
@@ -38,3 +72,150 @@ func (ah *APIHandler) applyLicense(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ah.Respond(w, license)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
type checkoutResponse struct {
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
RedirectURL string `json:"redirectURL"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
hClient := &http.Client{}
|
||||
req, err := http.NewRequest("POST", constants.LicenseSignozIo+"/checkout", r.Body)
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||
licenseResp, err := hClient.Do(req)
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// decode response body
|
||||
var resp checkoutResponse
|
||||
if err := json.NewDecoder(licenseResp.Body).Decode(&resp); err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, resp.Data)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||
licenseKey := r.URL.Query().Get("licenseKey")
|
||||
|
||||
if licenseKey == "" {
|
||||
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
||||
return
|
||||
}
|
||||
|
||||
billingURL := fmt.Sprintf("%s/usage?licenseKey=%s", constants.LicenseSignozIo, licenseKey)
|
||||
|
||||
hClient := &http.Client{}
|
||||
req, err := http.NewRequest("GET", billingURL, nil)
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||
billingResp, err := hClient.Do(req)
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// decode response body
|
||||
var billingResponse billingDetails
|
||||
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(srikanthccv):Fetch the current day usage and add it to the response
|
||||
ah.Respond(w, billingResponse.Data)
|
||||
}
|
||||
|
||||
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
licenses, apiError := ah.LM().GetLicenses(context.Background())
|
||||
if apiError != nil {
|
||||
RespondError(w, apiError, nil)
|
||||
}
|
||||
|
||||
resp := model.Licenses{
|
||||
TrialStart: -1,
|
||||
TrialEnd: -1,
|
||||
OnTrial: false,
|
||||
WorkSpaceBlock: false,
|
||||
Licenses: licenses,
|
||||
}
|
||||
|
||||
var currentActiveLicenseKey string
|
||||
|
||||
for _, license := range licenses {
|
||||
if license.IsCurrent {
|
||||
currentActiveLicenseKey = license.Key
|
||||
}
|
||||
}
|
||||
|
||||
// For the case when no license is applied i.e community edition
|
||||
// There will be no trial details or license details
|
||||
if currentActiveLicenseKey == "" {
|
||||
ah.Respond(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch trial details
|
||||
hClient := &http.Client{}
|
||||
url := fmt.Sprintf("%s/trial?licenseKey=%s", constants.LicenseSignozIo, currentActiveLicenseKey)
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
zap.S().Error("Error while creating request for trial details", err)
|
||||
// If there is an error in fetching trial details, we will still return the license details
|
||||
// to avoid blocking the UI
|
||||
ah.Respond(w, resp)
|
||||
return
|
||||
}
|
||||
req.Header.Add("X-SigNoz-SecretKey", constants.LicenseAPIKey)
|
||||
trialResp, err := hClient.Do(req)
|
||||
if err != nil {
|
||||
zap.S().Error("Error while fetching trial details", err)
|
||||
// If there is an error in fetching trial details, we will still return the license details
|
||||
// to avoid incorrectly blocking the UI
|
||||
ah.Respond(w, resp)
|
||||
return
|
||||
}
|
||||
defer trialResp.Body.Close()
|
||||
|
||||
trialRespBody, err := io.ReadAll(trialResp.Body)
|
||||
|
||||
if err != nil || trialResp.StatusCode != http.StatusOK {
|
||||
zap.S().Error("Error while fetching trial details", err)
|
||||
// If there is an error in fetching trial details, we will still return the license details
|
||||
// to avoid incorrectly blocking the UI
|
||||
ah.Respond(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
// decode response body
|
||||
var trialRespData model.SubscriptionServerResp
|
||||
|
||||
if err := json.Unmarshal(trialRespBody, &trialRespData); err != nil {
|
||||
zap.S().Error("Error while decoding trial details", err)
|
||||
// If there is an error in fetching trial details, we will still return the license details
|
||||
// to avoid incorrectly blocking the UI
|
||||
ah.Respond(w, resp)
|
||||
return
|
||||
}
|
||||
|
||||
resp.TrialStart = trialRespData.Data.TrialStart
|
||||
resp.TrialEnd = trialRespData.Data.TrialEnd
|
||||
resp.OnTrial = trialRespData.Data.OnTrial
|
||||
resp.WorkSpaceBlock = trialRespData.Data.WorkSpaceBlock
|
||||
|
||||
ah.Respond(w, resp)
|
||||
}
|
||||
|
||||
@@ -137,8 +137,8 @@ func (ah *APIHandler) queryRangeMetricsV2(w http.ResponseWriter, r *http.Request
|
||||
var s basemodel.Series
|
||||
s.QueryName = name
|
||||
s.Labels = v.Metric.Copy().Map()
|
||||
for _, p := range v.Points {
|
||||
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.V})
|
||||
for _, p := range v.Floats {
|
||||
s.Points = append(s.Points, basemodel.MetricPoint{Timestamp: p.T, Value: p.F})
|
||||
}
|
||||
seriesList = append(seriesList, &s)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import (
|
||||
"go.signoz.io/signoz/pkg/query-service/app/opamp"
|
||||
opAmpModel "go.signoz.io/signoz/pkg/query-service/app/opamp/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/cache"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
"go.signoz.io/signoz/pkg/query-service/healthcheck"
|
||||
basealm "go.signoz.io/signoz/pkg/query-service/integrations/alertManager"
|
||||
@@ -62,6 +63,8 @@ type ServerOptions struct {
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
DialTimeout time.Duration
|
||||
CacheConfigPath string
|
||||
FluxInterval string
|
||||
}
|
||||
|
||||
// Server runs HTTP api service
|
||||
@@ -189,6 +192,21 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
|
||||
telemetry.GetInstance().SetReader(reader)
|
||||
|
||||
var c cache.Cache
|
||||
if serverOptions.CacheConfigPath != "" {
|
||||
cacheOpts, err := cache.LoadFromYAMLCacheConfigFile(serverOptions.CacheConfigPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c = cache.NewCache(cacheOpts)
|
||||
}
|
||||
|
||||
fluxInterval, err := time.ParseDuration(serverOptions.FluxInterval)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiOpts := api.APIHandlerOptions{
|
||||
DataConnector: reader,
|
||||
SkipConfig: skipConfig,
|
||||
@@ -199,9 +217,12 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||
DialTimeout: serverOptions.DialTimeout,
|
||||
AppDao: modelDao,
|
||||
RulesManager: rm,
|
||||
UsageManager: usageManager,
|
||||
FeatureFlags: lm,
|
||||
LicenseManager: lm,
|
||||
LogsParsingPipelineController: logParsingPipelineController,
|
||||
Cache: c,
|
||||
FluxInterval: fluxInterval,
|
||||
}
|
||||
|
||||
apiHandler, err := api.NewAPIHandler(apiOpts)
|
||||
@@ -548,7 +569,7 @@ func (s *Server) Start() error {
|
||||
|
||||
go func() {
|
||||
zap.S().Info("Starting OpAmp Websocket server", zap.String("addr", baseconst.OpAmpWsEndpoint))
|
||||
err := opamp.InitalizeServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents)
|
||||
err := opamp.InitializeAndStartServer(baseconst.OpAmpWsEndpoint, &opAmpModel.AllAgents)
|
||||
if err != nil {
|
||||
zap.S().Info("opamp ws server failed to start", err)
|
||||
s.unavailableChannel <- healthcheck.Unavailable
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
)
|
||||
|
||||
var LicenseSignozIo = "https://license.signoz.io/api/v1"
|
||||
var LicenseAPIKey = GetOrDefaultEnv("SIGNOZ_LICENSE_API_KEY", "")
|
||||
|
||||
var SpanLimitStr = GetOrDefaultEnv("SPAN_LIMIT", "5000")
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ type ModelDao interface {
|
||||
DB() *sqlx.DB
|
||||
|
||||
// auth methods
|
||||
PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError)
|
||||
CanUsePassword(ctx context.Context, email string) (bool, basemodel.BaseApiError)
|
||||
PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError)
|
||||
GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error)
|
||||
|
||||
@@ -5,16 +5,61 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.signoz.io/signoz/ee/query-service/constants"
|
||||
"go.signoz.io/signoz/ee/query-service/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
|
||||
"go.signoz.io/signoz/pkg/query-service/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// PrepareSsoRedirect prepares redirect page link after SSO response
|
||||
func (m *modelDao) createUserForSAMLRequest(ctx context.Context, email string) (*basemodel.User, basemodel.BaseApiError) {
|
||||
// get auth domain from email domain
|
||||
domain, apierr := m.GetDomainByEmail(ctx, email)
|
||||
|
||||
if apierr != nil {
|
||||
zap.S().Errorf("failed to get domain from email", apierr)
|
||||
return nil, model.InternalErrorStr("failed to get domain from email")
|
||||
}
|
||||
|
||||
hash, err := baseauth.PasswordHash(utils.GeneratePassowrd())
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to generate password hash when registering a user via SSO redirect", zap.Error(err))
|
||||
return nil, model.InternalErrorStr("failed to generate password hash")
|
||||
}
|
||||
|
||||
group, apiErr := m.GetGroupByName(ctx, baseconst.ViewerGroup)
|
||||
if apiErr != nil {
|
||||
zap.S().Debugf("GetGroupByName failed, err: %v\n", apiErr.Err)
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
user := &basemodel.User{
|
||||
Id: uuid.NewString(),
|
||||
Name: "",
|
||||
Email: email,
|
||||
Password: hash,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
ProfilePictureURL: "", // Currently unused
|
||||
GroupId: group.Id,
|
||||
OrgId: domain.OrgId,
|
||||
}
|
||||
|
||||
user, apiErr = m.CreateUser(ctx, user, false)
|
||||
if apiErr != nil {
|
||||
zap.S().Debugf("CreateUser failed, err: %v\n", apiErr.Err)
|
||||
return nil, apiErr
|
||||
}
|
||||
|
||||
return user, nil
|
||||
|
||||
}
|
||||
|
||||
// PrepareSsoRedirect prepares redirect page link after SSO response
|
||||
// is successfully parsed (i.e. valid email is available)
|
||||
func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email string) (redirectURL string, apierr basemodel.BaseApiError) {
|
||||
|
||||
@@ -24,7 +69,20 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
return "", model.BadRequestStr("invalid user email received from the auth provider")
|
||||
}
|
||||
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(&userPayload.User)
|
||||
user := &basemodel.User{}
|
||||
|
||||
if userPayload == nil {
|
||||
newUser, apiErr := m.createUserForSAMLRequest(ctx, email)
|
||||
user = newUser
|
||||
if apiErr != nil {
|
||||
zap.S().Errorf("failed to create user with email received from auth provider: %v", apierr.Error())
|
||||
return "", apiErr
|
||||
}
|
||||
} else {
|
||||
user = &userPayload.User
|
||||
}
|
||||
|
||||
tokenStore, err := baseauth.GenerateJWTForUser(user)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to generate token for SSO login user", err)
|
||||
return "", model.InternalErrorStr("failed to generate token for the user")
|
||||
@@ -33,7 +91,7 @@ func (m *modelDao) PrepareSsoRedirect(ctx context.Context, redirectUri, email st
|
||||
return fmt.Sprintf("%s?jwt=%s&usr=%s&refreshjwt=%s",
|
||||
redirectUri,
|
||||
tokenStore.AccessJwt,
|
||||
userPayload.User.Id,
|
||||
user.Id,
|
||||
tokenStore.RefreshJwt), nil
|
||||
}
|
||||
|
||||
@@ -62,10 +120,10 @@ func (m *modelDao) CanUsePassword(ctx context.Context, email string) (bool, base
|
||||
|
||||
// PrecheckLogin is called when the login or signup page is loaded
|
||||
// to check sso login is to be prompted
|
||||
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*model.PrecheckResponse, basemodel.BaseApiError) {
|
||||
func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (*basemodel.PrecheckResponse, basemodel.BaseApiError) {
|
||||
|
||||
// assume user is valid unless proven otherwise
|
||||
resp := &model.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||||
resp := &basemodel.PrecheckResponse{IsUser: true, CanSelfRegister: false}
|
||||
|
||||
// check if email is a valid user
|
||||
userPayload, baseApiErr := m.GetUserByEmail(ctx, email)
|
||||
@@ -76,6 +134,7 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
|
||||
if userPayload == nil {
|
||||
resp.IsUser = false
|
||||
}
|
||||
|
||||
ssoAvailable := true
|
||||
err := m.checkFeature(model.SSO)
|
||||
if err != nil {
|
||||
@@ -91,6 +150,8 @@ func (m *modelDao) PrecheckLogin(ctx context.Context, email, sourceUrl string) (
|
||||
|
||||
if ssoAvailable {
|
||||
|
||||
resp.IsUser = true
|
||||
|
||||
// find domain from email
|
||||
orgDomain, apierr := m.GetDomainByEmail(ctx, email)
|
||||
if apierr != nil {
|
||||
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -28,29 +28,70 @@ type StoredDomain struct {
|
||||
|
||||
// GetDomainFromSsoResponse uses relay state received from IdP to fetch
|
||||
// user domain. The domain is further used to process validity of the response.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId as query parameter.
|
||||
// when sending login request to IdP we send relay state as URL (site url)
|
||||
// with domainId or domainName as query parameter.
|
||||
func (m *modelDao) GetDomainFromSsoResponse(ctx context.Context, relayState *url.URL) (*model.OrgDomain, error) {
|
||||
// derive domain id from relay state now
|
||||
var domainIdStr string
|
||||
var domainIdStr string
|
||||
var domainNameStr string
|
||||
var domain *model.OrgDomain
|
||||
|
||||
for k, v := range relayState.Query() {
|
||||
if k == "domainId" && len(v) > 0 {
|
||||
domainIdStr = strings.Replace(v[0], ":", "-", -1)
|
||||
}
|
||||
if k == "domainName" && len(v) > 0 {
|
||||
domainNameStr = v[0]
|
||||
}
|
||||
}
|
||||
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if domainIdStr != "" {
|
||||
domainId, err := uuid.Parse(domainIdStr)
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to parse domainId from relay state", err)
|
||||
return nil, fmt.Errorf("failed to parse domainId from IdP response")
|
||||
}
|
||||
|
||||
domain, err = m.GetDomain(ctx, domainId)
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain from domainId received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
|
||||
if domainNameStr != "" {
|
||||
|
||||
domainFromDB, err := m.GetDomainByName(ctx, domainNameStr)
|
||||
domain = domainFromDB
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain from domainName received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
}
|
||||
}
|
||||
if domain != nil {
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find domain received in IdP response")
|
||||
}
|
||||
|
||||
// GetDomainByName returns org domain for a given domain name
|
||||
func (m *modelDao) GetDomainByName(ctx context.Context, name string) (*model.OrgDomain, basemodel.BaseApiError) {
|
||||
|
||||
stored := StoredDomain{}
|
||||
err := m.DB().Get(&stored, `SELECT * FROM org_domains WHERE name=$1 LIMIT 1`, name)
|
||||
|
||||
if err != nil {
|
||||
zap.S().Errorf("failed to parse domain id from relay state", err)
|
||||
return nil, fmt.Errorf("failed to parse response from IdP response")
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, model.BadRequest(fmt.Errorf("invalid domain name"))
|
||||
}
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
domain, err := m.GetDomain(ctx, domainId)
|
||||
if (err != nil) || domain == nil {
|
||||
zap.S().Errorf("failed to find domain received in IdP response", err.Error())
|
||||
return nil, fmt.Errorf("invalid credentials")
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
@@ -69,7 +110,7 @@ func (m *modelDao) GetDomain(ctx context.Context, id uuid.UUID) (*model.OrgDomai
|
||||
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return domain, model.InternalError(err)
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
@@ -206,7 +247,7 @@ func (m *modelDao) GetDomainByEmail(ctx context.Context, email string) (*model.O
|
||||
|
||||
domain := &model.OrgDomain{Id: stored.Id, Name: stored.Name, OrgId: stored.OrgId}
|
||||
if err := domain.LoadConfig(stored.Data); err != nil {
|
||||
return domain, model.InternalError(err)
|
||||
return nil, model.InternalError(err)
|
||||
}
|
||||
return domain, nil
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ func main() {
|
||||
// the url used to build link in the alert messages in slack and other systems
|
||||
var ruleRepoURL string
|
||||
|
||||
var cacheConfigPath, fluxInterval string
|
||||
var enableQueryServiceLogOTLPExport bool
|
||||
var preferDelta bool
|
||||
var preferSpanMetrics bool
|
||||
@@ -99,10 +100,14 @@ func main() {
|
||||
flag.IntVar(&maxOpenConns, "max-open-conns", 100, "(max connections for use at any time.)")
|
||||
flag.DurationVar(&dialTimeout, "dial-timeout", 5*time.Second, "(the maximum time to establish a connection.)")
|
||||
flag.StringVar(&ruleRepoURL, "rules.repo-url", baseconst.AlertHelpPage, "(host address used to build rule link in alert messages)")
|
||||
flag.StringVar(&cacheConfigPath, "experimental.cache-config", "", "(cache config to use)")
|
||||
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
|
||||
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
loggerMgr := initZapLog(enableQueryServiceLogOTLPExport)
|
||||
|
||||
zap.ReplaceGlobals(loggerMgr)
|
||||
defer loggerMgr.Sync() // flushes buffer, if any
|
||||
|
||||
@@ -121,6 +126,8 @@ func main() {
|
||||
MaxIdleConns: maxIdleConns,
|
||||
MaxOpenConns: maxOpenConns,
|
||||
DialTimeout: dialTimeout,
|
||||
CacheConfigPath: cacheConfigPath,
|
||||
FluxInterval: fluxInterval,
|
||||
}
|
||||
|
||||
// Read the jwt secret key
|
||||
|
||||
@@ -4,18 +4,9 @@ import (
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
// PrecheckResponse contains login precheck response
|
||||
type PrecheckResponse struct {
|
||||
SSO bool `json:"sso"`
|
||||
SsoUrl string `json:"ssoUrl"`
|
||||
CanSelfRegister bool `json:"canSelfRegister"`
|
||||
IsUser bool `json:"isUser"`
|
||||
SsoError string `json:"ssoError"`
|
||||
}
|
||||
|
||||
// GettableInvitation overrides base object and adds precheck into
|
||||
// response
|
||||
type GettableInvitation struct {
|
||||
*basemodel.InvitationResponseObject
|
||||
Precheck *PrecheckResponse `json:"precheck"`
|
||||
Precheck *basemodel.PrecheckResponse `json:"precheck"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
basemodel "go.signoz.io/signoz/pkg/query-service/model"
|
||||
)
|
||||
|
||||
@@ -61,7 +62,6 @@ func InternalError(err error) *ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// InternalErrorStr returns a ApiError object of internal type for string input
|
||||
func InternalErrorStr(s string) *ApiError {
|
||||
return &ApiError{
|
||||
@@ -69,6 +69,7 @@ func InternalErrorStr(s string) *ApiError {
|
||||
Err: fmt.Errorf(s),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrorNone basemodel.ErrorType = ""
|
||||
ErrorTimeout basemodel.ErrorType = "timeout"
|
||||
|
||||
@@ -89,3 +89,16 @@ func (l *License) ParseFeatures() {
|
||||
l.FeatureSet = BasicPlan
|
||||
}
|
||||
}
|
||||
|
||||
type Licenses struct {
|
||||
TrialStart int64 `json:"trialStart"`
|
||||
TrialEnd int64 `json:"trialEnd"`
|
||||
OnTrial bool `json:"onTrial"`
|
||||
WorkSpaceBlock bool `json:"workSpaceBlock"`
|
||||
Licenses []License `json:"licenses"`
|
||||
}
|
||||
|
||||
type SubscriptionServerResp struct {
|
||||
Status string `json:"status"`
|
||||
Data Licenses `json:"data"`
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ const Basic = "BASIC_PLAN"
|
||||
const Pro = "PRO_PLAN"
|
||||
const Enterprise = "ENTERPRISE_PLAN"
|
||||
const DisableUpsell = "DISABLE_UPSELL"
|
||||
const Onboarding = "ONBOARDING"
|
||||
const ChatSupport = "CHAT_SUPPORT"
|
||||
|
||||
var BasicPlan = basemodel.FeatureSet{
|
||||
basemodel.Feature{
|
||||
@@ -81,6 +83,13 @@ var BasicPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: false,
|
||||
@@ -161,6 +170,13 @@ var ProPlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
@@ -241,6 +257,13 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelOpsgenie,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: basemodel.AlertChannelMsTeams,
|
||||
Active: true,
|
||||
@@ -255,4 +278,18 @@ var EnterprisePlan = basemodel.FeatureSet{
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: Onboarding,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
basemodel.Feature{
|
||||
Name: ChatSupport,
|
||||
Active: true,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
node_modules
|
||||
.vscode
|
||||
build
|
||||
.env
|
||||
.git
|
||||
|
||||
6
frontend/.prettierignore
Normal file
6
frontend/.prettierignore
Normal file
@@ -0,0 +1,6 @@
|
||||
# Ignore artifacts:
|
||||
build
|
||||
coverage
|
||||
|
||||
# Ignore all MD files:
|
||||
**/*.md
|
||||
@@ -1,38 +1,17 @@
|
||||
# Builder stage
|
||||
FROM node:16.15.0 as builder
|
||||
FROM nginx:1.25.2-alpine
|
||||
|
||||
# Add Maintainer Info
|
||||
LABEL maintainer="signoz"
|
||||
|
||||
ARG TARGETOS=linux
|
||||
ARG TARGETARCH
|
||||
|
||||
# Set working directory
|
||||
WORKDIR /frontend
|
||||
|
||||
# Copy the package.json and .yarnrc files prior to install dependencies
|
||||
COPY package.json ./
|
||||
# Copy lock file
|
||||
COPY yarn.lock ./
|
||||
COPY .yarnrc ./
|
||||
|
||||
# Install the dependencies and make the folder
|
||||
RUN CI=1 yarn install
|
||||
|
||||
COPY . .
|
||||
|
||||
# Build the project and copy the files
|
||||
RUN yarn build
|
||||
|
||||
|
||||
FROM nginx:1.24.0-alpine
|
||||
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Remove default nginx index page
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Copy from the stahg 1
|
||||
COPY --from=builder /frontend/build /usr/share/nginx/html
|
||||
# Copy custom nginx config and static files
|
||||
COPY conf/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY build /usr/share/nginx/html
|
||||
|
||||
EXPOSE 3301
|
||||
|
||||
|
||||
7
frontend/example.env
Normal file
7
frontend/example.env
Normal file
@@ -0,0 +1,7 @@
|
||||
NODE_ENV="development"
|
||||
BUNDLE_ANALYSER="true"
|
||||
FRONTEND_API_ENDPOINT="http://localhost:3301/"
|
||||
INTERCOM_APP_ID="intercom-app-id"
|
||||
|
||||
PLAYWRIGHT_TEST_BASE_URL="http://localhost:3301"
|
||||
CI="1"
|
||||
@@ -7,7 +7,7 @@ const config: Config.InitialOptions = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
moduleNameMapper: {
|
||||
'\\.(css|less)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
},
|
||||
globals: {
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
|
||||
@@ -29,27 +29,31 @@
|
||||
"dependencies": {
|
||||
"@ant-design/colors": "6.0.0",
|
||||
"@ant-design/icons": "4.8.0",
|
||||
"@grafana/data": "^8.4.3",
|
||||
"@grafana/data": "^9.5.2",
|
||||
"@mdx-js/loader": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@monaco-editor/react": "^4.3.1",
|
||||
"@uiw/react-md-editor": "3.23.5",
|
||||
"@xstate/react": "^3.0.0",
|
||||
"ansi-to-html": "0.7.2",
|
||||
"antd": "5.0.5",
|
||||
"antd-table-saveas-excel": "2.2.1",
|
||||
"axios": "^0.21.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.0",
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-jest": "^29.6.4",
|
||||
"babel-loader": "9.1.3",
|
||||
"babel-plugin-named-asset-import": "^0.3.7",
|
||||
"babel-preset-minify": "^0.5.1",
|
||||
"babel-preset-react-app": "^10.0.0",
|
||||
"babel-preset-react-app": "^10.0.1",
|
||||
"chart.js": "3.9.1",
|
||||
"chartjs-adapter-date-fns": "^2.0.0",
|
||||
"chartjs-plugin-annotation": "^1.4.0",
|
||||
"classnames": "2.3.2",
|
||||
"color": "^4.2.1",
|
||||
"color-alpha": "1.1.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "4.3.0",
|
||||
"css-minimizer-webpack-plugin": "^3.2.0",
|
||||
"css-loader": "5.0.0",
|
||||
"css-minimizer-webpack-plugin": "5.0.1",
|
||||
"dayjs": "^1.10.7",
|
||||
"dompurify": "3.0.0",
|
||||
"dotenv": "8.2.0",
|
||||
@@ -58,7 +62,7 @@
|
||||
"file-loader": "6.1.1",
|
||||
"fontfaceobserver": "2.3.0",
|
||||
"history": "4.10.1",
|
||||
"html-webpack-plugin": "5.1.0",
|
||||
"html-webpack-plugin": "5.5.0",
|
||||
"i18next": "^21.6.12",
|
||||
"i18next-browser-languagedetector": "^6.1.3",
|
||||
"i18next-http-backend": "^1.3.2",
|
||||
@@ -75,21 +79,23 @@
|
||||
"react-dnd-html5-backend": "16.0.1",
|
||||
"react-dom": "18.2.0",
|
||||
"react-drag-listview": "2.0.0",
|
||||
"react-force-graph": "^1.41.0",
|
||||
"react-force-graph": "^1.43.0",
|
||||
"react-grid-layout": "^1.3.4",
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-i18next": "^11.16.1",
|
||||
"react-intersection-observer": "9.4.1",
|
||||
"react-markdown": "8.0.7",
|
||||
"react-query": "^3.34.19",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-syntax-highlighter": "15.5.0",
|
||||
"react-use": "^17.3.2",
|
||||
"react-virtuoso": "4.0.3",
|
||||
"redux": "^4.0.5",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"stream": "^0.0.2",
|
||||
"style-loader": "1.3.0",
|
||||
"styled-components": "^5.2.1",
|
||||
"styled-components": "^5.3.11",
|
||||
"terser-webpack-plugin": "^5.2.5",
|
||||
"timestamp-nano": "^1.0.0",
|
||||
"ts-node": "^10.2.1",
|
||||
@@ -97,8 +103,8 @@
|
||||
"typescript": "^4.0.5",
|
||||
"uuid": "^8.3.2",
|
||||
"web-vitals": "^0.2.4",
|
||||
"webpack": "^5.23.0",
|
||||
"webpack-dev-server": "^4.3.1",
|
||||
"webpack": "5.88.2",
|
||||
"webpack-dev-server": "^4.15.1",
|
||||
"xstate": "^4.31.0"
|
||||
},
|
||||
"browserslist": {
|
||||
@@ -114,13 +120,13 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.13",
|
||||
"@babel/core": "^7.22.11",
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.12.13",
|
||||
"@babel/preset-env": "^7.12.17",
|
||||
"@babel/preset-env": "^7.22.14",
|
||||
"@babel/preset-react": "^7.12.13",
|
||||
"@babel/preset-typescript": "^7.12.17",
|
||||
"@commitlint/cli": "^16.2.4",
|
||||
"@babel/preset-typescript": "^7.21.4",
|
||||
"@commitlint/cli": "^16.3.0",
|
||||
"@commitlint/config-conventional": "^16.2.4",
|
||||
"@jest/globals": "^27.5.1",
|
||||
"@playwright/test": "^1.22.0",
|
||||
@@ -146,25 +152,25 @@
|
||||
"@types/react-redux": "^7.1.11",
|
||||
"@types/react-resizable": "3.0.3",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@types/react-syntax-highlighter": "15.5.7",
|
||||
"@types/styled-components": "^5.1.4",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@types/webpack": "^5.28.0",
|
||||
"@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",
|
||||
"@types/webpack-dev-server": "^4.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"autoprefixer": "^9.0.0",
|
||||
"babel-plugin-styled-components": "^1.12.0",
|
||||
"compression-webpack-plugin": "9.0.0",
|
||||
"copy-webpack-plugin": "^8.1.0",
|
||||
"critters-webpack-plugin": "^3.0.1",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint": "^7.32.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.25.4",
|
||||
"eslint-plugin-jest": "^26.1.2",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jest": "^26.9.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
@@ -175,17 +181,20 @@
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
"is-ci": "^3.0.1",
|
||||
"jest-playwright-preset": "^1.7.0",
|
||||
"jest-playwright-preset": "^1.7.2",
|
||||
"jest-styled-components": "^7.0.8",
|
||||
"lint-staged": "^12.3.7",
|
||||
"lint-staged": "^12.5.0",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"prettier": "2.2.1",
|
||||
"raw-loader": "4.0.2",
|
||||
"react-hooks-testing-library": "0.6.0",
|
||||
"react-hot-loader": "^4.13.0",
|
||||
"react-resizable": "3.0.4",
|
||||
"ts-jest": "^27.1.4",
|
||||
"sass": "1.66.1",
|
||||
"sass-loader": "13.3.2",
|
||||
"ts-jest": "^27.1.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript-plugin-css-modules": "^3.4.0",
|
||||
"typescript-plugin-css-modules": "5.0.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
},
|
||||
@@ -196,6 +205,9 @@
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "18.0.26",
|
||||
"@types/react-dom": "18.0.10"
|
||||
"@types/react-dom": "18.0.10",
|
||||
"debug": "4.3.4",
|
||||
"semver": "7.5.4",
|
||||
"xml2js": "0.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
1
frontend/public/Logos/cmd-terminal.svg
Normal file
1
frontend/public/Logos/cmd-terminal.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 103.53" style="enable-background:new 0 0 122.88 103.53" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;} .st0{fill:#1668dc;} .st1{fill:#FFFFFF;}</style><g><path class="st0" d="M5.47,0h111.93c3.01,0,5.47,2.46,5.47,5.47v92.58c0,3.01-2.46,5.47-5.47,5.47H5.47 c-3.01,0-5.47-2.46-5.47-5.47V5.47C0,2.46,2.46,0,5.47,0L5.47,0z M31.84,38.55l17.79,18.42l2.14,2.13l-2.12,2.16L31.68,80.31 l-5.07-5l15.85-16.15L26.81,43.6L31.84,38.55L31.84,38.55z M94.1,79.41H54.69v-6.84H94.1V79.41L94.1,79.41z M38.19,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S35,9.83,38.19,9.83L38.19,9.83z M18.95,9.83 c3.19,0,5.78,2.59,5.78,5.78s-2.59,5.78-5.78,5.78c-3.19,0-5.78-2.59-5.78-5.78S15.75,9.83,18.95,9.83L18.95,9.83z M7.49,5.41 h107.91c1.15,0,2.09,0.94,2.09,2.09v18.32H5.4V7.5C5.4,6.35,6.34,5.41,7.49,5.41L7.49,5.41z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
1
frontend/public/Logos/docker.svg
Normal file
1
frontend/public/Logos/docker.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 88.17" style="enable-background:new 0 0 122.88 88.17" xml:space="preserve"><style type="text/css">.st0{fill:#0091E2;}</style><g><path class="st0" d="M121.68,33.34c-0.34-0.28-3.42-2.62-10.03-2.62c-1.71,0-3.48,0.17-5.19,0.46c-1.25-8.72-8.49-12.94-8.78-13.16 l-1.77-1.03l-1.14,1.65c-1.42,2.22-2.51,4.73-3.13,7.29c-1.2,4.96-0.46,9.63,2.05,13.62c-3.02,1.71-7.92,2.11-8.95,2.17l-80.93,0 c-2.11,0-3.82,1.71-3.82,3.82c-0.11,7.07,1.08,14.13,3.53,20.8c2.79,7.29,6.95,12.71,12.31,16.01c6.04,3.7,15.9,5.81,27.01,5.81 c5.01,0,10.03-0.46,14.99-1.37c6.9-1.25,13.51-3.65,19.6-7.12c5.02-2.91,9.52-6.61,13.34-10.94c6.44-7.24,10.26-15.33,13.05-22.51 c0.4,0,0.74,0,1.14,0c7.01,0,11.34-2.79,13.73-5.19c1.6-1.48,2.79-3.31,3.65-5.36l0.51-1.48L121.68,33.34L121.68,33.34z M71.59,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C70.68,38.98,71.08,39.38,71.59,39.38L71.59,39.38z M56.49,11.63h10.83c0.51,0,0.97-0.4,0.97-0.97V0.97c0-0.51-0.46-0.97-0.97-0.97 L56.49,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,11.17,55.97,11.63,56.49,11.63L56.49,11.63z M56.49,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.46-0.97-0.97-0.97H56.49c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C55.52,25.08,55.97,25.53,56.49,25.53L56.49,25.53z M41.5,25.53h10.83c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0H41.5c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C40.53,25.08,40.93,25.53,41.5,25.53L41.5,25.53z M26.28,25.53h10.83 c0.51,0,0.97-0.46,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0H26.28c-0.51,0-0.97,0.4-0.97,0.97v9.69 C25.37,25.08,25.77,25.53,26.28,25.53L26.28,25.53z M56.49,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97v-9.69c0-0.51-0.4-0.97-0.97-0.97 l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69C55.52,38.98,55.97,39.38,56.49,39.38L56.49,39.38L56.49,39.38z M41.5,39.38 h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0v9.69 C40.53,38.98,40.93,39.38,41.5,39.38L41.5,39.38L41.5,39.38z M26.28,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69 c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97v9.69C25.37,38.98,25.77,39.38,26.28,39.38L26.28,39.38z M11.35,39.38h10.83c0.51,0,0.97-0.4,0.97-0.97l0,0v-9.69c0-0.51-0.4-0.97-0.97-0.97l0,0l-10.83,0c-0.51,0-0.97,0.4-0.97,0.97l0,0 v9.69C10.44,38.98,10.84,39.38,11.35,39.38L11.35,39.38L11.35,39.38z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
1
frontend/public/Logos/kubernetes.svg
Normal file
1
frontend/public/Logos/kubernetes.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.9 KiB |
1
frontend/public/Logos/node-js.svg
Normal file
1
frontend/public/Logos/node-js.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 109 122.88" style="enable-background:new 0 0 109 122.88" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#689F63;}</style><g><path class="st0" d="M68.43,87.08c-19.7,0-23.83-9.04-23.83-16.63c0-0.72,0.58-1.3,1.3-1.3h5.82c0.64,0,1.18,0.47,1.28,1.1 c0.88,5.93,3.49,8.92,15.41,8.92c9.49,0,13.52-2.14,13.52-7.18c0-2.9-1.15-5.05-15.89-6.49c-12.33-1.22-19.95-3.93-19.95-13.8 c0-9.08,7.66-14.49,20.5-14.49c14.42,0,21.56,5,22.46,15.76c0.03,0.37-0.1,0.73-0.35,1c-0.25,0.26-0.6,0.42-0.96,0.42H81.9 c-0.61,0-1.14-0.43-1.26-1.01c-1.41-6.23-4.81-8.23-14.07-8.23c-10.36,0-11.56,3.61-11.56,6.31c0,3.28,1.42,4.24,15.4,6.09 c13.84,1.84,20.41,4.43,20.41,14.16c0,9.81-8.18,15.43-22.45,15.43L68.43,87.08L68.43,87.08z M54.52,122.88 c-1.65,0-3.28-0.43-4.72-1.26l-15.03-8.9c-2.25-1.26-1.15-1.7-0.41-1.96c2.99-1.05,3.6-1.28,6.8-3.1c0.34-0.19,0.78-0.12,1.12,0.08 l11.55,6.85c0.42,0.23,1.01,0.23,1.4,0l45.03-25.99c0.42-0.24,0.69-0.72,0.69-1.22V35.43c0-0.52-0.27-0.98-0.7-1.24L55.23,8.22 c-0.42-0.25-0.97-0.25-1.39,0l-45,25.97c-0.44,0.25-0.71,0.73-0.71,1.23v51.96c0,0.5,0.27,0.97,0.7,1.21l12.33,7.12 c6.69,3.35,10.79-0.6,10.79-4.56V39.86c0-0.73,0.57-1.3,1.31-1.3l5.7,0c0.71,0,1.3,0.56,1.3,1.3v51.31 c0,8.93-4.87,14.05-13.33,14.05c-2.6,0-4.66,0-10.38-2.82L4.72,95.59C1.8,93.9,0,90.75,0,87.38V35.42c0-3.38,1.8-6.54,4.72-8.21 l45.07-26c2.85-1.61,6.64-1.61,9.47,0l45.02,26.01c2.91,1.68,4.72,4.82,4.72,8.21v51.96c0,3.37-1.81,6.51-4.72,8.21l-45.02,26 c-1.44,0.83-3.08,1.26-4.74,1.26L54.52,122.88L54.52,122.88z M54.52,122.88L54.52,122.88L54.52,122.88L54.52,122.88z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
1
frontend/public/Logos/software-window.svg
Normal file
1
frontend/public/Logos/software-window.svg
Normal file
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 122.88 98.18" style="enable-background:new 0 0 122.88 98.18" xml:space="preserve"><style type="text/css">.st0{fill-rule:evenodd;clip-rule:evenodd;} .st0{fill:#1668dc;} .st1{fill:#FFFFFF;}</style><g><path class="st0" d="M3.42,0h116.05c1.88,0,3.41,1.54,3.41,3.41v91.36c0,1.88-1.54,3.41-3.41,3.41l-116.05,0 C1.54,98.18,0,96.65,0,94.77L0,3.41C0,1.53,1.54,0,3.42,0L3.42,0L3.42,0z M25.89,8.19c2.05,0,3.72,1.67,3.72,3.72 c0,2.05-1.67,3.72-3.72,3.72c-2.05,0-3.72-1.67-3.72-3.72C22.17,9.85,23.83,8.19,25.89,8.19L25.89,8.19z M103.07,7.69l2.52,2.77 l2.52-2.77l1.97,1.79l-2.69,2.96l2.69,2.96l-1.97,1.79l-2.52-2.77l-2.52,2.77l-1.97-1.79l2.69-2.96l-2.69-2.96L103.07,7.69 L103.07,7.69z M14.52,8.19c2.05,0,3.72,1.67,3.72,3.72c0,2.05-1.67,3.72-3.72,3.72c-2.05,0-3.72-1.67-3.72-3.72 C10.79,9.85,12.46,8.19,14.52,8.19L14.52,8.19z M37.26,8.19c2.05,0,3.72,1.67,3.72,3.72c0,2.05-1.67,3.72-3.72,3.72 c-2.05,0-3.72-1.67-3.72-3.72C33.54,9.85,35.21,8.19,37.26,8.19L37.26,8.19z M14.05,22.75h93.33c1.77,0,3.22,1.49,3.22,3.22v59.2 c0,1.73-1.49,3.22-3.22,3.22l-93.33,0c-1.73,0-3.22-1.45-3.22-3.22v-59.2C10.84,24.2,12.29,22.75,14.05,22.75L14.05,22.75 L14.05,22.75z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
9
frontend/public/Logos/syslogs.svg
Normal file
9
frontend/public/Logos/syslogs.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 6.2 KiB |
@@ -13,5 +13,12 @@
|
||||
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
||||
"error_loading_json": "Error loading JSON file",
|
||||
"empty_json_not_allowed": "Empty JSON is not allowed",
|
||||
"new_dashboard_title": "Sample Title"
|
||||
"new_dashboard_title": "Sample Title",
|
||||
"layout_saved_successfully": "Layout saved successfully",
|
||||
"add_panel": "Add Panel",
|
||||
"save_layout": "Save Layout",
|
||||
"variable_updated_successfully": "Variable updated successfully",
|
||||
"error_while_updating_variable": "Error while updating variable",
|
||||
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
|
||||
}
|
||||
|
||||
3
frontend/public/locales/en-GB/explorer.json
Normal file
3
frontend/public/locales/en-GB/explorer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
1
frontend/public/locales/en-GB/logs.json
Normal file
1
frontend/public/locales/en-GB/logs.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "fetching_log_lines": "Fetching log lines" }
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls",
|
||||
"pipeline": "Pipeline",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"ingestion_settings": "Ingestion Settings",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls",
|
||||
"pipeline": "Pipeline",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"INSTRUMENTATION": "SigNoz | Get Started",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
"DASHBOARD": "SigNoz | Dashboard",
|
||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||
"ALERTS_NEW": "SigNoz | New Alert",
|
||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||
"ALL_ERROR": "SigNoz | All Errors",
|
||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||
"VERSION": "SigNoz | Version",
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||
}
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"GET_STARTED": "SigNoz | Get Started",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
"DASHBOARD": "SigNoz | Dashboard",
|
||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||
"ALERTS_NEW": "SigNoz | New Alert",
|
||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||
"ALL_ERROR": "SigNoz | All Errors",
|
||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||
"VERSION": "SigNoz | Version",
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
"field_slack_recipient": "Recipient",
|
||||
"field_slack_title": "Title",
|
||||
"field_slack_description": "Description",
|
||||
"field_opsgenie_api_key": "API Key",
|
||||
"field_opsgenie_description": "Description",
|
||||
"placeholder_opsgenie_description": "Description",
|
||||
"field_webhook_username": "User Name (optional)",
|
||||
"field_webhook_password": "Password (optional)",
|
||||
"field_pager_routing_key": "Routing Key",
|
||||
@@ -31,8 +34,12 @@
|
||||
"field_pager_class": "Class",
|
||||
"field_pager_client": "Client",
|
||||
"field_pager_client_url": "Client URL",
|
||||
"field_opsgenie_message": "Message",
|
||||
"field_opsgenie_priority": "Priority",
|
||||
"placeholder_slack_description": "Description",
|
||||
"placeholder_pager_description": "Description",
|
||||
"placeholder_opsgenie_message": "Message",
|
||||
"placeholder_opsgenie_priority": "Priority",
|
||||
"help_pager_client": "Shows up as event source in Pagerduty",
|
||||
"help_pager_client_url": "Shows up as event source link in Pagerduty",
|
||||
"help_pager_class": "The class/type of the event",
|
||||
@@ -43,6 +50,9 @@
|
||||
"help_webhook_username": "Leave empty for bearer auth or when authentication is not necessary.",
|
||||
"help_webhook_password": "Specify a password or bearer token",
|
||||
"help_pager_description": "Shows up as description in pagerduty",
|
||||
"help_opsgenie_message": "Shows up as message in opsgenie",
|
||||
"help_opsgenie_priority": "Priority of the incident",
|
||||
"help_opsgenie_description": "Shows up as description in opsgenie",
|
||||
"channel_creation_done": "Successfully created the channel",
|
||||
"channel_creation_failed": "An unexpected error occurred while creating this channel",
|
||||
"channel_edit_done": "Channels Edited Successfully",
|
||||
|
||||
@@ -13,5 +13,12 @@
|
||||
"import_dashboard_by_pasting": "Import dashboard by pasting JSON or importing JSON file",
|
||||
"error_loading_json": "Error loading JSON file",
|
||||
"empty_json_not_allowed": "Empty JSON is not allowed",
|
||||
"new_dashboard_title": "Sample Title"
|
||||
"new_dashboard_title": "Sample Title",
|
||||
"layout_saved_successfully": "Layout saved successfully",
|
||||
"add_panel": "Add Panel",
|
||||
"save_layout": "Save Layout",
|
||||
"variable_updated_successfully": "Variable updated successfully",
|
||||
"error_while_updating_variable": "Error while updating variable",
|
||||
"dashboard_has_been_updated": "Dashboard has been updated",
|
||||
"do_you_want_to_refresh_the_dashboard": "Do you want to refresh the dashboard?"
|
||||
}
|
||||
|
||||
3
frontend/public/locales/en/explorer.json
Normal file
3
frontend/public/locales/en/explorer.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"name_of_the_view": "Name of the view"
|
||||
}
|
||||
1
frontend/public/locales/en/logs.json
Normal file
1
frontend/public/locales/en/logs.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "fetching_log_lines": "Fetching log lines" }
|
||||
@@ -25,6 +25,7 @@
|
||||
"delete_processor_description": "Logs are processed sequentially in processors. Deleting a processor may change content of data processed by other processors",
|
||||
"search_pipeline_placeholder": "Filter Pipelines",
|
||||
"pipeline_name_placeholder": "Name",
|
||||
"pipeline_filter_placeholder": "Filter for selecting logs to be processed by this pipeline. Example: service_name = billing",
|
||||
"pipeline_tags_placeholder": "Tags",
|
||||
"pipeline_description_placeholder": "Enter description for your pipeline",
|
||||
"processor_name_placeholder": "Name",
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls",
|
||||
"pipeline": "Pipeline",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
{
|
||||
"general": "General",
|
||||
"alert_channels": "Alert Channels",
|
||||
"organization_settings": "Organization Settings",
|
||||
"ingestion_settings": "Ingestion Settings",
|
||||
"my_settings": "My Settings",
|
||||
"overview_metrics": "Overview Metrics",
|
||||
"dbcall_metrics": "Database Calls",
|
||||
"external_metrics": "External Calls",
|
||||
"pipeline": "Pipeline",
|
||||
"pipelines": "Pipelines",
|
||||
"archives": "Archives",
|
||||
"logs_to_metrics": "Logs To Metrics"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"label_orgname": "Organization Name",
|
||||
"placeholder_orgname": "Your Company",
|
||||
"prompt_keepme_posted": "Keep me updated on new SigNoz features",
|
||||
"prompt_anonymise": "Anonymise my usage date. We collect data to measure product usage",
|
||||
"prompt_anonymise": "Anonymise my usage data. We collect data to measure product usage",
|
||||
"failed_confirm_password": "Passwords don’t match. Please try again",
|
||||
"unexpected_error": "Something went wrong",
|
||||
"failed_to_initiate_login": "Signup completed but failed to initiate login",
|
||||
|
||||
@@ -1,36 +1,38 @@
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"INSTRUMENTATION": "SigNoz | Get Started",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
"DASHBOARD": "SigNoz | Dashboard",
|
||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||
"ALERTS_NEW": "SigNoz | New Alert",
|
||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||
"ALL_ERROR": "SigNoz | All Errors",
|
||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||
"VERSION": "SigNoz | Version",
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||
}
|
||||
{
|
||||
"SIGN_UP": "SigNoz | Sign Up",
|
||||
"LOGIN": "SigNoz | Login",
|
||||
"SERVICE_METRICS": "SigNoz | Service Metrics",
|
||||
"SERVICE_MAP": "SigNoz | Service Map",
|
||||
"GET_STARTED": "SigNoz | Get Started",
|
||||
"TRACE": "SigNoz | Trace",
|
||||
"TRACE_DETAIL": "SigNoz | Trace Detail",
|
||||
"TRACES_EXPLORER": "SigNoz | Traces Explorer",
|
||||
"SETTINGS": "SigNoz | Settings",
|
||||
"USAGE_EXPLORER": "SigNoz | Usage Explorer",
|
||||
"APPLICATION": "SigNoz | Home",
|
||||
"ALL_DASHBOARD": "SigNoz | All Dashboards",
|
||||
"DASHBOARD": "SigNoz | Dashboard",
|
||||
"DASHBOARD_WIDGET": "SigNoz | Dashboard Widget",
|
||||
"EDIT_ALERTS": "SigNoz | Edit Alerts",
|
||||
"LIST_ALL_ALERT": "SigNoz | All Alerts",
|
||||
"ALERTS_NEW": "SigNoz | New Alert",
|
||||
"ALL_CHANNELS": "SigNoz | All Channels",
|
||||
"CHANNELS_NEW": "SigNoz | New Channel",
|
||||
"CHANNELS_EDIT": "SigNoz | Edit Channel",
|
||||
"ALL_ERROR": "SigNoz | All Errors",
|
||||
"ERROR_DETAIL": "SigNoz | Error Detail",
|
||||
"VERSION": "SigNoz | Version",
|
||||
"MY_SETTINGS": "SigNoz | My Settings",
|
||||
"ORG_SETTINGS": "SigNoz | Organization Settings",
|
||||
"INGESTION_SETTINGS": "SigNoz | Ingestion Settings",
|
||||
"SOMETHING_WENT_WRONG": "SigNoz | Something Went Wrong",
|
||||
"UN_AUTHORIZED": "SigNoz | Unauthorized",
|
||||
"NOT_FOUND": "SigNoz | Page Not Found",
|
||||
"LOGS": "SigNoz | Logs",
|
||||
"LOGS_EXPLORER": "SigNoz | Logs Explorer",
|
||||
"LIVE_LOGS": "SigNoz | Live Logs",
|
||||
"HOME_PAGE": "Open source Observability Platform | SigNoz",
|
||||
"PASSWORD_RESET": "SigNoz | Password Reset",
|
||||
"LIST_LICENSES": "SigNoz | List of Licenses",
|
||||
"DEFAULT": "Open source Observability Platform | SigNoz"
|
||||
}
|
||||
|
||||
@@ -1,20 +1,108 @@
|
||||
import { ConfigProvider } from 'antd';
|
||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||
import NotFound from 'components/NotFound';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { FeatureKeys } from 'constants/features';
|
||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import ROUTES from 'constants/routes';
|
||||
import AppLayout from 'container/AppLayout';
|
||||
import { useThemeConfig } from 'hooks/useDarkMode';
|
||||
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
||||
import { NotificationProvider } from 'hooks/useNotifications';
|
||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||
import history from 'lib/history';
|
||||
import { DashboardProvider } from 'providers/Dashboard/Dashboard';
|
||||
import { QueryBuilderProvider } from 'providers/QueryBuilder';
|
||||
import { Suspense } from 'react';
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { Route, Router, Switch } from 'react-router-dom';
|
||||
import { Dispatch } from 'redux';
|
||||
import { AppState } from 'store/reducers';
|
||||
import AppActions from 'types/actions';
|
||||
import { UPDATE_FEATURE_FLAG_RESPONSE } from 'types/actions/app';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { trackPageView } from 'utils/segmentAnalytics';
|
||||
|
||||
import PrivateRoute from './Private';
|
||||
import routes from './routes';
|
||||
import defaultRoutes from './routes';
|
||||
|
||||
function App(): JSX.Element {
|
||||
const themeConfig = useThemeConfig();
|
||||
const [routes, setRoutes] = useState(defaultRoutes);
|
||||
const { isLoggedIn: isLoggedInState, user } = useSelector<
|
||||
AppState,
|
||||
AppReducer
|
||||
>((state) => state.app);
|
||||
|
||||
const dispatch = useDispatch<Dispatch<AppActions>>();
|
||||
|
||||
const { hostname, pathname } = window.location;
|
||||
|
||||
const featureResponse = useGetFeatureFlag((allFlags) => {
|
||||
const isOnboardingEnabled =
|
||||
allFlags.find((flag) => flag.name === FeatureKeys.ONBOARDING)?.active ||
|
||||
false;
|
||||
|
||||
const isChatSupportEnabled =
|
||||
allFlags.find((flag) => flag.name === FeatureKeys.CHAT_SUPPORT)?.active ||
|
||||
false;
|
||||
|
||||
dispatch({
|
||||
type: UPDATE_FEATURE_FLAG_RESPONSE,
|
||||
payload: {
|
||||
featureFlag: allFlags,
|
||||
refetch: featureResponse.refetch,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
!isOnboardingEnabled ||
|
||||
!(hostname && hostname.endsWith('signoz.cloud'))
|
||||
) {
|
||||
const newRoutes = routes.filter(
|
||||
(route) => route?.path !== ROUTES.GET_STARTED,
|
||||
);
|
||||
|
||||
setRoutes(newRoutes);
|
||||
}
|
||||
|
||||
if (isLoggedInState && isChatSupportEnabled) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.Intercom('boot', {
|
||||
app_id: process.env.INTERCOM_APP_ID,
|
||||
email: user?.email || '',
|
||||
name: user?.name || '',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
|
||||
if (
|
||||
isLoggedInState &&
|
||||
user &&
|
||||
user.userId &&
|
||||
user.email &&
|
||||
!isIdentifiedUser
|
||||
) {
|
||||
setLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER, 'true');
|
||||
|
||||
window.analytics.identify(user?.email, {
|
||||
email: user?.email,
|
||||
name: user?.name,
|
||||
});
|
||||
|
||||
window.clarity('identify', user.email, user.name);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isLoggedInState, user]);
|
||||
|
||||
useEffect(() => {
|
||||
trackPageView(pathname);
|
||||
}, [pathname]);
|
||||
|
||||
return (
|
||||
<ConfigProvider theme={themeConfig}>
|
||||
@@ -23,22 +111,24 @@ function App(): JSX.Element {
|
||||
<PrivateRoute>
|
||||
<ResourceProvider>
|
||||
<QueryBuilderProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
<DashboardProvider>
|
||||
<AppLayout>
|
||||
<Suspense fallback={<Spinner size="large" tip="Loading..." />}>
|
||||
<Switch>
|
||||
{routes.map(({ path, component, exact }) => (
|
||||
<Route
|
||||
key={`${path}`}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>
|
||||
))}
|
||||
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
<Route path="*" component={NotFound} />
|
||||
</Switch>
|
||||
</Suspense>
|
||||
</AppLayout>
|
||||
</DashboardProvider>
|
||||
</QueryBuilderProvider>
|
||||
</ResourceProvider>
|
||||
</PrivateRoute>
|
||||
|
||||
@@ -44,6 +44,10 @@ export const GettingStarted = Loadable(
|
||||
() => import(/* webpackChunkName: "GettingStarted" */ 'pages/GettingStarted'),
|
||||
);
|
||||
|
||||
export const Onboarding = Loadable(
|
||||
() => import(/* webpackChunkName: "Onboarding" */ 'pages/OnboardingPage'),
|
||||
);
|
||||
|
||||
export const DashboardPage = Loadable(
|
||||
() => import(/* webpackChunkName: "DashboardPage" */ 'pages/Dashboard'),
|
||||
);
|
||||
@@ -98,6 +102,10 @@ export const OrganizationSettings = Loadable(
|
||||
() => import(/* webpackChunkName: "All Settings" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const IngestionSettings = Loadable(
|
||||
() => import(/* webpackChunkName: "Ingestion Settings" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const MySettings = Loadable(
|
||||
() => import(/* webpackChunkName: "All MySettings" */ 'pages/MySettings'),
|
||||
);
|
||||
@@ -110,6 +118,10 @@ export const LogsExplorer = Loadable(
|
||||
() => import(/* webpackChunkName: "Logs Explorer" */ 'pages/LogsExplorer'),
|
||||
);
|
||||
|
||||
export const LiveLogs = Loadable(
|
||||
() => import(/* webpackChunkName: "Live Logs" */ 'pages/LiveLogs'),
|
||||
);
|
||||
|
||||
export const Login = Loadable(
|
||||
() => import(/* webpackChunkName: "Login" */ 'pages/Login'),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ROUTES from 'constants/routes';
|
||||
import DashboardWidget from 'pages/DashboardWidget';
|
||||
import { RouteProps } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@@ -8,18 +7,21 @@ import {
|
||||
CreateAlertChannelAlerts,
|
||||
CreateNewAlerts,
|
||||
DashboardPage,
|
||||
DashboardWidget,
|
||||
EditAlertChannelsAlerts,
|
||||
EditRulesPage,
|
||||
ErrorDetails,
|
||||
GettingStarted,
|
||||
IngestionSettings,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LiveLogs,
|
||||
Login,
|
||||
Logs,
|
||||
LogsExplorer,
|
||||
LogsIndexToFields,
|
||||
MySettings,
|
||||
NewDashboardPage,
|
||||
Onboarding,
|
||||
OrganizationSettings,
|
||||
PasswordReset,
|
||||
PipelinePage,
|
||||
@@ -45,6 +47,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: false,
|
||||
key: 'SIGN_UP',
|
||||
},
|
||||
{
|
||||
path: ROUTES.GET_STARTED,
|
||||
exact: true,
|
||||
component: Onboarding,
|
||||
isPrivate: true,
|
||||
key: 'GET_STARTED',
|
||||
},
|
||||
{
|
||||
component: LogsIndexToFields,
|
||||
path: ROUTES.LOGS_INDEX_FIELDS,
|
||||
@@ -94,13 +103,6 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'USAGE_EXPLORER',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INSTRUMENTATION,
|
||||
exact: true,
|
||||
component: GettingStarted,
|
||||
isPrivate: true,
|
||||
key: 'INSTRUMENTATION',
|
||||
},
|
||||
{
|
||||
path: ROUTES.ALL_DASHBOARD,
|
||||
exact: true,
|
||||
@@ -213,6 +215,13 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'ORG_SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INGESTION_SETTINGS,
|
||||
exact: true,
|
||||
component: IngestionSettings,
|
||||
isPrivate: true,
|
||||
key: 'INGESTION_SETTINGS',
|
||||
},
|
||||
{
|
||||
path: ROUTES.MY_SETTINGS,
|
||||
exact: true,
|
||||
@@ -234,6 +243,13 @@ const routes: AppRoutes[] = [
|
||||
key: 'LOGS_EXPLORER',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LIVE_LOGS,
|
||||
exact: true,
|
||||
component: LiveLogs,
|
||||
key: 'LIVE_LOGS',
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
path: ROUTES.LOGIN,
|
||||
exact: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { AxiosAlertManagerInstance } from 'api';
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import convertObjectIntoParams from 'lib/query/convertObjectIntoParams';
|
||||
@@ -11,15 +11,15 @@ const getTriggered = async (
|
||||
try {
|
||||
const queryParams = convertObjectIntoParams(props);
|
||||
|
||||
const response = await AxiosAlertManagerInstance.get(
|
||||
`/alerts?${queryParams}`,
|
||||
);
|
||||
const response = await axios.get(`/alerts?${queryParams}`);
|
||||
|
||||
const amData = JSON.parse(response.data.data);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data,
|
||||
payload: amData.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
|
||||
37
frontend/src/api/channels/createOpsgenie.ts
Normal file
37
frontend/src/api/channels/createOpsgenie.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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/createOpsgenie';
|
||||
|
||||
const create = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/channels', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default create;
|
||||
38
frontend/src/api/channels/editOpsgenie.ts
Normal file
38
frontend/src/api/channels/editOpsgenie.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
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/editOpsgenie';
|
||||
|
||||
const editOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.put(`/channels/${props.id}`, {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
send_resolved: true,
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default editOpsgenie;
|
||||
37
frontend/src/api/channels/testOpsgenie.ts
Normal file
37
frontend/src/api/channels/testOpsgenie.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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/createOpsgenie';
|
||||
|
||||
const testOpsgenie = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/testChannel', {
|
||||
name: props.name,
|
||||
opsgenie_configs: [
|
||||
{
|
||||
api_key: props.api_key,
|
||||
description: props.description,
|
||||
priority: props.priority,
|
||||
message: props.message,
|
||||
details: {
|
||||
...props.detailsArray,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default testOpsgenie;
|
||||
@@ -1,24 +1,9 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { Props } from 'types/api/dashboard/delete';
|
||||
import { PayloadProps, Props } from 'types/api/dashboard/delete';
|
||||
|
||||
const deleteDashboard = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<undefined> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.delete(`/dashboards/${props.uuid}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
const deleteDashboard = (props: Props): Promise<PayloadProps> =>
|
||||
axios
|
||||
.delete<PayloadProps>(`/dashboards/${props.uuid}`)
|
||||
.then((response) => response.data);
|
||||
|
||||
export default deleteDashboard;
|
||||
|
||||
@@ -1,24 +1,11 @@
|
||||
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/dashboard/get';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { Props } from 'types/api/dashboard/get';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const get = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.get(`/dashboards/${props.uuid}`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
const get = (props: Props): Promise<Dashboard> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard>>(`/dashboards/${props.uuid}`)
|
||||
.then((res) => res.data.data);
|
||||
|
||||
export default get;
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/dashboard/getAll';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
|
||||
const getAll = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get('/dashboards');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.message,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getAll;
|
||||
export const getAllDashboardList = (): Promise<Dashboard[]> =>
|
||||
axios
|
||||
.get<ApiResponse<Dashboard[]>>('/dashboards')
|
||||
.then((res) => res.data.data);
|
||||
|
||||
@@ -1,24 +1,10 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps } from 'types/api/features/getFeaturesFlags';
|
||||
import { ApiResponse } from 'types/api';
|
||||
import { FeatureFlagProps } from 'types/api/features/getFeaturesFlags';
|
||||
|
||||
const getFeaturesFlags = async (): Promise<
|
||||
SuccessResponse<PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/featureFlags`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
const getFeaturesFlags = (): Promise<FeatureFlagProps[]> =>
|
||||
axios
|
||||
.get<ApiResponse<FeatureFlagProps[]>>(`/featureFlags`)
|
||||
.then((response) => response.data.data);
|
||||
|
||||
export default getFeaturesFlags;
|
||||
|
||||
@@ -79,7 +79,6 @@ const interceptorRejected = async (
|
||||
|
||||
// when refresh token is expired
|
||||
if (response.status === 401 && response.config.url === '/login') {
|
||||
console.log('logging out ');
|
||||
Logout();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const AddToSelectedFields = async (
|
||||
const addToSelectedFields = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
@@ -16,8 +16,8 @@ const AddToSelectedFields = async (
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default AddToSelectedFields;
|
||||
export default addToSelectedFields;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import { PayloadProps, Props } from 'types/api/logs/addToSelectedFields';
|
||||
|
||||
const RemoveSelectedField = async (
|
||||
const removeSelectedField = async (
|
||||
props: Props,
|
||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
@@ -16,8 +16,8 @@ const RemoveSelectedField = async (
|
||||
payload: data.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
return Promise.reject(ErrorResponseHandler(error as AxiosError));
|
||||
}
|
||||
};
|
||||
|
||||
export default RemoveSelectedField;
|
||||
export default removeSelectedField;
|
||||
|
||||
@@ -4,11 +4,11 @@ import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import {
|
||||
MetricRangePayloadV3,
|
||||
MetricsRangeProps,
|
||||
QueryRangePayload,
|
||||
} from 'types/api/metrics/getQueryRange';
|
||||
|
||||
export const getMetricsQueryRange = async (
|
||||
props: MetricsRangeProps,
|
||||
props: QueryRangePayload,
|
||||
): Promise<SuccessResponse<MetricRangePayloadV3> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post('/query_range', props);
|
||||
|
||||
21
frontend/src/api/pipeline/preview.ts
Normal file
21
frontend/src/api/pipeline/preview.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import axios from 'api';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { PipelineData } from 'types/api/pipeline/def';
|
||||
|
||||
export interface PipelineSimulationRequest {
|
||||
logs: ILog[];
|
||||
pipelines: PipelineData[];
|
||||
}
|
||||
|
||||
export interface PipelineSimulationResponse {
|
||||
logs: ILog[];
|
||||
}
|
||||
|
||||
const simulatePipelineProcessing = async (
|
||||
requestBody: PipelineSimulationRequest,
|
||||
): Promise<PipelineSimulationResponse> =>
|
||||
axios
|
||||
.post('/logs/pipelines/preview', requestBody)
|
||||
.then((res) => res.data.data);
|
||||
|
||||
export default simulatePipelineProcessing;
|
||||
5
frontend/src/api/saveView/deleteView.ts
Normal file
5
frontend/src/api/saveView/deleteView.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import axios from 'api';
|
||||
import { DeleteViewPayloadProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const deleteView = (uuid: string): Promise<DeleteViewPayloadProps> =>
|
||||
axios.delete(`explorer/views/${uuid}`);
|
||||
9
frontend/src/api/saveView/getAllViews.ts
Normal file
9
frontend/src/api/saveView/getAllViews.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { AllViewsProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const getAllViews = (
|
||||
sourcepage: DataSource,
|
||||
): Promise<AxiosResponse<AllViewsProps>> =>
|
||||
axios.get(`explorer/views?sourcePage=${sourcepage}`);
|
||||
16
frontend/src/api/saveView/saveView.ts
Normal file
16
frontend/src/api/saveView/saveView.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import axios from 'api';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { SaveViewPayloadProps, SaveViewProps } from 'types/api/saveViews/types';
|
||||
|
||||
export const saveView = ({
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
viewName,
|
||||
extraData,
|
||||
}: SaveViewProps): Promise<AxiosResponse<SaveViewPayloadProps>> =>
|
||||
axios.post('explorer/views', {
|
||||
name: viewName,
|
||||
sourcePage,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
});
|
||||
19
frontend/src/api/saveView/updateView.ts
Normal file
19
frontend/src/api/saveView/updateView.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
UpdateViewPayloadProps,
|
||||
UpdateViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
|
||||
export const updateView = ({
|
||||
compositeQuery,
|
||||
viewName,
|
||||
extraData,
|
||||
sourcePage,
|
||||
viewKey,
|
||||
}: UpdateViewProps): Promise<UpdateViewPayloadProps> =>
|
||||
axios.put(`explorer/views/${viewKey}`, {
|
||||
name: viewName,
|
||||
compositeQuery,
|
||||
extraData,
|
||||
sourcePage,
|
||||
});
|
||||
24
frontend/src/api/settings/getIngestionData.ts
Normal file
24
frontend/src/api/settings/getIngestionData.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 { IngestionResponseProps } from 'types/api/settings/ingestion';
|
||||
|
||||
const getIngestionData = async (): Promise<
|
||||
SuccessResponse<IngestionResponseProps> | ErrorResponse
|
||||
> => {
|
||||
try {
|
||||
const response = await axios.get(`/settings/ingestion_key`);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: 'Success',
|
||||
payload: response.data,
|
||||
};
|
||||
} catch (error) {
|
||||
return ErrorResponseHandler(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
export default getIngestionData;
|
||||
@@ -2,14 +2,12 @@ import axios from 'api';
|
||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
||||
import { AxiosError } from 'axios';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
import * as loginPrecheck from 'types/api/user/loginPrecheck';
|
||||
import { PayloadProps } from 'types/api/user/loginPrecheck';
|
||||
import { Props } from 'types/api/user/signup';
|
||||
|
||||
const signup = async (
|
||||
props: Props,
|
||||
): Promise<
|
||||
SuccessResponse<null | loginPrecheck.PayloadProps> | ErrorResponse
|
||||
> => {
|
||||
): Promise<SuccessResponse<null | PayloadProps> | ErrorResponse> => {
|
||||
try {
|
||||
const response = await axios.post(`/register`, {
|
||||
...props,
|
||||
|
||||
@@ -14,7 +14,11 @@ import {
|
||||
export const Logout = (): void => {
|
||||
deleteLocalStorageKey(LOCALSTORAGE.AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.IS_LOGGED_IN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.REFRESH_AUTH_TOKEN);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_EMAIL);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.LOGGED_IN_USER_NAME);
|
||||
deleteLocalStorageKey(LOCALSTORAGE.CHAT_SUPPORT);
|
||||
|
||||
store.dispatch({
|
||||
type: LOGGED_IN,
|
||||
@@ -60,5 +64,9 @@ export const Logout = (): void => {
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
window.Intercom('shutdown');
|
||||
|
||||
history.push(ROUTES.LOGIN);
|
||||
};
|
||||
|
||||
244
frontend/src/components/ExplorerCard/ExplorerCard.tsx
Normal file
244
frontend/src/components/ExplorerCard/ExplorerCard.tsx
Normal file
@@ -0,0 +1,244 @@
|
||||
import {
|
||||
DeleteOutlined,
|
||||
MoreOutlined,
|
||||
SaveOutlined,
|
||||
ShareAltOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Dropdown,
|
||||
MenuProps,
|
||||
Popover,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import axios from 'axios';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { useGetSearchQueryParam } from 'hooks/queryBuilder/useGetSearchQueryParam';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useGetAllViews } from 'hooks/saveViews/useGetAllViews';
|
||||
import { useUpdateView } from 'hooks/saveViews/useUpdateView';
|
||||
import useErrorNotification from 'hooks/useErrorNotification';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { ExploreHeaderToolTip, SaveButtonText } from './constants';
|
||||
import MenuItemGenerator from './MenuItemGenerator';
|
||||
import SaveViewWithName from './SaveViewWithName';
|
||||
import {
|
||||
DropDownOverlay,
|
||||
ExplorerCardHeadContainer,
|
||||
OffSetCol,
|
||||
} from './styles';
|
||||
import { ExplorerCardProps } from './types';
|
||||
import { deleteViewHandler } from './utils';
|
||||
|
||||
function ExplorerCard({
|
||||
sourcepage,
|
||||
children,
|
||||
}: ExplorerCardProps): JSX.Element {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [, setCopyUrl] = useCopyToClipboard();
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const onCopyUrlHandler = (): void => {
|
||||
setCopyUrl(window.location.href);
|
||||
notifications.success({
|
||||
message: 'Copied to clipboard',
|
||||
});
|
||||
};
|
||||
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
isStagedQueryUpdated,
|
||||
} = useQueryBuilder();
|
||||
|
||||
const {
|
||||
data: viewsData,
|
||||
isLoading,
|
||||
error,
|
||||
isRefetching,
|
||||
refetch: refetchAllView,
|
||||
} = useGetAllViews(sourcepage);
|
||||
|
||||
useErrorNotification(error);
|
||||
|
||||
const handleOpenChange = (newOpen = false): void => {
|
||||
setIsOpen(newOpen);
|
||||
};
|
||||
|
||||
const viewName = useGetSearchQueryParam(QueryParams.viewName) || '';
|
||||
|
||||
const viewKey = useGetSearchQueryParam(QueryParams.viewKey) || '';
|
||||
|
||||
const isQueryUpdated = isStagedQueryUpdated(viewsData?.data?.data, viewKey);
|
||||
|
||||
const { mutateAsync: updateViewAsync } = useUpdateView({
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
});
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(viewKey);
|
||||
|
||||
const showErrorNotification = (err: Error): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
const onDeleteHandler = (): void =>
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: viewKey,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage: sourcepage,
|
||||
});
|
||||
|
||||
const onUpdateQueryHandler = (): void => {
|
||||
updateViewAsync(
|
||||
{
|
||||
compositeQuery: mapCompositeQueryFromQuery(currentQuery, panelType),
|
||||
viewKey,
|
||||
extraData: '',
|
||||
sourcePage: sourcepage,
|
||||
viewName,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
notifications.success({
|
||||
message: 'View Updated Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(err);
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const moreOptionMenu: MenuProps = {
|
||||
items: [
|
||||
{
|
||||
key: 'delete',
|
||||
label: <Typography.Text strong>Delete</Typography.Text>,
|
||||
onClick: onDeleteHandler,
|
||||
icon: <DeleteOutlined />,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const saveButtonType = isQueryUpdated ? 'default' : 'primary';
|
||||
const saveButtonIcon = isQueryUpdated ? null : <SaveOutlined />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ExplorerCardHeadContainer size="small">
|
||||
<Row align="middle">
|
||||
<Col span={6}>
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url={ExploreHeaderToolTip.url}
|
||||
text={ExploreHeaderToolTip.text}
|
||||
useFilledIcon={false}
|
||||
/>
|
||||
</Space>
|
||||
</Col>
|
||||
<OffSetCol span={18}>
|
||||
<Space size="large">
|
||||
{viewsData?.data.data && viewsData?.data.data.length && (
|
||||
<Space>
|
||||
<Select
|
||||
getPopupContainer={popupContainer}
|
||||
loading={isLoading || isRefetching}
|
||||
showSearch
|
||||
placeholder="Select a view"
|
||||
dropdownStyle={DropDownOverlay}
|
||||
dropdownMatchSelectWidth={false}
|
||||
optionLabelProp="value"
|
||||
value={viewName || undefined}
|
||||
>
|
||||
{viewsData?.data.data.map((view) => (
|
||||
<Select.Option key={view.uuid} value={view.name}>
|
||||
<MenuItemGenerator
|
||||
viewName={view.name}
|
||||
viewKey={viewKey}
|
||||
createdBy={view.createdBy}
|
||||
uuid={view.uuid}
|
||||
refetchAllView={refetchAllView}
|
||||
viewData={viewsData.data.data}
|
||||
sourcePage={sourcepage}
|
||||
/>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Space>
|
||||
)}
|
||||
{isQueryUpdated && (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SaveOutlined />}
|
||||
onClick={onUpdateQueryHandler}
|
||||
>
|
||||
Save changes
|
||||
</Button>
|
||||
)}
|
||||
<Popover
|
||||
getPopupContainer={popupContainer}
|
||||
placement="bottomLeft"
|
||||
trigger="click"
|
||||
content={
|
||||
<SaveViewWithName
|
||||
sourcePage={sourcepage}
|
||||
handlePopOverClose={handleOpenChange}
|
||||
refetchAllView={refetchAllView}
|
||||
/>
|
||||
}
|
||||
showArrow={false}
|
||||
open={isOpen}
|
||||
onOpenChange={handleOpenChange}
|
||||
>
|
||||
<Button type={saveButtonType} icon={saveButtonIcon}>
|
||||
{isQueryUpdated
|
||||
? SaveButtonText.SAVE_AS_NEW_VIEW
|
||||
: SaveButtonText.SAVE_VIEW}
|
||||
</Button>
|
||||
</Popover>
|
||||
<ShareAltOutlined onClick={onCopyUrlHandler} />
|
||||
{viewKey && (
|
||||
<Dropdown trigger={['click']} menu={moreOptionMenu}>
|
||||
<MoreOutlined />
|
||||
</Dropdown>
|
||||
)}
|
||||
</Space>
|
||||
</OffSetCol>
|
||||
</Row>
|
||||
</ExplorerCardHeadContainer>
|
||||
<Card>{children}</Card>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ExplorerCard;
|
||||
103
frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
Normal file
103
frontend/src/components/ExplorerCard/MenuItemGenerator.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
import { DeleteOutlined } from '@ant-design/icons';
|
||||
import { Col, Row, Tooltip, Typography } from 'antd';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useDeleteView } from 'hooks/saveViews/useDeleteView';
|
||||
import { useHandleExplorerTabChange } from 'hooks/useHandleExplorerTabChange';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { MouseEvent, useCallback } from 'react';
|
||||
|
||||
import { MenuItemContainer } from './styles';
|
||||
import { MenuItemLabelGeneratorProps } from './types';
|
||||
import {
|
||||
deleteViewHandler,
|
||||
getViewDetailsUsingViewKey,
|
||||
trimViewName,
|
||||
} from './utils';
|
||||
|
||||
function MenuItemGenerator({
|
||||
viewName,
|
||||
viewKey,
|
||||
createdBy,
|
||||
uuid,
|
||||
viewData,
|
||||
refetchAllView,
|
||||
sourcePage,
|
||||
}: MenuItemLabelGeneratorProps): JSX.Element {
|
||||
const {
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
updateAllQueriesOperators,
|
||||
} = useQueryBuilder();
|
||||
const { handleExplorerTabChange } = useHandleExplorerTabChange();
|
||||
|
||||
const { notifications } = useNotifications();
|
||||
|
||||
const { mutateAsync: deleteViewAsync } = useDeleteView(uuid);
|
||||
|
||||
const onDeleteHandler = (event: MouseEvent<HTMLElement>): void => {
|
||||
event.stopPropagation();
|
||||
deleteViewHandler({
|
||||
deleteViewAsync,
|
||||
notifications,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
viewId: uuid,
|
||||
viewKey,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
});
|
||||
};
|
||||
|
||||
const onMenuItemSelectHandler = useCallback(
|
||||
({ key }: { key: string }): void => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(key, viewData);
|
||||
if (!currentViewDetails) return;
|
||||
const {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
panelType: currentPanelType,
|
||||
} = currentViewDetails;
|
||||
|
||||
handleExplorerTabChange(currentPanelType, {
|
||||
query,
|
||||
name,
|
||||
uuid,
|
||||
});
|
||||
},
|
||||
[viewData, handleExplorerTabChange],
|
||||
);
|
||||
|
||||
const onLabelClickHandler = (): void => {
|
||||
onMenuItemSelectHandler({
|
||||
key: uuid,
|
||||
});
|
||||
};
|
||||
|
||||
const newViewName = trimViewName(viewName);
|
||||
|
||||
return (
|
||||
<MenuItemContainer onClick={onLabelClickHandler}>
|
||||
<Row justify="space-between">
|
||||
<Col span={22}>
|
||||
<Row>
|
||||
<Tooltip title={viewName}>
|
||||
<Typography.Text strong>{newViewName}</Typography.Text>
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row>
|
||||
<Typography.Text type="secondary">Created by {createdBy}</Typography.Text>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={2}>
|
||||
<Typography.Link>
|
||||
<DeleteOutlined onClick={onDeleteHandler} />
|
||||
</Typography.Link>
|
||||
</Col>
|
||||
</Row>
|
||||
</MenuItemContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default MenuItemGenerator;
|
||||
76
frontend/src/components/ExplorerCard/SaveViewWithName.tsx
Normal file
76
frontend/src/components/ExplorerCard/SaveViewWithName.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
import { Card, Form, Input, Typography } from 'antd';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
|
||||
import { useSaveView } from 'hooks/saveViews/useSaveView';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { mapCompositeQueryFromQuery } from 'lib/newQueryBuilder/queryBuilderMappers/mapCompositeQueryFromQuery';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SaveButton } from './styles';
|
||||
import { SaveViewFormProps, SaveViewWithNameProps } from './types';
|
||||
import { saveViewHandler } from './utils';
|
||||
|
||||
function SaveViewWithName({
|
||||
sourcePage,
|
||||
handlePopOverClose,
|
||||
refetchAllView,
|
||||
}: SaveViewWithNameProps): JSX.Element {
|
||||
const [form] = Form.useForm<SaveViewFormProps>();
|
||||
const { t } = useTranslation(['explorer']);
|
||||
const {
|
||||
currentQuery,
|
||||
panelType,
|
||||
redirectWithQueryBuilderData,
|
||||
} = useQueryBuilder();
|
||||
const { notifications } = useNotifications();
|
||||
const compositeQuery = mapCompositeQueryFromQuery(currentQuery, panelType);
|
||||
|
||||
const { isLoading, mutateAsync: saveViewAsync } = useSaveView({
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData: '',
|
||||
});
|
||||
|
||||
const onSaveHandler = (): void => {
|
||||
saveViewHandler({
|
||||
compositeQuery,
|
||||
handlePopOverClose,
|
||||
extraData: '',
|
||||
notifications,
|
||||
panelType: panelType || PANEL_TYPES.LIST,
|
||||
redirectWithQueryBuilderData,
|
||||
refetchAllView,
|
||||
saveViewAsync,
|
||||
sourcePage,
|
||||
viewName: form.getFieldValue('viewName'),
|
||||
form,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Typography>{t('name_of_the_view')}</Typography>
|
||||
<Form form={form} onFinish={onSaveHandler}>
|
||||
<Form.Item
|
||||
name={['viewName']}
|
||||
required
|
||||
requiredMark
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Please enter view name',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="Enter Name" />
|
||||
</Form.Item>
|
||||
<SaveButton htmlType="submit" type="primary" loading={isLoading}>
|
||||
Save
|
||||
</SaveButton>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
export default SaveViewWithName;
|
||||
32
frontend/src/components/ExplorerCard/__mock__/viewData.ts
Normal file
32
frontend/src/components/ExplorerCard/__mock__/viewData.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { ViewProps } from 'types/api/saveViews/types';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
export const viewMockData: ViewProps[] = [
|
||||
{
|
||||
uuid: 'view1',
|
||||
name: 'View 1',
|
||||
createdBy: 'User 1',
|
||||
category: 'category 1',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:31:00.000Z',
|
||||
updatedAt: '2021-07-07T06:33:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 1',
|
||||
},
|
||||
{
|
||||
uuid: 'view2',
|
||||
name: 'View 2',
|
||||
createdBy: 'User 2',
|
||||
category: 'category 2',
|
||||
compositeQuery: {} as ICompositeMetricQuery,
|
||||
createdAt: '2021-07-07T06:30:00.000Z',
|
||||
updatedAt: '2021-07-07T06:30:00.000Z',
|
||||
extraData: '',
|
||||
sourcePage: DataSource.TRACES,
|
||||
tags: [],
|
||||
updatedBy: 'User 2',
|
||||
},
|
||||
];
|
||||
14
frontend/src/components/ExplorerCard/constants.ts
Normal file
14
frontend/src/components/ExplorerCard/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
|
||||
export const ExploreHeaderToolTip = {
|
||||
url:
|
||||
'https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder',
|
||||
text: 'More details on how to use query builder',
|
||||
};
|
||||
|
||||
export const SaveButtonText = {
|
||||
SAVE_AS_NEW_VIEW: 'Save as new view',
|
||||
SAVE_VIEW: 'Save view',
|
||||
};
|
||||
|
||||
export type QuerySearchParamNames = QueryParams.viewName | QueryParams.viewKey;
|
||||
@@ -1,27 +0,0 @@
|
||||
import { Card, Space, Typography } from 'antd';
|
||||
import TextToolTip from 'components/TextToolTip';
|
||||
|
||||
function ExplorerCard({ children }: Props): JSX.Element {
|
||||
return (
|
||||
<Card
|
||||
size="small"
|
||||
title={
|
||||
<Space>
|
||||
<Typography>Query Builder</Typography>
|
||||
<TextToolTip
|
||||
url="https://signoz.io/docs/userguide/query-builder/?utm_source=product&utm_medium=new-query-builder"
|
||||
text="More details on how to use query builder"
|
||||
/>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default ExplorerCard;
|
||||
28
frontend/src/components/ExplorerCard/styles.ts
Normal file
28
frontend/src/components/ExplorerCard/styles.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Button, Card, Col } from 'antd';
|
||||
import styled, { CSSProperties } from 'styled-components';
|
||||
|
||||
export const ExplorerCardHeadContainer = styled(Card)`
|
||||
margin: 1rem 0;
|
||||
`;
|
||||
|
||||
export const OffSetCol = styled(Col)`
|
||||
text-align: right;
|
||||
`;
|
||||
|
||||
export const SaveButton = styled(Button)`
|
||||
&&& {
|
||||
margin: 1rem 0;
|
||||
width: 5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export const DropDownOverlay: CSSProperties = {
|
||||
maxHeight: '20rem',
|
||||
overflowY: 'auto',
|
||||
width: '20rem',
|
||||
padding: 0,
|
||||
};
|
||||
|
||||
export const MenuItemContainer = styled(Card)`
|
||||
padding: 0;
|
||||
`;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import ExplorerCard from '../ExplorerCard';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}/${ROUTES.TRACES_EXPLORER}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useGetAllViews', () => ({
|
||||
useGetAllViews: jest.fn(() => ({
|
||||
data: { data: { data: viewMockData } },
|
||||
isLoading: false,
|
||||
error: null,
|
||||
isRefetching: false,
|
||||
refetch: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useUpdateView', () => ({
|
||||
useUpdateView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useDeleteView', () => ({
|
||||
useDeleteView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('ExplorerCard', () => {
|
||||
it('renders a card with a title and a description', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.getByText('Query Builder')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders a save view button', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>child</ExplorerCard>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
expect(screen.getByText('Save view')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should see all the view listed in dropdown', async () => {
|
||||
const screen = render(
|
||||
<ExplorerCard sourcepage={DataSource.TRACES}>Mock Children</ExplorerCard>,
|
||||
);
|
||||
const selectPlaceholder = screen.getByText('Select a view');
|
||||
|
||||
fireEvent.mouseDown(selectPlaceholder);
|
||||
const viewNameText = await screen.getAllByText('View 1');
|
||||
viewNameText.forEach((element) => {
|
||||
expect(element).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import { viewMockData } from '../__mock__/viewData';
|
||||
import MenuItemGenerator from '../MenuItemGenerator';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('antd/es/form/Form', () => ({
|
||||
useForm: jest.fn().mockReturnValue({
|
||||
onFinish: jest.fn(),
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('MenuItemGenerator', () => {
|
||||
it('should render MenuItemGenerator component', () => {
|
||||
const screen = render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].uuid}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].uuid}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(viewMockData[0].name)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onMenuItemSelectHandler on click of MenuItemGenerator', () => {
|
||||
render(
|
||||
<MockQueryClientProvider>
|
||||
<MenuItemGenerator
|
||||
viewName={viewMockData[0].name}
|
||||
viewKey={viewMockData[0].uuid}
|
||||
createdBy={viewMockData[0].createdBy}
|
||||
uuid={viewMockData[0].uuid}
|
||||
refetchAllView={jest.fn()}
|
||||
viewData={viewMockData}
|
||||
sourcePage={DataSource.TRACES}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
|
||||
const spanElement = screen.getByRole('img', {
|
||||
name: 'delete',
|
||||
});
|
||||
|
||||
expect(spanElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { fireEvent, render } from '@testing-library/react';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
|
||||
import SaveViewWithName from '../SaveViewWithName';
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useLocation: (): { pathname: string } => ({
|
||||
pathname: `${process.env.FRONTEND_API_ENDPOINT}${ROUTES.APPLICATION}/`,
|
||||
}),
|
||||
}));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.mock('hooks/queryBuilder/useGetPanelTypesQueryParam', () => ({
|
||||
useGetPanelTypesQueryParam: jest.fn(() => 'mockedPanelType'),
|
||||
}));
|
||||
|
||||
jest.mock('hooks/saveViews/useSaveView', () => ({
|
||||
useSaveView: jest.fn(() => ({
|
||||
mutateAsync: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('SaveViewWithName', () => {
|
||||
it('should render SaveViewWithName component', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call saveViewAsync on click of Save button', () => {
|
||||
const screen = render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SaveViewWithName
|
||||
sourcePage={DataSource.TRACES}
|
||||
handlePopOverClose={jest.fn()}
|
||||
refetchAllView={jest.fn()}
|
||||
/>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByText('Save'));
|
||||
|
||||
expect(screen.getByText('Save')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
84
frontend/src/components/ExplorerCard/types.ts
Normal file
84
frontend/src/components/ExplorerCard/types.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { FormInstance } from 'antd';
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { UseMutateAsyncFunction } from 'react-query';
|
||||
import { ICompositeMetricQuery } from 'types/api/alerts/compositeQuery';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import {
|
||||
DeleteViewPayloadProps,
|
||||
SaveViewPayloadProps,
|
||||
SaveViewProps,
|
||||
ViewProps,
|
||||
} from 'types/api/saveViews/types';
|
||||
import { DataSource, QueryBuilderContextType } from 'types/common/queryBuilder';
|
||||
|
||||
export interface ExplorerCardProps {
|
||||
sourcepage: DataSource;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export type GetViewDetailsUsingViewKey = (
|
||||
viewKey: string,
|
||||
data: ViewProps[] | undefined,
|
||||
) =>
|
||||
| { query: Query; name: string; uuid: string; panelType: PANEL_TYPES }
|
||||
| undefined;
|
||||
|
||||
export interface IsQueryUpdatedInViewProps {
|
||||
viewKey: string;
|
||||
data: ViewProps[] | undefined;
|
||||
stagedQuery: Query | null;
|
||||
currentPanelType: PANEL_TYPES | null;
|
||||
}
|
||||
|
||||
export interface SaveViewWithNameProps {
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
handlePopOverClose: VoidFunction;
|
||||
refetchAllView: VoidFunction;
|
||||
}
|
||||
|
||||
export interface SaveViewFormProps {
|
||||
viewName: string;
|
||||
}
|
||||
|
||||
export interface MenuItemLabelGeneratorProps {
|
||||
viewName: string;
|
||||
viewKey: string;
|
||||
createdBy: string;
|
||||
uuid: string;
|
||||
viewData: ViewProps[];
|
||||
refetchAllView: VoidFunction;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
|
||||
export interface SaveViewHandlerProps {
|
||||
viewName: string;
|
||||
compositeQuery: ICompositeMetricQuery;
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
extraData: string;
|
||||
panelType: PANEL_TYPES | null;
|
||||
notifications: NotificationInstance;
|
||||
refetchAllView: SaveViewWithNameProps['refetchAllView'];
|
||||
saveViewAsync: UseMutateAsyncFunction<
|
||||
AxiosResponse<SaveViewPayloadProps>,
|
||||
Error,
|
||||
SaveViewProps,
|
||||
SaveViewPayloadProps
|
||||
>;
|
||||
handlePopOverClose: SaveViewWithNameProps['handlePopOverClose'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
form: FormInstance<SaveViewFormProps>;
|
||||
}
|
||||
|
||||
export interface DeleteViewHandlerProps {
|
||||
deleteViewAsync: UseMutateAsyncFunction<DeleteViewPayloadProps, Error, string>;
|
||||
refetchAllView: MenuItemLabelGeneratorProps['refetchAllView'];
|
||||
redirectWithQueryBuilderData: QueryBuilderContextType['redirectWithQueryBuilderData'];
|
||||
notifications: NotificationInstance;
|
||||
panelType: PANEL_TYPES | null;
|
||||
viewKey: string;
|
||||
viewId: string;
|
||||
updateAllQueriesOperators: QueryBuilderContextType['updateAllQueriesOperators'];
|
||||
sourcePage: ExplorerCardProps['sourcepage'];
|
||||
}
|
||||
183
frontend/src/components/ExplorerCard/utils.ts
Normal file
183
frontend/src/components/ExplorerCard/utils.ts
Normal file
@@ -0,0 +1,183 @@
|
||||
import { NotificationInstance } from 'antd/es/notification/interface';
|
||||
import axios from 'axios';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { QueryParams } from 'constants/query';
|
||||
import { initialQueriesMap, PANEL_TYPES } from 'constants/queryBuilder';
|
||||
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
|
||||
import isEqual from 'lodash-es/isEqual';
|
||||
|
||||
import {
|
||||
DeleteViewHandlerProps,
|
||||
GetViewDetailsUsingViewKey,
|
||||
IsQueryUpdatedInViewProps,
|
||||
SaveViewHandlerProps,
|
||||
} from './types';
|
||||
|
||||
const showErrorNotification = (
|
||||
notifications: NotificationInstance,
|
||||
err: Error,
|
||||
): void => {
|
||||
notifications.error({
|
||||
message: axios.isAxiosError(err) ? err.message : SOMETHING_WENT_WRONG,
|
||||
});
|
||||
};
|
||||
|
||||
export const getViewDetailsUsingViewKey: GetViewDetailsUsingViewKey = (
|
||||
viewKey,
|
||||
data,
|
||||
) => {
|
||||
const selectedView = data?.find((view) => view.uuid === viewKey);
|
||||
if (selectedView) {
|
||||
const { compositeQuery, name, uuid } = selectedView;
|
||||
const query = mapQueryDataFromApi(compositeQuery);
|
||||
return { query, name, uuid, panelType: compositeQuery.panelType };
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export const isQueryUpdatedInView = ({
|
||||
viewKey,
|
||||
data,
|
||||
stagedQuery,
|
||||
currentPanelType,
|
||||
}: IsQueryUpdatedInViewProps): boolean => {
|
||||
const currentViewDetails = getViewDetailsUsingViewKey(viewKey, data);
|
||||
if (!currentViewDetails) {
|
||||
return false;
|
||||
}
|
||||
const { query, panelType } = currentViewDetails;
|
||||
|
||||
// Omitting id from aggregateAttribute and groupBy
|
||||
const updatedCurrentQuery = {
|
||||
...stagedQuery,
|
||||
builder: {
|
||||
...stagedQuery?.builder,
|
||||
queryData: stagedQuery?.builder.queryData.map((queryData) => {
|
||||
const { id, ...rest } = queryData.aggregateAttribute;
|
||||
const newAggregateAttribute = rest;
|
||||
const newGroupByAttributes = queryData.groupBy.map((groupByAttribute) => {
|
||||
const { id, ...rest } = groupByAttribute;
|
||||
return rest;
|
||||
});
|
||||
const newItems = queryData.filters.items.map((item) => {
|
||||
const { id, ...newItem } = item;
|
||||
if (item.key) {
|
||||
const { id, ...rest } = item.key;
|
||||
return {
|
||||
...newItem,
|
||||
key: rest,
|
||||
};
|
||||
}
|
||||
return newItem;
|
||||
});
|
||||
return {
|
||||
...queryData,
|
||||
aggregateAttribute: newAggregateAttribute,
|
||||
groupBy: newGroupByAttributes,
|
||||
filters: {
|
||||
...queryData.filters,
|
||||
items: newItems,
|
||||
},
|
||||
limit: queryData.limit ? queryData.limit : 0,
|
||||
offset: queryData.offset ? queryData.offset : 0,
|
||||
pageSize: queryData.pageSize ? queryData.pageSize : 0,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
panelType !== currentPanelType ||
|
||||
!isEqual(query.builder, updatedCurrentQuery?.builder) ||
|
||||
!isEqual(query.clickhouse_sql, updatedCurrentQuery?.clickhouse_sql) ||
|
||||
!isEqual(query.promql, updatedCurrentQuery?.promql)
|
||||
);
|
||||
};
|
||||
|
||||
export const saveViewHandler = ({
|
||||
saveViewAsync,
|
||||
refetchAllView,
|
||||
notifications,
|
||||
handlePopOverClose,
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
redirectWithQueryBuilderData,
|
||||
panelType,
|
||||
form,
|
||||
}: SaveViewHandlerProps): void => {
|
||||
saveViewAsync(
|
||||
{
|
||||
viewName,
|
||||
compositeQuery,
|
||||
sourcePage,
|
||||
extraData,
|
||||
},
|
||||
{
|
||||
onSuccess: (data) => {
|
||||
refetchAllView();
|
||||
redirectWithQueryBuilderData(mapQueryDataFromApi(compositeQuery), {
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewName]: viewName,
|
||||
[QueryParams.viewKey]: data.data.data,
|
||||
});
|
||||
notifications.success({
|
||||
message: 'View Saved Successfully',
|
||||
});
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
onSettled: () => {
|
||||
handlePopOverClose();
|
||||
form.resetFields();
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
export const deleteViewHandler = ({
|
||||
deleteViewAsync,
|
||||
refetchAllView,
|
||||
redirectWithQueryBuilderData,
|
||||
notifications,
|
||||
panelType,
|
||||
viewKey,
|
||||
viewId,
|
||||
updateAllQueriesOperators,
|
||||
sourcePage,
|
||||
}: DeleteViewHandlerProps): void => {
|
||||
deleteViewAsync(viewKey, {
|
||||
onSuccess: () => {
|
||||
if (viewId === viewKey) {
|
||||
redirectWithQueryBuilderData(
|
||||
updateAllQueriesOperators(
|
||||
initialQueriesMap.traces,
|
||||
panelType || PANEL_TYPES.LIST,
|
||||
sourcePage,
|
||||
),
|
||||
{
|
||||
[QueryParams.viewName]: '',
|
||||
[QueryParams.panelTypes]: panelType,
|
||||
[QueryParams.viewKey]: '',
|
||||
},
|
||||
);
|
||||
}
|
||||
notifications.success({
|
||||
message: 'View Deleted Successfully',
|
||||
});
|
||||
refetchAllView();
|
||||
},
|
||||
onError: (err) => {
|
||||
showErrorNotification(notifications, err);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const trimViewName = (viewName: string): string => {
|
||||
if (viewName.length > 20) {
|
||||
return `${viewName.substring(0, 20)}...`;
|
||||
}
|
||||
return viewName;
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Chart, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||
import * as ChartHelpers from 'chart.js/helpers';
|
||||
import { getRelativePosition } from 'chart.js/helpers';
|
||||
|
||||
// utils
|
||||
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||
@@ -45,7 +45,7 @@ function createMousedownHandler(
|
||||
return (ev): void => {
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: startDragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: startDragPositionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > startDragPositionX) {
|
||||
startDragPositionX = left;
|
||||
@@ -74,7 +74,7 @@ function createMousemoveHandler(
|
||||
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: dragPositionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: dragPositionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > dragPositionX) {
|
||||
dragPositionX = left;
|
||||
@@ -99,7 +99,7 @@ function createMouseupHandler(
|
||||
return (ev): void => {
|
||||
const { left, right } = chart.chartArea;
|
||||
|
||||
let { x: endRelativePostionX } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x: endRelativePostionX } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > endRelativePostionX) {
|
||||
endRelativePostionX = left;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Chart, ChartEvent, ChartTypeRegistry, Plugin } from 'chart.js';
|
||||
import * as ChartHelpers from 'chart.js/helpers';
|
||||
import { getRelativePosition } from 'chart.js/helpers';
|
||||
|
||||
// utils
|
||||
import { ChartEventHandler, mergeDefaultOptions } from './utils';
|
||||
@@ -42,7 +42,7 @@ function createMousemoveHandler(
|
||||
return (ev: ChartEvent | MouseEvent): void => {
|
||||
const { left, right, top, bottom } = chart.chartArea;
|
||||
|
||||
let { x, y } = ChartHelpers.getRelativePosition(ev, chart);
|
||||
let { x, y } = getRelativePosition(ev, chart);
|
||||
|
||||
if (left > x) {
|
||||
x = left;
|
||||
|
||||
@@ -21,8 +21,6 @@ import {
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
// interfaces
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
|
||||
// styles
|
||||
import {
|
||||
@@ -31,19 +29,17 @@ import {
|
||||
RawLogContent,
|
||||
RawLogViewContainer,
|
||||
} from './styles';
|
||||
import { RawLogViewProps } from './types';
|
||||
|
||||
const convert = new Convert();
|
||||
|
||||
interface RawLogViewProps {
|
||||
isActiveLog?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
data: ILog;
|
||||
linesPerRow: number;
|
||||
}
|
||||
|
||||
function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
const { isActiveLog = false, isReadOnly = false, data, linesPerRow } = props;
|
||||
|
||||
function RawLogView({
|
||||
isActiveLog,
|
||||
isReadOnly,
|
||||
data,
|
||||
linesPerRow,
|
||||
isTextOverflowEllipsisDisabled,
|
||||
}: RawLogViewProps): JSX.Element {
|
||||
const { isHighlighted, isLogsExplorerPage, onLogCopy } = useCopyLogLink(
|
||||
data.id,
|
||||
);
|
||||
@@ -64,12 +60,14 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
const isReadOnlyLog = !isLogsExplorerPage || isReadOnly;
|
||||
|
||||
const severityText = data.severity_text ? `${data.severity_text} |` : '';
|
||||
|
||||
const text = useMemo(
|
||||
() =>
|
||||
typeof data.timestamp === 'string'
|
||||
? `${dayjs(data.timestamp).format()} | ${data.body}`
|
||||
: `${dayjs(data.timestamp / 1e6).format()} | ${data.body}`,
|
||||
[data.timestamp, data.body],
|
||||
? `${dayjs(data.timestamp).format()} | ${severityText} ${data.body}`
|
||||
: `${dayjs(data.timestamp / 1e6).format()} | ${severityText} ${data.body}`,
|
||||
[data.timestamp, data.body, severityText],
|
||||
);
|
||||
|
||||
const handleClickExpand = useCallback(() => {
|
||||
@@ -118,11 +116,6 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
[text],
|
||||
);
|
||||
|
||||
const mouseActions = useMemo(
|
||||
() => ({ onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }),
|
||||
[handleMouseEnter, handleMouseLeave],
|
||||
);
|
||||
|
||||
return (
|
||||
<RawLogViewContainer
|
||||
onClick={handleClickExpand}
|
||||
@@ -131,8 +124,8 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
$isDarkMode={isDarkMode}
|
||||
$isReadOnly={isReadOnly}
|
||||
$isActiveLog={isHighlighted}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...mouseActions}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
{!isReadOnly && (
|
||||
<ExpandIconWrapper flex="30px">
|
||||
@@ -143,6 +136,7 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
<RawLogContent
|
||||
$isReadOnly={isReadOnly}
|
||||
$isActiveLog={isActiveLog}
|
||||
$isTextOverflowEllipsisDisabled={isTextOverflowEllipsisDisabled}
|
||||
linesPerRow={linesPerRow}
|
||||
dangerouslySetInnerHTML={html}
|
||||
/>
|
||||
@@ -181,6 +175,7 @@ function RawLogView(props: RawLogViewProps): JSX.Element {
|
||||
RawLogView.defaultProps = {
|
||||
isActiveLog: false,
|
||||
isReadOnly: false,
|
||||
isTextOverflowEllipsisDisabled: false,
|
||||
};
|
||||
|
||||
export default RawLogView;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user