Compare commits

..

41 Commits

Author SHA1 Message Date
Naman Verma
7011d60999 Merge branch 'main' into nv/4102 2026-03-03 22:38:40 +05:30
Naman Verma
aa124a3a56 fix: parallel calls for each query in readmultiple method 2026-03-03 22:37:59 +05:30
Vinicius Lourenço
ccb88ab03f feat(vite): migrate from webpack (#10392)
Some checks are pending
build-staging / prepare (push) Waiting to run
build-staging / js-build (push) Blocked by required conditions
build-staging / go-build (push) Blocked by required conditions
build-staging / staging (push) Blocked by required conditions
Release Drafter / update_release_draft (push) Waiting to run
2026-03-03 19:50:13 +05:30
Ashwin Bhatkal
4f0e245e3d chore(frontend): dynamic vars load with empty textbox variables (#10480)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* fix(frontend): dynamic vars load with empty textbox variables
2026-03-03 16:11:57 +05:30
Nikhil Mantri
a5549f6d5b chore: add status info icon and include additional metrics for status (#10231) 2026-03-03 10:13:52 +00:00
Srikanth Chekuri
1d967fadac chore(metrics-explorer): handle errors properly (#10474) 2026-03-03 09:27:09 +00:00
Naman Verma
bdef3660a5 Merge branch 'main' into nv/4102 2026-03-03 14:48:00 +05:30
Abhi kumar
5af6ed6148 chore: updated config builder types (#10477)
* chore: made baseconfigbuilder generic to be used across different charts

* chore: updated baseconfigbuilder test

* chore: updated timezone types

* chore: fixed tsc + test

* chore: fixed tsc + test

* chore: fixed tsc + test

* chore: updated config builder types

* chore: updated comment
2026-03-03 14:17:57 +05:30
Abhi kumar
d094e9cb45 chore: made baseconfigbuilder generic to be used across different charts (#10451)
* chore: made baseconfigbuilder generic to be used across different charts

* chore: updated baseconfigbuilder test

* chore: updated timezone types

* chore: fixed tsc + test

* chore: fixed tsc + test

* chore: fixed tsc + test
2026-03-03 14:05:54 +05:30
Naman Verma
c22a81ba3b chore: 1.25 bullseye is still an rc so shifting to bookworm 2026-03-03 11:18:59 +05:30
Naman Verma
5b40ae3477 chore: no need for our own alert store callback 2026-03-03 11:12:25 +05:30
Naman Verma
196036988e chore: upgrade go version in dockerfile to 1.25 2026-03-03 10:49:53 +05:30
Naman Verma
0cbcc63cbe Merge branch 'main' into nv/4102 2026-03-03 09:31:50 +05:30
Srikanth Chekuri
dbcd1a598e chore: add basic integration tests for meter (#10463)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-03-02 23:47:54 +05:30
Srikanth Chekuri
00ce5a91ea fix(metrics-explorer): address several bugs in explorer tab (#10458) 2026-03-02 22:11:20 +05:30
Naman Verma
f3b2cb245f Merge branch 'main' into nv/4102 2026-03-02 19:04:52 +05:30
Naman Verma
3be3727dc2 test: ut fix, pass no nil prometheus registry 2026-03-02 19:04:37 +05:30
SagarRajput-7
4b84b715b4 feat: removed the sso auth announcement banner (#10471)
* feat: removed the sso auth announcement banner

* feat: updated icon to use signozhq icons
2026-03-02 19:00:03 +05:30
Naman Verma
438bc0a014 test: ut fix, just matched expected and actual nothing else 2026-03-02 18:44:59 +05:30
Naman Verma
7c809d7d54 test: ut fix, just matched expected and actual nothing else 2026-03-02 18:43:52 +05:30
Naman Verma
cd9fd910bf test: ut fix, just matched expected and actual nothing else 2026-03-02 18:22:13 +05:30
Naman Verma
ec4d55e796 test: ut fix, just matched expected and actual nothing else 2026-03-02 18:18:54 +05:30
Naman Verma
59a43cb1ef fix: correct imported version of semconv in sdk.go 2026-03-02 17:59:44 +05:30
Naman Verma
50ed7e99cc Merge branch 'main' into nv/4102 2026-03-02 17:44:23 +05:30
Srikanth Chekuri
b3c08ec417 chore: address gaps in summary tab (#10462)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-03-02 11:52:50 +00:00
Vikrant Gupta
37cd1ab84b feat(service-account): introduce service account (#10436)
* feat(service-account): initial domain changes for service account

* feat(service-account): add module logic and complete handler

* feat(service-account): finish module and add store barebones

* feat(service-account): add http handlers, openapi spec and store implementation

* feat(service-account): add status update and emailing base setup

* feat(service-account): update emailing templates

* feat(service-account): update openapi spec

* feat(service-account): testing changes

* feat(service-account): update openapi spec

* feat(service-account): remove templating for email subject

* feat(service-account): remove sqlmigrations

* feat(service-account): add key to the create response
2026-03-02 15:51:59 +05:30
Naman Verma
8184e60c03 fix: add validity check for spatial aggregation (#10429) 2026-03-02 09:13:16 +00:00
Vikrant Gupta
11c793deb0 fix(authz): remove usage for guarded routes (#10468) 2026-03-02 08:56:05 +00:00
Naman Verma
87455bd014 chore: slightly unsure changes 2026-03-02 14:04:48 +05:30
Naman Verma
f5b9c55408 Merge branch 'main' into nv/4102 2026-03-02 13:35:08 +05:30
Naman Verma
cb1ab0024f chore: easy changes first 2026-03-02 13:31:33 +05:30
Vinicius Lourenço
9557d20a48 chore: authz helpers (#10367)
Co-authored-by: Vikrant Gupta <vikrant@signoz.io>
2026-03-02 07:48:00 +00:00
Abhi kumar
ad22137aa3 fix: fixed memory leak because of canvas persistance over mount (#10455)
* fix: fixed memory leak because of canvas persistance over mount

* chore: removed weakref
2026-03-02 12:14:07 +05:30
Naman Verma
0bbf1a3bee chore: upgrade prometheus/prometheus to latest available version 2026-03-02 11:25:59 +05:30
Naman Verma
2595ee0b53 chore: upgrade prometheus/common to latest available version 2026-03-02 11:04:25 +05:30
Srikanth Chekuri
95fc905448 chore: move savedview and integration types into their own types package (#10454)
Some checks failed
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / staging (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
2026-02-28 12:55:26 +00:00
Vinicius Lourenço
9f5afaf36f perf(bundle-size): be explicit when including new languages for react-syntax-highlighter (#10228)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-02-28 14:48:50 +05:30
Srikanth Chekuri
3f69d5cdc2 chore: add docs for service (#10450) 2026-02-28 13:59:42 +05:30
Srikanth Chekuri
98617b5120 chore: add doc for adding new abstractions to codebase (#10444)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* chore: add doc for adding new abstractions to codebase

* chore: reorder

---------

Co-authored-by: Pandey <vibhupandey28@gmail.com>
2026-02-27 20:55:15 +00:00
Vinicius Lourenço
df30852296 perf(bundle-size): move antd-table-saveas-excel out of main bundle (#10229)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
2026-02-27 17:27:30 +00:00
Srikanth Chekuri
b9beabb425 chore: add guide for packages (#10443) 2026-02-27 13:50:44 +00:00
418 changed files with 19197 additions and 15570 deletions

View File

@@ -58,19 +58,19 @@ jobs:
run: |
mkdir -p frontend
echo 'CI=1' > frontend/.env
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
echo 'PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> frontend/.env
echo 'APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> frontend/.env
echo 'PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> frontend/.env
echo 'DOCS_BASE_URL="https://signoz.io"' >> frontend/.env
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' >> frontend/.env
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> frontend/.env
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> frontend/.env
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> frontend/.env
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> frontend/.env
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> frontend/.env
echo 'VITE_TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> frontend/.env
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'VITE_POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> frontend/.env
echo 'VITE_PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> frontend/.env
echo 'VITE_APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> frontend/.env
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> frontend/.env
echo 'VITE_DOCS_BASE_URL="https://signoz.io"' >> frontend/.env
- name: cache-dotenv
uses: actions/cache@v4
with:

View File

@@ -64,12 +64,12 @@ jobs:
run: |
mkdir -p frontend
echo 'CI=1' > frontend/.env
echo 'TUNNEL_URL="${{ secrets.NP_TUNNEL_URL }}"' >> frontend/.env
echo 'TUNNEL_DOMAIN="${{ secrets.NP_TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'PYLON_APP_ID="${{ secrets.NP_PYLON_APP_ID }}"' >> frontend/.env
echo 'APPCUES_APP_ID="${{ secrets.NP_APPCUES_APP_ID }}"' >> frontend/.env
echo 'PYLON_IDENTITY_SECRET="${{ secrets.NP_PYLON_IDENTITY_SECRET }}"' >> frontend/.env
echo 'DOCS_BASE_URL="https://staging.signoz.io"' >> frontend/.env
echo 'VITE_TUNNEL_URL="${{ secrets.NP_TUNNEL_URL }}"' >> frontend/.env
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.NP_TUNNEL_DOMAIN }}"' >> frontend/.env
echo 'VITE_PYLON_APP_ID="${{ secrets.NP_PYLON_APP_ID }}"' >> frontend/.env
echo 'VITE_APPCUES_APP_ID="${{ secrets.NP_APPCUES_APP_ID }}"' >> frontend/.env
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.NP_PYLON_IDENTITY_SECRET }}"' >> frontend/.env
echo 'VITE_DOCS_BASE_URL="https://staging.signoz.io"' >> frontend/.env
- name: cache-dotenv
uses: actions/cache@v4
with:

View File

@@ -24,19 +24,19 @@ jobs:
- name: dotenv-frontend
working-directory: frontend
run: |
echo 'INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > .env
echo 'SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> .env
echo 'SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> .env
echo 'SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> .env
echo 'SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> .env
echo 'SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> .env
echo 'TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> .env
echo 'TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> .env
echo 'POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> .env
echo 'PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> .env
echo 'APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> .env
echo 'PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> .env
echo 'DOCS_BASE_URL="https://signoz.io"' >> .env
echo 'VITE_INTERCOM_APP_ID="${{ secrets.INTERCOM_APP_ID }}"' > .env
echo 'VITE_SEGMENT_ID="${{ secrets.SEGMENT_ID }}"' >> .env
echo 'VITE_SENTRY_AUTH_TOKEN="${{ secrets.SENTRY_AUTH_TOKEN }}"' >> .env
echo 'VITE_SENTRY_ORG="${{ secrets.SENTRY_ORG }}"' >> .env
echo 'VITE_SENTRY_PROJECT_ID="${{ secrets.SENTRY_PROJECT_ID }}"' >> .env
echo 'VITE_SENTRY_DSN="${{ secrets.SENTRY_DSN }}"' >> .env
echo 'VITE_TUNNEL_URL="${{ secrets.TUNNEL_URL }}"' >> .env
echo 'VITE_TUNNEL_DOMAIN="${{ secrets.TUNNEL_DOMAIN }}"' >> .env
echo 'VITE_POSTHOG_KEY="${{ secrets.POSTHOG_KEY }}"' >> .env
echo 'VITE_PYLON_APP_ID="${{ secrets.PYLON_APP_ID }}"' >> .env
echo 'VITE_APPCUES_APP_ID="${{ secrets.APPCUES_APP_ID }}"' >> .env
echo 'VITE_PYLON_IDENTITY_SECRET="${{ secrets.PYLON_IDENTITY_SECRET }}"' >> .env
echo 'VITE_DOCS_BASE_URL="https://signoz.io"' >> .env
- name: node-setup
uses: actions/setup-node@v5
with:

View File

@@ -61,3 +61,60 @@ jobs:
with:
PRIMUS_REF: main
JS_SRC: frontend
md-languages:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: validate md languages
run: bash frontend/scripts/validate-md-languages.sh
authz:
if: |
(github.event_name == 'pull_request' && ! github.event.pull_request.head.repo.fork && github.event.pull_request.user.login != 'dependabot[bot]' && ! contains(github.event.pull_request.labels.*.name, 'safe-to-test')) ||
(github.event_name == 'pull_request_target' && contains(github.event.pull_request.labels.*.name, 'safe-to-test'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "22"
- name: Install frontend dependencies
working-directory: ./frontend
run: |
yarn install
- name: Install uv
uses: astral-sh/setup-uv@v5
- name: Install Python dependencies
working-directory: ./tests/integration
run: |
uv sync
- name: Start test environment
run: |
make py-test-setup
- name: Generate permissions.type.ts
working-directory: ./frontend
run: |
yarn generate:permissions-type
- name: Teardown test environment
if: always()
run: |
make py-test-teardown
- name: Check for changes
run: |
if ! git diff --exit-code frontend/src/hooks/useAuthZ/permissions.type.ts; then
echo "::error::frontend/src/hooks/useAuthZ/permissions.type.ts is out of date. Please run the generator locally and commit the changes: npm run generate:permissions-type (from the frontend directory)"
exit 1
fi

View File

@@ -238,4 +238,4 @@ py-clean: ## Clear all pycache and pytest cache from tests directory recursively
.PHONY: gen-mocks
gen-mocks:
@echo ">> Generating mocks"
@mockery --config .mockery.yml
@mockery --config .mockery.yml

View File

@@ -1,4 +1,4 @@
FROM golang:1.24-bullseye
FROM golang:1.25-bookworm
ARG OS="linux"
ARG TARGETARCH

View File

@@ -6,7 +6,7 @@ ENV NODE_OPTIONS=--max-old-space-size=8192
RUN CI=1 yarn install
RUN CI=1 yarn build
FROM golang:1.24-bullseye
FROM golang:1.25-bookworm
ARG OS="linux"
ARG TARGETARCH

View File

@@ -1763,6 +1763,134 @@ components:
- type
- orgId
type: object
ServiceaccounttypesFactorAPIKey:
properties:
createdAt:
format: date-time
type: string
expires_at:
minimum: 0
type: integer
id:
type: string
key:
type: string
last_used:
format: date-time
type: string
name:
type: string
service_account_id:
type: string
updatedAt:
format: date-time
type: string
required:
- id
- key
- expires_at
- last_used
- service_account_id
type: object
ServiceaccounttypesGettableFactorAPIKeyWithKey:
properties:
id:
type: string
key:
type: string
required:
- id
- key
type: object
ServiceaccounttypesPostableFactorAPIKey:
properties:
expires_at:
minimum: 0
type: integer
name:
type: string
required:
- name
- expires_at
type: object
ServiceaccounttypesPostableServiceAccount:
properties:
email:
type: string
name:
type: string
roles:
items:
type: string
type: array
required:
- name
- email
- roles
type: object
ServiceaccounttypesServiceAccount:
properties:
createdAt:
format: date-time
type: string
email:
type: string
id:
type: string
name:
type: string
orgID:
type: string
roles:
items:
type: string
type: array
status:
type: string
updatedAt:
format: date-time
type: string
required:
- id
- name
- email
- roles
- status
- orgID
type: object
ServiceaccounttypesUpdatableFactorAPIKey:
properties:
expires_at:
minimum: 0
type: integer
name:
type: string
required:
- name
- expires_at
type: object
ServiceaccounttypesUpdatableServiceAccount:
properties:
email:
type: string
name:
type: string
roles:
items:
type: string
type: array
required:
- name
- email
- roles
type: object
ServiceaccounttypesUpdatableServiceAccountStatus:
properties:
status:
type: string
required:
- status
type: object
TelemetrytypesFieldContext:
enum:
- metric
@@ -4537,6 +4665,586 @@ paths:
summary: Patch objects for a role by relation
tags:
- role
/api/v1/service_accounts:
get:
deprecated: false
description: This endpoint lists the service accounts for an organisation
operationId: ListServiceAccounts
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/ServiceaccounttypesServiceAccount'
type: array
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List service accounts
tags:
- serviceaccount
post:
deprecated: false
description: This endpoint creates a service account
operationId: CreateServiceAccount
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesPostableServiceAccount'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/TypesIdentifiable'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create service account
tags:
- serviceaccount
/api/v1/service_accounts/{id}:
delete:
deprecated: false
description: This endpoint deletes an existing service account
operationId: DeleteServiceAccount
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Deletes a service account
tags:
- serviceaccount
get:
deprecated: false
description: This endpoint gets an existing service account
operationId: GetServiceAccount
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/ServiceaccounttypesServiceAccount'
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Gets a service account
tags:
- serviceaccount
put:
deprecated: false
description: This endpoint updates an existing service account
operationId: UpdateServiceAccount
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesUpdatableServiceAccount'
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Updates a service account
tags:
- serviceaccount
/api/v1/service_accounts/{id}/keys:
get:
deprecated: false
description: This endpoint lists the service account keys
operationId: ListServiceAccountKeys
parameters:
- in: path
name: id
required: true
schema:
type: string
responses:
"200":
content:
application/json:
schema:
properties:
data:
items:
$ref: '#/components/schemas/ServiceaccounttypesFactorAPIKey'
type: array
status:
type: string
required:
- status
- data
type: object
description: OK
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: List service account keys
tags:
- serviceaccount
post:
deprecated: false
description: This endpoint creates a service account key
operationId: CreateServiceAccountKey
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesPostableFactorAPIKey'
responses:
"201":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/ServiceaccounttypesGettableFactorAPIKeyWithKey'
status:
type: string
required:
- status
- data
type: object
description: Created
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"409":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Conflict
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Create a service account key
tags:
- serviceaccount
/api/v1/service_accounts/{id}/keys/{fid}:
delete:
deprecated: false
description: This endpoint revokes an existing service account key
operationId: RevokeServiceAccountKey
parameters:
- in: path
name: id
required: true
schema:
type: string
- in: path
name: fid
required: true
schema:
type: string
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Revoke a service account key
tags:
- serviceaccount
put:
deprecated: false
description: This endpoint updates an existing service account key
operationId: UpdateServiceAccountKey
parameters:
- in: path
name: id
required: true
schema:
type: string
- in: path
name: fid
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesUpdatableFactorAPIKey'
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Updates a service account key
tags:
- serviceaccount
/api/v1/service_accounts/{id}/status:
put:
deprecated: false
description: This endpoint updates an existing service account status
operationId: UpdateServiceAccountStatus
parameters:
- in: path
name: id
required: true
schema:
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/ServiceaccounttypesUpdatableServiceAccountStatus'
responses:
"204":
content:
application/json:
schema:
type: string
description: No Content
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- ADMIN
- tokenizer:
- ADMIN
summary: Updates a service account status
tags:
- serviceaccount
/api/v1/user:
get:
deprecated: false
@@ -5459,6 +6167,10 @@ paths:
name: searchText
schema:
type: string
- in: query
name: source
schema:
type: string
responses:
"200":
content:

View File

@@ -0,0 +1,127 @@
# Abstractions
This document provides rules for deciding when a new type, interface, or intermediate representation is warranted in Go code. The goal is to keep the codebase navigable by ensuring every abstraction earns its place.
## The cost of a new abstraction
Every exported type, interface, or wrapper is a permanent commitment. It must be named, documented, tested, and understood by every future contributor. It creates a new concept in the codebase vocabulary. Before introducing one, verify that the cost is justified by a concrete benefit that cannot be achieved with existing mechanisms.
## Before you introduce anything new
Answer these four questions. If writing a PR, include the answers in the description.
1. **What already exists?** Name the specific type, function, interface, or library that covers this ground today.
2. **What does the new abstraction add?** Name the concrete operation, guarantee, or capability. "Cleaner" or "more reusable" are not sufficient; name what the caller can do that it could not do before.
3. **What does the new abstraction drop?** If it wraps or mirrors an existing structure, list what it cannot represent. Every gap must be either justified or handled with an explicit error.
4. **Who consumes it?** List the call sites. If there is only one producer and one consumer in the same call chain, you likely need a function, not a type.
## Rules
### 1. Prefer functions over types
If a piece of logic has one input and one output, write a function. Do not create a struct to hold intermediate state that is built in one place and read in one place. A function is easier to test, easier to inline, and does not expand the vocabulary of the codebase.
```go
// Prefer this:
func ConvertConfig(src ExternalConfig) (InternalConfig, error)
// Over this:
type ConfigAdapter struct { ... }
func NewConfigAdapter(src ExternalConfig) *ConfigAdapter
func (a *ConfigAdapter) ToInternal() (InternalConfig, error)
```
The two-step version is only justified when `ConfigAdapter` has multiple distinct consumers that use it in different ways.
### 2. Do not duplicate structures you do not own
When a library or external package produces a structured output, operate on that output directly. Do not create a parallel type that mirrors a subset of its fields.
A partial copy will:
- **Silently lose data** when the source has fields or variants the copy does not account for.
- **Drift** when the source evolves and the copy is not updated in lockstep.
- **Add a conversion step** that doubles the code surface and the opportunity for bugs.
If you need to shield consumers from a dependency, define a narrow interface over the dependency's type rather than copying its shape into a new struct.
### 3. Never silently discard input
If your code receives structured input and cannot handle part of it, return an error. Do not silently return nil, skip the element, or produce a partial result. Silent data loss is the hardest class of bug to detect because the code appears to work, it just produces wrong results.
```go
// Wrong: silently ignores the unrecognized case.
default:
return nil
// Right: makes the gap visible.
default:
return nil, fmt.Errorf("unsupported %T value: %v", v, v)
```
This applies broadly: type switches, format conversions, data migrations, enum mappings, configuration parsing. Anywhere a `default` or `else` branch can swallow input, it should surface an error instead.
### 4. Do not expose methods that lose information
A method on a structured type should not strip meaning from the structure it belongs to. If a caller needs to iterate over elements for a specific purpose (validation, aggregation, logging), write that logic as a standalone function that operates on the structure with full context, rather than adding a method that returns a reduced view.
```go
// Problematic: callers cannot distinguish how items were related.
func (o *Order) AllLineItems() []LineItem { ... }
// Better: the validation logic operates on the full structure.
func ValidateOrder(o *Order) error { ... }
```
Public methods shape how a type is used. Once a lossy accessor exists, callers will depend on it, and the lost information becomes unrecoverable at those call sites.
### 5. Interfaces should be discovered, not predicted
Do not define an interface before you have at least two concrete implementations that need it. An interface with one implementation is not abstraction; it is indirection that makes it harder to navigate from call site to implementation.
The exception is interfaces required for testing (e.g., for mocking an external dependency). In that case, define the interface in the **consuming** package, not the providing package, following the Go convention of [accepting interfaces and returning structs](https://go.dev/wiki/CodeReviewComments#interfaces).
### 6. Wrappers must add semantics, not just rename
A wrapper type is justified when it adds meaning, validation, or invariants that the underlying type does not carry. It is not justified when it merely renames fields or reorganizes the same data into a different shape.
```go
// Justified: adds validation that the underlying string does not carry.
type OrgID struct{ value string }
func NewOrgID(s string) (OrgID, error) { /* validates format */ }
// Not justified: renames fields with no new invariant or behavior.
type UserInfo struct {
Name string // same as source.Name
Email string // same as source.Email
}
```
Ask: what does the wrapper guarantee that the underlying type does not? If the answer is nothing, use the underlying type directly.
## When a new type IS warranted
A new type earns its place when it meets **at least one** of these criteria:
- **Serialization boundary**: It must be persisted, sent over the wire, or written to config. The source type is unsuitable (unexported fields, function pointers, cycles).
- **Invariant enforcement**: The constructor or methods enforce constraints that raw data does not carry (e.g., non-empty, validated format, bounded range).
- **Multiple distinct consumers**: Three or more call sites use the type in meaningfully different ways. The type is the shared vocabulary between them.
- **Dependency firewall**: The type lives in a lightweight package so that consumers avoid importing a heavy dependency.
## What should I remember?
- A function is almost always simpler than a type. Start with a function; promote to a type only when you have evidence of need.
- Never silently drop data. If you cannot handle it, error.
- If your new type mirrors an existing one, you need a strong reason beyond "nicer to work with".
- If your type has one producer and one consumer, it is indirection, not abstraction.
- Interfaces come from need (multiple implementations), not from prediction.
- When in doubt, do not add it. It is easier to add an abstraction later when the need is clear than to remove one after it has spread through the codebase.
## Further reading
These works and our own lessions shaped the above guidelines
- [The Wrong Abstraction](https://sandimetz.com/blog/2016/1/20/the-wrong-abstraction) - Sandi Metz. The wrong abstraction is worse than duplication. If you find yourself passing parameters and adding conditional paths through shared code, inline it back into every caller and let the duplication show you what the right abstraction is.
- [Write code that is easy to delete, not easy to extend](https://programmingisterrible.com/post/139222674273/write-code-that-is-easy-to-delete-not-easy-to) - tef. Every abstraction is a bet on the future. Optimize for how cheaply you can remove code when the bet is wrong, not for how easily you can extend it when the bet is right.
- [Goodbye, Clean Code](https://overreacted.io/goodbye-clean-code/) - Dan Abramov. A refactoring that removes duplication can look cleaner while making the code harder to change. Clean-looking and easy-to-change are not the same thing.
- [A Philosophy of Software Design](https://www.amazon.com/Philosophy-Software-Design-John-Ousterhout/dp/1732102201) - John Ousterhout. Good abstractions are deep: simple interface, complex implementation. A "false abstraction" omits important details while appearing simple, and is worse than no abstraction at all. ([Summary by Pragmatic Engineer](https://blog.pragmaticengineer.com/a-philosophy-of-software-design-review/))
- [Simplicity is Complicated](https://go.dev/talks/2015/simplicity-is-complicated.slide) - Rob Pike. Go-specific. Fewer orthogonal concepts that compose predictably beat many overlapping ones. Features were left out of Go deliberately; the same discipline applies to your own code.

View File

@@ -0,0 +1,126 @@
# Packages
All shared Go code in SigNoz lives under `pkg/`. Each package represents a distinct domain concept and exposes a clear public interface. This guide covers the conventions for creating, naming, and organising packages so the codebase stays consistent as it grows.
## How should I name a package?
Use short, lowercase, single-word names. No underscores or camelCase (`querier`, `cache`, `authz`, not `query_builder` or `dataStore`).
Names must be **domain-specific**. A package name should tell you what problem domain it deals with, not what data structure it wraps. Prefer `alertmanager` over `manager`, `licensing` over `checker`.
Avoid generic names like `util`, `helpers`, `common`, `misc`, or `base`. If you can't name it, the code probably belongs in an existing package.
## When should I create a new package?
Create a new package when:
- The functionality represents a **distinct domain concept** (e.g., `authz`, `licensing`, `cache`).
- Two or more other packages would import it; it serves as shared infrastructure.
- The code has a clear public interface that can stand on its own.
Do **not** create a new package when:
- There is already a package that covers the same domain. Extend the existing package instead.
- The code is only used in one place. Keep it local to the caller.
- You are splitting purely for file size. Use multiple files within the same package instead.
## How should I lay out a package?
A typical package looks like:
```
pkg/cache/
├── cache.go # Public interface + exported types
├── config.go # Configuration types if needed
├── memorycache/ # Implementation sub-package
├── rediscache/ # Another implementation
└── cachetest/ # Test helpers for consumers
```
Follow these rules:
1. **Interface-first file**: The file matching the package name (e.g., `cache.go` in `pkg/cache/`) should define the public interface and core exported types. Keep implementation details out of this file.
2. **One responsibility per file**: Name files after what they contain (`config.go`, `handler.go`, `service.go`), not after the package name. If a package merges two concerns, prefix files to group them (e.g., `memory_store.go`, `redis_store.go` in a storage package).
3. **Sub-packages for implementations**: When a package defines an interface with multiple implementations, put each implementation in its own sub-package (`memorycache/`, `rediscache/`). This keeps the parent package import-free of implementation dependencies.
4. **Test helpers in `{pkg}test/`**: If consumers need test mocks or builders, put them in a `{pkg}test/` sub-package (e.g., `cachetest/`, `sqlstoretest/`). This avoids polluting the main package with test-only code.
5. **Test files stay alongside source**: Unit tests go in `_test.go` files next to the code they test, in the same package.
## How should I name symbols?
### Exported symbols
- **Interfaces**: For single-method interfaces, follow the standard `-er` suffix convention (`Reader`, `Writer`, `Closer`). For multi-method interfaces, use clear nouns (`Cache`, `Store`, `Provider`).
- **Constructors**: `New<Type>(...)` (e.g., `NewMemoryCache()`).
- **Avoid stutter**: Since callers qualify with the package name, don't repeat it. Write `cache.Cache`, not `cache.CacheInterface`. Write `authz.FromRole`, not `authz.AuthzFromRole`.
### Unexported symbols
- Struct receivers: one or two characters (`c`, `f`, `br`).
- Helper functions: descriptive lowercase names (`parseToken`, `buildQuery`).
### Constants
- Use `PascalCase` for exported constants.
- When merging files from different origins into one package, watch out for **name collisions** across files. Prefix to disambiguate when two types share a natural name.
## How should I organise imports?
Group imports in three blocks separated by blank lines:
```go
import (
// 1. Standard library
"fmt"
"net/http"
// 2. External dependencies
"github.com/gorilla/mux"
// 3. Internal
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/types"
)
```
Never introduce circular imports. If package A needs package B and B needs A, extract the shared types into a third package (often under `pkg/types/`).
## Where do shared types go?
Most types belong in `pkg/types/` under a domain-specific sub-package (e.g., `pkg/types/ruletypes`, `pkg/types/authtypes`).
Do not put domain logic in `pkg/types/`. Only data structures, constants, and simple methods.
## How do I merge or move packages?
When two packages are tightly coupled (one imports the other's constants, they cover the same domain), merge them:
1. Pick a domain-specific name for the combined package.
2. Prefix files to preserve origin (e.g., `memory_store.go`, `redis_store.go`).
3. Resolve symbol conflicts explicitly; rename with a prefix rather than silently shadowing.
4. Update all consumers in a single change.
5. Delete the old packages. Do not leave behind re-export shims.
6. Verify with `go build ./...`, `go test ./<new-pkg>/...`, and `go vet ./...`.
## When should I add documentation?
Add a `doc.go` with a package-level comment for any package that is non-trivial or has multiple consumers. Keep it to 13 sentences:
```go
// Package cache provides a caching interface with pluggable backends
// for in-memory and Redis-based storage.
package cache
```
## What should I remember?
- Package names are domain-specific and lowercase. Never generic names like `util` or `common`.
- The file matching the package name (e.g., `cache.go`) defines the public interface. Implementation details go elsewhere.
- Never introduce circular imports. Extract shared types into `pkg/types/` when needed.
- Watch for symbol name collisions when merging packages, prefix to disambiguate.
- Put test helpers in a `{pkg}test/` sub-package, not in the main package.
- Before submitting, verify with `go build ./...`, `go test ./<your-pkg>/...`, and `go vet ./...`.
- Update all consumers when you rename or move symbols.

View File

@@ -8,4 +8,15 @@ We adhere to three primary style guides as our foundation:
- [Code Review Comments](https://go.dev/wiki/CodeReviewComments) - For understanding common comments in code reviews
- [Google Style Guide](https://google.github.io/styleguide/go/) - Additional practices from Google
We **recommend** (almost enforce) reviewing these guides before contributing to the codebase. They provide valuable insights into writing idiomatic Go code and will help you understand our approach to backend development. In addition, we have a few additional rules that make certain areas stricter than the above which can be found in area-specific files in this package.
We **recommend** (almost enforce) reviewing these guides before contributing to the codebase. They provide valuable insights into writing idiomatic Go code and will help you understand our approach to backend development. In addition, we have a few additional rules that make certain areas stricter than the above which can be found in area-specific files in this package:
- [Abstractions](abstractions.md) - When to introduce new types and intermediate representations
- [Errors](errors.md) - Structured error handling
- [Endpoint](endpoint.md) - HTTP endpoint patterns
- [Flagger](flagger.md) - Feature flag patterns
- [Handler](handler.md) - HTTP handler patterns
- [Integration](integration.md) - Integration testing
- [Provider](provider.md) - Dependency injection and provider patterns
- [Packages](packages.md) - Naming, layout, and conventions for `pkg/` packages
- [Service](service.md) - Managed service lifecycle with `factory.Service`
- [SQL](sql.md) - Database and SQL patterns

View File

@@ -0,0 +1,269 @@
# Service
A service is a component with a managed lifecycle: it starts, runs for the lifetime of the application, and stops gracefully.
Services are distinct from [providers](provider.md). A provider adapts an external dependency behind an interface. A service has a managed lifecycle that is tied to the lifetime of the application.
## When do you need a service?
You need a service when your component needs to do work that outlives a single method call:
- **Periodic work**: polling an external system, garbage-collecting expired data, syncing state on an interval.
- **Graceful shutdown**: holding resources (connections, caches, buffers) that must be flushed or closed before the process exits.
- **Blocking on readiness**: waiting for an external dependency to become available before the application can proceed.
If your component only responds to calls and holds no state that requires cleanup, it is a provider, not a service. If it does both (responds to calls *and* needs a lifecycle), embed `factory.Service` in the provider interface; see [How to create a service](#how-to-create-a-service).
## The interface
The `factory.Service` interface in `pkg/factory/service.go` defines two methods:
```go
type Service interface {
// Starts a service. It should block and should not return until the service is stopped or it fails.
Start(context.Context) error
// Stops a service.
Stop(context.Context) error
}
```
`Start` **must block**. It should not return until the service is stopped (returning `nil`) or something goes wrong (returning an error). If `Start` returns an error, the entire application shuts down.
`Stop` should cause `Start` to unblock and return. It must be safe to call from a different goroutine than the one running `Start`.
## Shutdown coordination
Every service uses a `stopC chan struct{}` to coordinate shutdown:
- **Constructor**: `stopC: make(chan struct{})`
- **Start**: blocks on `<-stopC` (or uses it in a `select` loop)
- **Stop**: `close(stopC)` to unblock `Start`
This is the standard pattern. Do not use `context.WithCancel` or other mechanisms for service-level shutdown coordination. See the examples in the next section.
## Service shapes
Two shapes recur across the codebase (these are not exhaustive, if a new shape is needed, bring it up for discussion before going ahead with the implementation), implemented by convention rather than base classes.
### Idle service
The service does work during startup or shutdown but has nothing to do while running. `Start` blocks on `<-stopC`. `Stop` closes `stopC` and optionally does cleanup.
The JWT tokenizer (`pkg/tokenizer/jwttokenizer/provider.go`) is a good example. It validates and creates tokens on demand via method calls, but has no periodic work to do. It still needs the service lifecycle so the registry can manage its lifetime:
```go
// pkg/tokenizer/jwttokenizer/provider.go
func (provider *provider) Start(ctx context.Context) error {
<-provider.stopC
return nil
}
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
return nil
}
```
The instrumentation SDK (`pkg/instrumentation/sdk.go`) is idle while running but does real cleanup in `Stop` shutting down its OpenTelemetry tracer and meter providers:
```go
// pkg/instrumentation/sdk.go
func (i *SDK) Start(ctx context.Context) error {
<-i.startCh
return nil
}
func (i *SDK) Stop(ctx context.Context) error {
close(i.startCh)
return errors.Join(
i.sdk.Shutdown(ctx),
i.meterProviderShutdownFunc(ctx),
)
}
```
### Scheduled service
The service runs an operation repeatedly on a fixed interval. `Start` runs a ticker loop with a `select` on `stopC` and the ticker channel.
The opaque tokenizer (`pkg/tokenizer/opaquetokenizer/provider.go`) garbage-collects expired tokens and flushes cached last-observed-at timestamps to the database on a configurable interval:
```go
// pkg/tokenizer/opaquetokenizer/provider.go
func (provider *provider) Start(ctx context.Context) error {
ticker := time.NewTicker(provider.config.Opaque.GC.Interval)
defer ticker.Stop()
for {
select {
case <-provider.stopC:
return nil
case <-ticker.C:
orgs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to get orgs data", "error", err)
continue
}
for _, org := range orgs {
if err := provider.gc(ctx, org); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to garbage collect tokens", "error", err, "org_id", org.ID)
}
if err := provider.flushLastObservedAt(ctx, org); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to flush tokens", "error", err, "org_id", org.ID)
}
}
}
}
}
```
Its `Stop` does a final gc and flush before returning, so no data is lost on shutdown:
```go
// pkg/tokenizer/opaquetokenizer/provider.go
func (provider *provider) Stop(ctx context.Context) error {
close(provider.stopC)
orgs, err := provider.orgGetter.ListByOwnedKeyRange(ctx)
if err != nil {
return err
}
for _, org := range orgs {
if err := provider.gc(ctx, org); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to garbage collect tokens", "error", err, "org_id", org.ID)
}
if err := provider.flushLastObservedAt(ctx, org); err != nil {
provider.settings.Logger().ErrorContext(ctx, "failed to flush tokens", "error", err, "org_id", org.ID)
}
}
return nil
}
```
The key points:
- In the loop, `select` on `stopC` and the ticker. Errors in iterations are logged but do not cause the service to return (which would shut down the application).
- Only return an error from `Start` if the failure is unrecoverable.
- Use `Stop` to flush or drain any in-memory state before the process exits.
## How to create a service
There are two cases: a standalone service and a provider that is also a service.
### Standalone service
A standalone service only has the `factory.Service` lifecycle i.e it does not serve as a dependency for other packages. The user reconciliation service is an example.
1. Define the service interface in your package. Embed `factory.Service`:
```go
// pkg/modules/user/service.go
package user
type Service interface {
factory.Service
}
```
2. Create the implementation in an `impl` sub-package. Use an unexported struct with an exported constructor that returns the interface:
```go
// pkg/modules/user/impluser/service.go
package impluser
type service struct {
settings factory.ScopedProviderSettings
// ... dependencies ...
stopC chan struct{}
}
func NewService(
providerSettings factory.ProviderSettings,
// ... dependencies ...
) user.Service {
return &service{
settings: factory.NewScopedProviderSettings(providerSettings, "go.signoz.io/pkg/modules/user"),
// ... dependencies ...
stopC: make(chan struct{}),
}
}
func (s *service) Start(ctx context.Context) error { ... }
func (s *service) Stop(ctx context.Context) error { ... }
```
### Provider that is also a service
Many providers need a managed lifecycle: they poll, sync, or garbage-collect in the background. In this case, embed `factory.Service` in the provider interface. The implementation satisfies both the provider methods and `Start`/`Stop`.
```go
// pkg/tokenizer/tokenizer.go
package tokenizer
type Tokenizer interface {
factory.Service
CreateToken(context.Context, *authtypes.Identity, map[string]string) (*authtypes.Token, error)
GetIdentity(context.Context, string) (*authtypes.Identity, error)
// ... other methods ...
}
```
The implementation (e.g. `pkg/tokenizer/opaquetokenizer/provider.go`) implements `Start`, `Stop`, and all the provider methods on the same struct. See the [provider guide](provider.md) for how to set up the factory, config, and constructor. The `stopC` channel and `Start`/`Stop` methods follow the same patterns described above.
## How to wire it up
Wiring happens in `pkg/signoz/signoz.go`.
### 1. Instantiate the service
For a standalone service, call the constructor directly:
```go
userService := impluser.NewService(providerSettings, store, module, orgGetter, authz, config.User.Root)
```
For a provider that is also a service, use `factory.NewProviderFromNamedMap` as described in the [provider guide](provider.md). The returned value already implements `factory.Service`.
### 2. Register in the registry
Wrap the service with `factory.NewNamedService` and pass it to `factory.NewRegistry`:
```go
registry, err := factory.NewRegistry(
instrumentation.Logger(),
// ... other services ...
factory.NewNamedService(factory.MustNewName("user"), userService),
)
```
The name must be unique across all services. The registry handles the rest:
- **Start**: launches all services concurrently in goroutines.
- **Wait**: blocks until a service returns an error, the context is cancelled, or a SIGINT/SIGTERM is received. Any service error triggers application shutdown.
- **Stop**: stops all services concurrently, collects errors via `errors.Join`.
You do not call `Start` or `Stop` on individual services. The registry does it.
## What should I remember?
- A service has a managed lifecycle: `Start` blocks, `Stop` unblocks it.
- Use `stopC chan struct{}` for shutdown coordination. `close(stopC)` in `Stop`, `<-stopC` in `Start`.
- Service shapes: idle (block on `stopC`) and scheduled (ticker loop with `select`).
- Unexported struct, exported `NewService` constructor returning the interface.
- First constructor parameter is `factory.ProviderSettings`. Create scoped settings with `factory.NewScopedProviderSettings`.
- Register in `factory.Registry` with `factory.NewNamedService`. The registry starts and stops everything.
- Only return an error from `Start` if the failure is unrecoverable. Log and continue for transient errors in polling loops.
## Further reading
- [Google Guava - ServiceExplained](https://github.com/google/guava/wiki/ServiceExplained) - the service lifecycle pattern takes inspiration from this
- [OpenTelemetry Collector](https://github.com/open-telemetry/opentelemetry-collector) - Worth studying for its approach to building composable components

View File

@@ -98,16 +98,20 @@ func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.
return provider.pkgAuthzService.ListByOrgIDAndNames(ctx, orgID, names)
}
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
return provider.pkgAuthzService.Grant(ctx, orgID, name, subject)
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*roletypes.Role, error) {
return provider.pkgAuthzService.ListByOrgIDAndIDs(ctx, orgID, ids)
}
func (provider *provider) ModifyGrant(ctx context.Context, orgID valuer.UUID, existingRoleName string, updatedRoleName string, subject string) error {
return provider.pkgAuthzService.ModifyGrant(ctx, orgID, existingRoleName, updatedRoleName, subject)
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
return provider.pkgAuthzService.Grant(ctx, orgID, names, subject)
}
func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, name string, subject string) error {
return provider.pkgAuthzService.Revoke(ctx, orgID, name, subject)
func (provider *provider) ModifyGrant(ctx context.Context, orgID valuer.UUID, existingRoleNames []string, updatedRoleNames []string, subject string) error {
return provider.pkgAuthzService.ModifyGrant(ctx, orgID, existingRoleNames, updatedRoleNames, subject)
}
func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
return provider.pkgAuthzService.Revoke(ctx, orgID, names, subject)
}
func (provider *provider) CreateManagedRoles(ctx context.Context, orgID valuer.UUID, managedRoles []*roletypes.Role) error {

View File

@@ -1,16 +0,0 @@
{
"presets": [
"@babel/preset-env",
["@babel/preset-react", { "runtime": "automatic" }],
"@babel/preset-typescript"
],
"plugins": [
"react-hot-loader/babel",
"@babel/plugin-proposal-class-properties"
],
"env": {
"production": {
"presets": ["minify"]
}
}
}

View File

@@ -1,77 +0,0 @@
---
description: Global vs local mock strategy for Jest tests
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# Mock Decision Strategy
## Global Mocks (20+ test files)
- Core infrastructure: react-router-dom, react-query, antd
- Browser APIs: ResizeObserver, matchMedia, localStorage
- Utility libraries: date-fns, lodash
- Available: `uplot` → `__mocks__/uplotMock.ts`
## Local Mocks (515 test files)
- Business logic dependencies
- API endpoints with specific responses
- Domain-specific components
- Error scenarios and edge cases
## Decision Tree
```
Is it used in 20+ test files?
├─ YES → Use Global Mock
│ ├─ react-router-dom
│ ├─ react-query
│ ├─ antd components
│ └─ browser APIs
└─ NO → Is it business logic?
├─ YES → Use Local Mock
│ ├─ API endpoints
│ ├─ Custom hooks
│ └─ Domain components
└─ NO → Is it test-specific?
├─ YES → Use Local Mock
│ ├─ Error scenarios
│ ├─ Loading states
│ └─ Specific data
└─ NO → Consider Global Mock
└─ If it becomes frequently used
```
## Anti-patterns
❌ Don't mock global dependencies locally:
```ts
jest.mock('react-router-dom', () => ({ ... })); // Already globally mocked
```
❌ Don't create global mocks for test-specific data:
```ts
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => specificTestData) // BAD - should be local
}));
```
✅ Do use global mocks for infrastructure:
```ts
import { useLocation } from 'react-router-dom';
```
✅ Do create local mocks for business logic:
```ts
jest.mock('../api/tracesService', () => ({
getTraces: jest.fn(() => mockTracesData)
}));
```

View File

@@ -1,124 +0,0 @@
---
description: Core Jest/React Testing Library conventions - harness, MSW, interactions, timers
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# Jest Test Conventions
Expert developer with Jest, React Testing Library, MSW, and TypeScript. Focus on critical functionality, mock dependencies before imports, test multiple scenarios, write maintainable tests.
**Auto-detect TypeScript**: Check for TypeScript in the project through tsconfig.json or package.json dependencies. Adjust syntax based on this detection.
## Imports
Always import from our harness:
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
```
For API mocks:
```ts
import { server, rest } from 'mocks-server/server';
```
❌ Do not import directly from `@testing-library/react`.
## Router
Use the router built into render:
```ts
render(<Page />, undefined, { initialRoute: '/traces-explorer' });
```
Only mock `useLocation` / `useParams` if the test depends on them.
## Hook Mocks
```ts
import useFoo from 'hooks/useFoo';
jest.mock('hooks/useFoo');
const mockUseFoo = jest.mocked(useFoo);
mockUseFoo.mockReturnValue(/* minimal shape */ as any);
```
Prefer helpers (`rqSuccess`, `rqLoading`, `rqError`) for React Query results.
## MSW
Global MSW server runs automatically. Override per-test:
```ts
server.use(
rest.get('*/api/v1/foo', (_req, res, ctx) => res(ctx.status(200), ctx.json({ ok: true })))
);
```
Keep large responses in `mocks-server/__mockdata__/`.
## Interactions
- Prefer `userEvent` for real user interactions (click, type, select, tab).
- Use `fireEvent` only for low-level/programmatic events not covered by `userEvent` (e.g., scroll, resize, setting `element.scrollTop` for virtualization). Wrap in `act(...)` if needed.
- Always await interactions:
```ts
const user = userEvent.setup({ pointerEventsCheck: 0 });
await user.click(screen.getByRole('button', { name: /save/i }));
```
```ts
// Example: virtualized list scroll (no userEvent helper)
const scroller = container.querySelector('[data-test-id="virtuoso-scroller"]') as HTMLElement;
scroller.scrollTop = targetScrollTop;
act(() => { fireEvent.scroll(scroller); });
```
## Timers
❌ No global fake timers. ✅ Per-test only:
```ts
jest.useFakeTimers();
const user = userEvent.setup({ advanceTimers: (ms) => jest.advanceTimersByTime(ms) });
await user.type(screen.getByRole('textbox'), 'query');
jest.advanceTimersByTime(400);
jest.useRealTimers();
```
## Queries
Prefer accessible queries (`getByRole`, `findByRole`, `getByLabelText`). Fallback: visible text. Last resort: `data-testid`.
## Best Practices
- **Critical Functionality**: Prioritize testing business logic and utilities
- **Dependency Mocking**: Global mocks for infra, local mocks for business logic
- **Data Scenarios**: Always test valid, invalid, and edge cases
- **Descriptive Names**: Make test intent clear
- **Organization**: Group related tests in describe
- **Consistency**: Match repo conventions
- **Edge Cases**: Test null, undefined, unexpected values
- **Limit Scope**: 35 focused tests per file
- **Use Helpers**: `rqSuccess`, `makeUser`, etc.
- **No Any**: Enforce type safety
## Anti-patterns
❌ Importing RTL directly | ❌ Global fake timers | ❌ Wrapping render in `act(...)` | ❌ Mocking infra locally
✅ Use harness | ✅ MSW for API | ✅ userEvent + await | ✅ Pin time only for relative-date tests
## Example
```ts
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import { server, rest } from 'mocks-server/server';
import MyComponent from '../MyComponent';
describe('MyComponent', () => {
it('renders and interacts', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
server.use(rest.get('*/api/v1/example', (_req, res, ctx) => res(ctx.status(200), ctx.json({ value: 42 }))));
render(<MyComponent />, undefined, { initialRoute: '/foo' });
expect(await screen.findByText(/value: 42/i)).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: /refresh/i }));
await waitFor(() => expect(screen.getByText(/loading/i)).toBeInTheDocument());
});
});
```

View File

@@ -1,168 +0,0 @@
---
description: TypeScript type safety for Jest tests - mocks, interfaces, no any
globs:
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/__tests__/**/*.ts"
- "**/__tests__/**/*.tsx"
alwaysApply: false
---
# TypeScript Type Safety for Jest Tests
**CRITICAL**: All Jest tests MUST be fully type-safe.
## Requirements
- Use proper TypeScript interfaces for all mock data
- Type all Jest mock functions with `jest.MockedFunction<T>`
- Use generic types for React components and hooks
- Define proper return types for mock functions
- Use `as const` for literal types when needed
- Avoid `any` type use proper typing instead
## Mock Function Typing
```ts
// ✅ GOOD
const mockFetchUser = jest.fn() as jest.MockedFunction<(id: number) => Promise<ApiResponse<User>>>;
const mockEventHandler = jest.fn() as jest.MockedFunction<(event: Event) => void>;
// ❌ BAD
const mockFetchUser = jest.fn() as any;
```
## Mock Data with Interfaces
```ts
interface User { id: number; name: string; email: string; }
interface ApiResponse<T> { data: T; status: number; message: string; }
const mockUser: User = { id: 1, name: 'John Doe', email: 'john@example.com' };
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
```
## Component Props Typing
```ts
interface ComponentProps { title: string; data: User[]; onUserSelect: (user: User) => void; }
const mockProps: ComponentProps = {
title: 'Test',
data: [{ id: 1, name: 'John', email: 'john@example.com' }],
onUserSelect: jest.fn() as jest.MockedFunction<(user: User) => void>,
};
render(<TestComponent {...mockProps} />);
```
## Hook Testing with Types
```ts
interface UseUserDataReturn {
user: User | null;
loading: boolean;
error: string | null;
refetch: () => void;
}
describe('useUserData', () => {
it('should return user data with proper typing', () => {
const mockUser: User = { id: 1, name: 'John', email: 'john@example.com' };
mockFetchUser.mockResolvedValue({ data: mockUser, status: 200, message: 'Success' });
const { result } = renderHook(() => useUserData(1));
expect(result.current.user).toEqual(mockUser);
expect(result.current.loading).toBe(false);
expect(result.current.error).toBeNull();
});
});
```
## Generic Mock Typing
```ts
interface MockApiResponse<T> { data: T; status: number; }
const mockFetchData = jest.fn() as jest.MockedFunction<
<T>(endpoint: string) => Promise<MockApiResponse<T>>
>;
mockFetchData<User>('/users').mockResolvedValue({ data: { id: 1, name: 'John' }, status: 200 });
```
## React Testing Library with Types
```ts
type TestComponentProps = ComponentProps<typeof TestComponent>;
const renderTestComponent = (props: Partial<TestComponentProps> = {}): RenderResult => {
const defaultProps: TestComponentProps = { title: 'Test', data: [], onSelect: jest.fn(), ...props };
return render(<TestComponent {...defaultProps} />);
};
```
## Error Handling with Types
```ts
interface ApiError { message: string; code: number; details?: Record<string, unknown>; }
const mockApiError: ApiError = { message: 'API Error', code: 500, details: { endpoint: '/users' } };
mockFetchUser.mockRejectedValue(new Error(JSON.stringify(mockApiError)));
```
## Global Mock Type Safety
```ts
// In __mocks__/routerMock.ts
export const mockUseLocation = (overrides: Partial<Location> = {}): Location => ({
pathname: '/traces',
search: '',
hash: '',
state: null,
key: 'test-key',
...overrides,
});
// In test files: const location = useLocation(); // Properly typed from global mock
```
## TypeScript Configuration for Jest
```json
// jest.config.ts
{
"preset": "ts-jest/presets/js-with-ts-esm",
"globals": {
"ts-jest": {
"useESM": true,
"isolatedModules": true,
"tsconfig": "<rootDir>/tsconfig.jest.json"
}
},
"extensionsToTreatAsEsm": [".ts", ".tsx"],
"moduleFileExtensions": ["ts", "tsx", "js", "json"]
}
```
```json
// tsconfig.jest.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "@testing-library/jest-dom"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node"
},
"include": ["src/**/*", "**/*.test.ts", "**/*.test.tsx", "__mocks__/**/*"]
}
```
## Type Safety Checklist
- [ ] All mock functions use `jest.MockedFunction<T>`
- [ ] All mock data has proper interfaces
- [ ] No `any` types in test files
- [ ] Generic types are used where appropriate
- [ ] Error types are properly defined
- [ ] Component props are typed
- [ ] Hook return types are defined
- [ ] API response types are defined
- [ ] Global mocks are type-safe
- [ ] Test utilities are properly typed

View File

@@ -2,12 +2,15 @@
* ESLint Configuration for SigNoz Frontend
*/
module.exports = {
ignorePatterns: ['src/parser/*.ts', 'scripts/update-registry.js'],
ignorePatterns: [
'src/parser/*.ts',
'scripts/update-registry.js',
'scripts/generate-permissions-type.js',
],
env: {
browser: true,
es2021: true,
node: true,
'jest/globals': true,
},
extends: [
'eslint:recommended',
@@ -21,6 +24,7 @@ module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
tsconfigRootDir: __dirname,
ecmaFeatures: {
jsx: true,
},
@@ -33,7 +37,7 @@ module.exports = {
'simple-import-sort', // Auto-sort imports
'react-hooks', // React Hooks rules
'prettier', // Code formatting
'jest', // Jest test rules
// 'jest', // TODO: Wait support on Biome to enable again
'jsx-a11y', // Accessibility rules
'import', // Import/export linting
'sonarjs', // Code quality/complexity

View File

@@ -1 +1 @@
16.15.0
22

View File

@@ -0,0 +1,4 @@
export const ENVIRONMENT = {
baseURL: process.env.VITE_FRONTEND_API_ENDPOINT || '',
wsURL: process.env.VITE_WEBSOCKET_API_ENDPOINT || '',
};

20
frontend/babel.config.cjs Normal file
View File

@@ -0,0 +1,20 @@
module.exports = {
presets: [
['@babel/preset-env', { modules: 'auto' }],
['@babel/preset-react', { runtime: 'automatic' }],
['@babel/preset-typescript'],
],
plugins: ['@babel/plugin-proposal-class-properties'],
env: {
test: {
presets: [
[
'@babel/preset-env',
{ modules: 'commonjs', targets: { node: 'current' } },
],
['@babel/preset-react', { runtime: 'automatic' }],
['@babel/preset-typescript'],
],
},
},
};

View File

@@ -1,6 +0,0 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};

View File

@@ -1,8 +0,0 @@
{
"files": [
{
"path": "./build/**.js",
"maxSize": "1.2MB"
}
]
}

View File

@@ -1,8 +1,8 @@
NODE_ENV="development"
BUNDLE_ANALYSER="true"
FRONTEND_API_ENDPOINT="http://localhost:8080/"
PYLON_APP_ID="pylon-app-id"
APPCUES_APP_ID="appcess-app-id"
PYLON_IDENTITY_SECRET="pylon-identity-secret"
VITE_FRONTEND_API_ENDPOINT="http://localhost:8080/"
VITE_PYLON_APP_ID="pylon-app-id"
VITE_APPCUES_APP_ID="appcess-app-id"
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
CI="1"
CI="1"

View File

@@ -1,127 +1,119 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate, max-age: 0"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<!-- Preconnect to CDNs -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.vercel.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
rel="stylesheet"
/>
<title data-react-helmet="true">
Open source Observability platform | SigNoz
</title>
<meta
data-react-helmet="true"
property="og:title"
content="Open source Observability platform | SigNoz"
/>
<meta
data-react-helmet="true"
name="description"
content="SigNoz is an open source observability platform to help you find issues in your deployed applications & solve them quickly. It provides a single pane of glass for metrics, traces and logs with deep filtering and aggregation to pin down specific issues very quickly."
/>
<meta
data-react-helmet="true"
property="og:description"
content="SigNoz is an open source observability platform to help you find issues in your deployed applications & solve them quickly. It provides a single pane of glass for metrics, traces and logs with deep filtering and aggregation to pin down specific issues very quickly."
/>
<meta
data-react-helmet="true"
property="og:image"
content="/images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image"
content="/images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image:alt"
content="Image for Open source Observability platform | SigNoz"
/>
<meta
data-react-helmet="true"
name="twitter:card"
content="summary_large_image"
/>
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
<% if (htmlWebpackPlugin.options.templateParameters.preloadFonts) { %> <%
htmlWebpackPlugin.options.templateParameters.preloadFonts.forEach(function(font)
{ %>
<link
rel="preload"
href="<%= font.href %>"
as="<%= font.as %>"
type="<%= font.type %>"
crossorigin="<%= font.crossorigin %>"
/>
<% }); %> <% } %>
</head>
<body data-theme="default">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
const PYLON_APP_ID = '<%= htmlWebpackPlugin.options.PYLON_APP_ID %>';
(function () {
var e = window;
var t = document;
var n = function () {
n.e(arguments);
};
n.q = [];
n.e = function (e) {
n.q.push(e);
};
e.Pylon = n;
var r = function () {
var e = t.createElement('script');
e.setAttribute('type', 'text/javascript');
e.setAttribute('async', 'true');
e.setAttribute(
'src',
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
);
var n = t.getElementsByTagName('script')[0];
n.parentNode.insertBefore(e, n);
};
if (t.readyState === 'complete') {
r();
} else if (e.addEventListener) {
e.addEventListener('load', r, false);
}
})();
</script>
<script type="text/javascript">
window.AppcuesSettings = { enableURLDetection: true };
</script>
<script>
const APPCUES_APP_ID = '<%= htmlWebpackPlugin.options.APPCUES_APP_ID %>';
(function (d, t) {
var a = d.createElement(t);
a.async = 1;
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
var s = d.getElementsByTagName(t)[0];
s.parentNode.insertBefore(a, s);
})(document, 'script');
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate, max-age: 0"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.vercel.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
rel="stylesheet"
/>
<title data-react-helmet="true">
Open source Observability platform | SigNoz
</title>
<meta
data-react-helmet="true"
property="og:title"
content="Open source Observability platform | SigNoz"
/>
<meta
data-react-helmet="true"
name="description"
content="SigNoz is an open source observability platform to help you find issues in your deployed applications & solve them quickly. It provides a single pane of glass for metrics, traces and logs with deep filtering and aggregation to pin down specific issues very quickly."
/>
<meta
data-react-helmet="true"
property="og:description"
content="SigNoz is an open source observability platform to help you find issues in your deployed applications & solve them quickly. It provides a single pane of glass for metrics, traces and logs with deep filtering and aggregation to pin down specific issues very quickly."
/>
<meta
data-react-helmet="true"
property="og:image"
content="/images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image"
content="/images/signoz-hero-image.webp"
/>
<meta
data-react-helmet="true"
name="twitter:image:alt"
content="Image for Open source Observability platform | SigNoz"
/>
<meta
data-react-helmet="true"
name="twitter:card"
content="summary_large_image"
/>
<meta data-react-helmet="true" name="docusaurus_locale" content="en" />
<meta data-react-helmet="true" name="docusaurus_tag" content="default" />
<meta name="robots" content="noindex" />
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
</head>
<body data-theme="default">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>
var PYLON_APP_ID = '<%- PYLON_APP_ID %>';
if (PYLON_APP_ID) {
(function () {
var e = window;
var t = document;
var n = function () {
n.e(arguments);
};
n.q = [];
n.e = function (e) {
n.q.push(e);
};
e.Pylon = n;
var r = function () {
var e = t.createElement('script');
e.setAttribute('type', 'text/javascript');
e.setAttribute('async', 'true');
e.setAttribute(
'src',
'https://widget.usepylon.com/widget/' + PYLON_APP_ID,
);
var n = t.getElementsByTagName('script')[0];
n.parentNode.insertBefore(e, n);
};
if (t.readyState === 'complete') {
r();
} else if (e.addEventListener) {
e.addEventListener('load', r, false);
}
})();
}
</script>
<script type="text/javascript">
window.AppcuesSettings = { enableURLDetection: true };
</script>
<script>
var APPCUES_APP_ID = '<%- APPCUES_APP_ID %>';
if (APPCUES_APP_ID) {
(function (d, t) {
var a = d.createElement(t);
a.async = 1;
a.src = '//fast.appcues.com/' + APPCUES_APP_ID + '.js';
var s = d.getElementsByTagName(t)[0];
s.parentNode.insertBefore(a, s);
})(document, 'script');
}
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

View File

@@ -17,27 +17,49 @@ const config: Config.InitialOptions = {
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
'^constants/env$': '<rootDir>/__mocks__/env.ts',
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
'^@signozhq/icons$':
'<rootDir>/node_modules/@signozhq/icons/dist/index.esm.js',
'^react-syntax-highlighter/dist/esm/(.*)$':
'<rootDir>/node_modules/react-syntax-highlighter/dist/cjs/$1',
'^@signozhq/sonner$':
'<rootDir>/node_modules/@signozhq/sonner/dist/sonner.js',
'^@signozhq/button$':
'<rootDir>/node_modules/@signozhq/button/dist/button.js',
'^@signozhq/calendar$':
'<rootDir>/node_modules/@signozhq/calendar/dist/calendar.js',
'^@signozhq/badge': '<rootDir>/node_modules/@signozhq/badge/dist/badge.js',
'^@signozhq/checkbox':
'<rootDir>/node_modules/@signozhq/checkbox/dist/checkbox.js',
'^@signozhq/switch': '<rootDir>/node_modules/@signozhq/switch/dist/switch.js',
'^@signozhq/callout':
'<rootDir>/node_modules/@signozhq/callout/dist/callout.js',
'^@signozhq/combobox':
'<rootDir>/node_modules/@signozhq/combobox/dist/combobox.js',
'^@signozhq/input': '<rootDir>/node_modules/@signozhq/input/dist/input.js',
'^@signozhq/command':
'<rootDir>/node_modules/@signozhq/command/dist/command.js',
'^@signozhq/radio-group':
'<rootDir>/node_modules/@signozhq/radio-group/dist/radio-group.js',
},
globals: {
extensionsToTreatAsEsm: ['.ts'],
'ts-jest': {
useESM: true,
isolatedModules: true,
tsconfig: '<rootDir>/tsconfig.jest.json',
},
},
extensionsToTreatAsEsm: ['.ts'],
testMatch: ['<rootDir>/src/**/*?(*.)(test).(ts|js)?(x)'],
preset: 'ts-jest/presets/js-with-ts-esm',
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
'^.+\\.(ts|tsx)?$': [
'ts-jest',
{
useESM: true,
tsconfig: '<rootDir>/tsconfig.jest.json',
},
],
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana)/)',
],
setupFilesAfterEnv: ['<rootDir>jest.setup.ts'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],
moduleDirectories: ['node_modules', 'src'],
testEnvironment: 'jest-environment-jsdom',

View File

@@ -1,297 +1,283 @@
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "webpack.config.js",
"scripts": {
"i18n:generate-hash": "node ./i18-generate-hash.js",
"dev": "cross-env NODE_ENV=development webpack serve --progress",
"build": "webpack --config=webpack.config.prod.js --progress",
"prettify": "prettier --write .",
"fmt": "prettier --check .",
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"jest": "jest",
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.js",
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
"commitlint": "commitlint --edit $1",
"test": "jest",
"test:changedsince": "jest --changedSince=main --coverage --silent",
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh"
},
"engines": {
"node": ">=16.15.0"
},
"author": "",
"license": "ISC",
"dependencies": {
"@ant-design/colors": "6.0.0",
"@ant-design/icons": "4.8.0",
"@codemirror/autocomplete": "6.18.6",
"@codemirror/lang-javascript": "6.2.3",
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@grafana/data": "^11.2.3",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
"@monaco-editor/react": "^4.3.1",
"@playwright/test": "1.55.1",
"@radix-ui/react-tabs": "1.0.4",
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
"@sentry/webpack-plugin": "2.22.6",
"@signozhq/badge": "0.0.2",
"@signozhq/button": "0.0.2",
"@signozhq/calendar": "0.0.0",
"@signozhq/callout": "0.0.2",
"@signozhq/checkbox": "0.0.2",
"@signozhq/combobox": "0.0.2",
"@signozhq/command": "0.0.0",
"@signozhq/design-tokens": "2.1.1",
"@signozhq/dialog": "0.0.2",
"@signozhq/drawer": "0.0.4",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.2",
"@signozhq/popover": "0.0.0",
"@signozhq/radio-group": "0.0.2",
"@signozhq/resizable": "0.0.0",
"@signozhq/sonner": "0.1.0",
"@signozhq/switch": "0.0.2",
"@signozhq/table": "0.3.7",
"@signozhq/tabs": "0.0.11",
"@signozhq/tooltip": "0.0.2",
"@tanstack/react-table": "8.20.6",
"@tanstack/react-virtual": "3.11.2",
"@uiw/codemirror-theme-copilot": "4.23.11",
"@uiw/codemirror-theme-dracula": "4.25.4",
"@uiw/codemirror-theme-github": "4.24.1",
"@uiw/react-codemirror": "4.23.10",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/hierarchy": "3.12.0",
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"antlr4": "4.13.2",
"axios": "1.12.0",
"babel-eslint": "^10.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.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",
"crypto-js": "4.2.0",
"css-loader": "5.0.0",
"css-minimizer-webpack-plugin": "5.0.1",
"d3-hierarchy": "3.1.2",
"dayjs": "^1.10.7",
"dompurify": "3.2.4",
"dotenv": "8.2.0",
"event-source-polyfill": "1.0.31",
"eventemitter3": "5.0.1",
"file-loader": "6.1.1",
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"html-webpack-plugin": "5.5.0",
"http-proxy-middleware": "3.0.5",
"http-status-codes": "2.3.0",
"i18next": "^21.6.12",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
"immer": "11.1.3",
"jest": "^27.5.1",
"js-base64": "^3.7.2",
"less": "^4.1.2",
"less-loader": "^10.2.0",
"lodash-es": "^4.17.21",
"lucide-react": "0.498.0",
"mini-css-extract-plugin": "2.4.5",
"motion": "12.4.13",
"nuqs": "2.8.8",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
"posthog-js": "1.298.0",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",
"react-beautiful-dnd": "13.1.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-error-boundary": "4.0.11",
"react-force-graph-2d": "^1.29.1",
"react-full-screen": "1.1.1",
"react-grid-layout": "^1.3.4",
"react-helmet-async": "1.3.0",
"react-hook-form": "7.40.0",
"react-i18next": "^11.16.1",
"react-lottie": "1.2.10",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.27.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",
"rehype-raw": "7.0.0",
"rrule": "2.8.1",
"stream": "^0.0.2",
"style-loader": "1.3.0",
"styled-components": "^5.3.11",
"terser-webpack-plugin": "^5.2.5",
"timestamp-nano": "^1.0.0",
"ts-node": "^10.2.1",
"tsconfig-paths-webpack-plugin": "^3.5.1",
"typescript": "^4.0.5",
"uplot": "1.6.31",
"uuid": "^8.3.2",
"web-vitals": "^0.2.4",
"webpack": "5.94.0",
"webpack-dev-server": "^5.2.1",
"webpack-retry-chunk-load-plugin": "3.1.1",
"xstate": "^4.31.0",
"zustand": "5.0.11"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.22.11",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/preset-env": "^7.22.14",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.21.4",
"@commitlint/cli": "^16.3.0",
"@commitlint/config-conventional": "^16.2.4",
"@faker-js/faker": "9.3.0",
"@jest/globals": "^27.5.1",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
"@types/color": "^3.0.3",
"@types/compression-webpack-plugin": "^9.0.0",
"@types/copy-webpack-plugin": "^8.0.1",
"@types/crypto-js": "4.2.2",
"@types/dompurify": "^2.4.0",
"@types/event-source-polyfill": "^1.0.0",
"@types/fontfaceobserver": "2.1.0",
"@types/jest": "^27.5.1",
"@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1",
"@types/node": "^16.10.3",
"@types/papaparse": "5.3.7",
"@types/react": "18.0.26",
"@types/react-addons-update": "0.14.21",
"@types/react-beautiful-dnd": "13.1.8",
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
"@types/react-helmet-async": "1.0.3",
"@types/react-lottie": "1.2.10",
"@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/redux-mock-store": "1.0.4",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
"@types/webpack": "^5.28.0",
"@types/webpack-dev-server": "^4.7.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0",
"compression-webpack-plugin": "9.0.0",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^26.9.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sonarjs": "^0.12.0",
"husky": "^7.0.4",
"image-minimizer-webpack-plugin": "^4.0.0",
"imagemin": "^8.0.1",
"imagemin-svgo": "^10.0.1",
"is-ci": "^3.0.1",
"jest-styled-components": "^7.0.8",
"lint-staged": "^12.5.0",
"msw": "1.3.2",
"npm-run-all": "latest",
"orval": "7.18.0",
"portfinder-sync": "^0.0.2",
"postcss": "8.4.38",
"prettier": "2.2.1",
"prop-types": "15.8.1",
"raw-loader": "4.0.2",
"react-hooks-testing-library": "0.6.0",
"react-hot-loader": "^4.13.0",
"react-resizable": "3.0.4",
"redux-mock-store": "1.5.4",
"sass": "1.66.1",
"sass-loader": "13.3.2",
"sharp": "^0.33.4",
"ts-jest": "^27.1.5",
"ts-node": "^10.2.1",
"typescript-plugin-css-modules": "5.2.0",
"webpack-bundle-analyzer": "^4.5.0",
"webpack-cli": "^5.1.4"
},
"lint-staged": {
"*.(js|jsx|ts|tsx)": [
"eslint --fix",
"sh scripts/typecheck-staged.sh"
]
},
"resolutions": {
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"debug": "4.3.4",
"semver": "7.5.4",
"xml2js": "0.5.0",
"phin": "^3.7.1",
"body-parser": "1.20.3",
"http-proxy-middleware": "3.0.5",
"cross-spawn": "7.0.5",
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0",
"got": "11.8.5",
"form-data": "4.0.4",
"brace-expansion": "^2.0.2",
"on-headers": "^1.1.0",
"tmp": "0.2.4"
}
"name": "frontend",
"version": "1.0.0",
"description": "",
"type": "module",
"scripts": {
"i18n:generate-hash": "node ./i18-generate-hash.cjs",
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"prettify": "prettier --write .",
"fmt": "prettier --check .",
"lint": "eslint ./src",
"lint:fix": "eslint ./src --fix",
"jest": "jest",
"jest:coverage": "jest --coverage",
"jest:watch": "jest --watch",
"postinstall": "yarn i18n:generate-hash && (is-ci || yarn husky:configure) && node scripts/update-registry.cjs",
"husky:configure": "cd .. && husky install frontend/.husky && cd frontend && chmod ug+x .husky/*",
"commitlint": "commitlint --edit $1",
"test": "jest",
"test:changedsince": "jest --changedSince=main --coverage --silent",
"generate:api": "orval --config ./orval.config.ts && sh scripts/post-types-generation.sh",
"generate:permissions-type": "node scripts/generate-permissions-type.cjs"
},
"engines": {
"node": ">=22.0.0"
},
"author": "",
"license": "ISC",
"dependencies": {
"@ant-design/colors": "6.0.0",
"@ant-design/icons": "4.8.0",
"@codemirror/autocomplete": "6.18.6",
"@codemirror/lang-javascript": "6.2.3",
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@grafana/data": "^11.2.3",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
"@monaco-editor/react": "^4.3.1",
"@playwright/test": "1.55.1",
"@radix-ui/react-tabs": "1.0.4",
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"@signozhq/badge": "0.0.2",
"@signozhq/button": "0.0.2",
"@signozhq/calendar": "0.0.0",
"@signozhq/callout": "0.0.2",
"@signozhq/checkbox": "0.0.2",
"@signozhq/combobox": "0.0.2",
"@signozhq/command": "0.0.0",
"@signozhq/design-tokens": "2.1.1",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.2",
"@signozhq/popover": "0.0.0",
"@signozhq/radio-group": "0.0.2",
"@signozhq/resizable": "0.0.0",
"@signozhq/sonner": "0.1.0",
"@signozhq/switch": "0.0.2",
"@signozhq/table": "0.3.7",
"@signozhq/tooltip": "0.0.2",
"@tanstack/react-table": "8.20.6",
"@tanstack/react-virtual": "3.11.2",
"@uiw/codemirror-theme-copilot": "4.23.11",
"@uiw/codemirror-theme-github": "4.24.1",
"@uiw/react-codemirror": "4.23.10",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/hierarchy": "3.12.0",
"@visx/shape": "3.5.0",
"@visx/tooltip": "3.3.0",
"@vitejs/plugin-react": "5.1.4",
"@xstate/react": "^3.0.0",
"ansi-to-html": "0.7.2",
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"antlr4": "4.13.2",
"axios": "1.12.0",
"babel-eslint": "^10.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.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": "2.0.0",
"cross-env": "^7.0.3",
"crypto-js": "4.2.0",
"d3-hierarchy": "3.1.2",
"dayjs": "^1.10.7",
"dompurify": "3.2.4",
"dotenv": "8.2.0",
"event-source-polyfill": "1.0.31",
"eventemitter3": "5.0.1",
"fontfaceobserver": "2.3.0",
"history": "4.10.1",
"http-proxy-middleware": "3.0.5",
"http-status-codes": "2.3.0",
"i18next": "^21.6.12",
"i18next-browser-languagedetector": "^6.1.3",
"i18next-http-backend": "^1.3.2",
"immer": "11.1.3",
"jest": "30.2.0",
"js-base64": "^3.7.2",
"lodash-es": "^4.17.21",
"lucide-react": "0.498.0",
"mini-css-extract-plugin": "2.4.5",
"motion": "12.4.13",
"nuqs": "2.8.8",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
"posthog-js": "1.298.0",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",
"react-beautiful-dnd": "13.1.1",
"react-dnd": "16.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dom": "18.2.0",
"react-drag-listview": "2.0.0",
"react-error-boundary": "4.0.11",
"react-force-graph-2d": "^1.29.1",
"react-full-screen": "1.1.1",
"react-grid-layout": "^1.3.4",
"react-helmet-async": "1.3.0",
"react-i18next": "^11.16.1",
"react-lottie": "1.2.10",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "^7.2.2",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.27.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",
"rehype-raw": "7.0.0",
"rollup-plugin-visualizer": "7.0.0",
"rrule": "2.8.1",
"stream": "^0.0.2",
"styled-components": "^5.3.11",
"timestamp-nano": "^1.0.0",
"ts-node": "^10.2.1",
"typescript": "5.9.3",
"uplot": "1.6.31",
"uuid": "^8.3.2",
"vite": "npm:rolldown-vite@7.3.1",
"vite-plugin-html": "3.2.2",
"web-vitals": "^0.2.4",
"xstate": "^4.31.0",
"zustand": "5.0.11"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@babel/core": "^7.22.11",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-syntax-jsx": "^7.12.13",
"@babel/preset-env": "^7.22.14",
"@babel/preset-react": "^7.12.13",
"@babel/preset-typescript": "^7.21.4",
"@commitlint/cli": "^20.4.2",
"@commitlint/config-conventional": "^20.4.2",
"@faker-js/faker": "9.3.0",
"@jest/globals": "30.2.0",
"@testing-library/jest-dom": "5.16.5",
"@testing-library/react": "13.4.0",
"@testing-library/user-event": "14.4.3",
"@types/color": "^3.0.3",
"@types/crypto-js": "4.2.2",
"@types/dompurify": "^2.4.0",
"@types/event-source-polyfill": "^1.0.0",
"@types/fontfaceobserver": "2.1.0",
"@types/jest": "30.0.0",
"@types/lodash-es": "^4.17.4",
"@types/mini-css-extract-plugin": "^2.5.1",
"@types/node": "^16.10.3",
"@types/papaparse": "5.3.7",
"@types/react": "18.0.26",
"@types/react-addons-update": "0.14.21",
"@types/react-beautiful-dnd": "13.1.8",
"@types/react-dom": "18.0.10",
"@types/react-grid-layout": "^1.1.2",
"@types/react-helmet-async": "1.0.3",
"@types/react-lottie": "1.2.10",
"@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.13",
"@types/redux-mock-store": "1.0.4",
"@types/styled-components": "^5.1.4",
"@types/uuid": "^8.3.1",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"autoprefixer": "10.4.19",
"babel-plugin-styled-components": "^1.12.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^29.15.0",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.3.0",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-sonarjs": "^0.12.0",
"husky": "^7.0.4",
"imagemin": "^8.0.1",
"imagemin-svgo": "^10.0.1",
"is-ci": "^3.0.1",
"jest-environment-jsdom": "29.7.0",
"jest-environment-node": "29.7.0",
"jest-styled-components": "^7.2.0",
"lint-staged": "^12.5.0",
"msw": "1.3.2",
"npm-run-all": "latest",
"orval": "7.18.0",
"portfinder-sync": "^0.0.2",
"postcss": "8.5.6",
"prettier": "2.2.1",
"prop-types": "15.8.1",
"react-hooks-testing-library": "0.6.0",
"react-resizable": "3.0.4",
"redux-mock-store": "1.5.4",
"sass": "1.97.3",
"sharp": "0.34.5",
"svgo": "4.0.0",
"ts-api-utils": "2.4.0",
"ts-jest": "29.4.6",
"ts-node": "^10.2.1",
"typescript-plugin-css-modules": "5.2.0",
"vite-plugin-checker": "0.12.0",
"vite-plugin-compression": "0.5.1",
"vite-plugin-image-optimizer": "2.0.3",
"vite-tsconfig-paths": "6.1.1"
},
"lint-staged": {
"*.(js|jsx|ts|tsx)": [
"eslint --fix",
"sh scripts/typecheck-staged.sh"
]
},
"resolutions": {
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"debug": "4.3.4",
"semver": "7.5.4",
"xml2js": "0.5.0",
"phin": "^3.7.1",
"body-parser": "1.20.3",
"http-proxy-middleware": "3.0.5",
"cross-spawn": "7.0.5",
"cookie": "^0.7.1",
"serialize-javascript": "6.0.2",
"prismjs": "1.30.0",
"got": "11.8.5",
"form-data": "4.0.4",
"brace-expansion": "^2.0.2",
"on-headers": "^1.1.0",
"tmp": "0.2.4",
"vite": "npm:rolldown-vite@7.3.1"
}
}

View File

@@ -1,312 +0,0 @@
<svg width="929" height="8" viewBox="0 0 929 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="2" height="2" rx="1" fill="#242834"/>
<rect x="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="12" width="2" height="2" rx="1" fill="#242834"/>
<rect x="18" width="2" height="2" rx="1" fill="#242834"/>
<rect x="24" width="2" height="2" rx="1" fill="#242834"/>
<rect x="30" width="2" height="2" rx="1" fill="#242834"/>
<rect x="36" width="2" height="2" rx="1" fill="#242834"/>
<rect x="42" width="2" height="2" rx="1" fill="#242834"/>
<rect x="48" width="2" height="2" rx="1" fill="#242834"/>
<rect x="54" width="2" height="2" rx="1" fill="#242834"/>
<rect x="60" width="2" height="2" rx="1" fill="#242834"/>
<rect x="66" width="2" height="2" rx="1" fill="#242834"/>
<rect x="72" width="2" height="2" rx="1" fill="#242834"/>
<rect x="78" width="2" height="2" rx="1" fill="#242834"/>
<rect x="84" width="2" height="2" rx="1" fill="#242834"/>
<rect x="90" width="2" height="2" rx="1" fill="#242834"/>
<rect x="96" width="2" height="2" rx="1" fill="#242834"/>
<rect x="102" width="2" height="2" rx="1" fill="#242834"/>
<rect x="108" width="2" height="2" rx="1" fill="#242834"/>
<rect x="114" width="2" height="2" rx="1" fill="#242834"/>
<rect x="120" width="2" height="2" rx="1" fill="#242834"/>
<rect x="126" width="2" height="2" rx="1" fill="#242834"/>
<rect x="132" width="2" height="2" rx="1" fill="#242834"/>
<rect x="138" width="2" height="2" rx="1" fill="#242834"/>
<rect x="144" width="2" height="2" rx="1" fill="#242834"/>
<rect x="150" width="2" height="2" rx="1" fill="#242834"/>
<rect x="156" width="2" height="2" rx="1" fill="#242834"/>
<rect x="162" width="2" height="2" rx="1" fill="#242834"/>
<rect x="168" width="2" height="2" rx="1" fill="#242834"/>
<rect x="174" width="2" height="2" rx="1" fill="#242834"/>
<rect x="180" width="2" height="2" rx="1" fill="#242834"/>
<rect x="186" width="2" height="2" rx="1" fill="#242834"/>
<rect x="192" width="2" height="2" rx="1" fill="#242834"/>
<rect x="198" width="2" height="2" rx="1" fill="#242834"/>
<rect x="204" width="2" height="2" rx="1" fill="#242834"/>
<rect x="210" width="2" height="2" rx="1" fill="#242834"/>
<rect x="216" width="2" height="2" rx="1" fill="#242834"/>
<rect x="222" width="2" height="2" rx="1" fill="#242834"/>
<rect x="228" width="2" height="2" rx="1" fill="#242834"/>
<rect x="234" width="2" height="2" rx="1" fill="#242834"/>
<rect x="240" width="2" height="2" rx="1" fill="#242834"/>
<rect x="246" width="2" height="2" rx="1" fill="#242834"/>
<rect x="252" width="2" height="2" rx="1" fill="#242834"/>
<rect x="258" width="2" height="2" rx="1" fill="#242834"/>
<rect x="264" width="2" height="2" rx="1" fill="#242834"/>
<rect x="270" width="2" height="2" rx="1" fill="#242834"/>
<rect x="276" width="2" height="2" rx="1" fill="#242834"/>
<rect x="282" width="2" height="2" rx="1" fill="#242834"/>
<rect x="288" width="2" height="2" rx="1" fill="#242834"/>
<rect x="294" width="2" height="2" rx="1" fill="#242834"/>
<rect x="300" width="2" height="2" rx="1" fill="#242834"/>
<rect x="306" width="2" height="2" rx="1" fill="#242834"/>
<rect x="312" width="2" height="2" rx="1" fill="#242834"/>
<rect x="318" width="2" height="2" rx="1" fill="#242834"/>
<rect x="324" width="2" height="2" rx="1" fill="#242834"/>
<rect x="330" width="2" height="2" rx="1" fill="#242834"/>
<rect x="336" width="2" height="2" rx="1" fill="#242834"/>
<rect x="342" width="2" height="2" rx="1" fill="#242834"/>
<rect x="348" width="2" height="2" rx="1" fill="#242834"/>
<rect x="354" width="2" height="2" rx="1" fill="#242834"/>
<rect x="360" width="2" height="2" rx="1" fill="#242834"/>
<rect x="366" width="2" height="2" rx="1" fill="#242834"/>
<rect x="372" width="2" height="2" rx="1" fill="#242834"/>
<rect x="378" width="2" height="2" rx="1" fill="#242834"/>
<rect x="384" width="2" height="2" rx="1" fill="#242834"/>
<rect x="390" width="2" height="2" rx="1" fill="#242834"/>
<rect x="396" width="2" height="2" rx="1" fill="#242834"/>
<rect x="402" width="2" height="2" rx="1" fill="#242834"/>
<rect x="408" width="2" height="2" rx="1" fill="#242834"/>
<rect x="414" width="2" height="2" rx="1" fill="#242834"/>
<rect x="420" width="2" height="2" rx="1" fill="#242834"/>
<rect x="426" width="2" height="2" rx="1" fill="#242834"/>
<rect x="432" width="2" height="2" rx="1" fill="#242834"/>
<rect x="438" width="2" height="2" rx="1" fill="#242834"/>
<rect x="444" width="2" height="2" rx="1" fill="#242834"/>
<rect x="450" width="2" height="2" rx="1" fill="#242834"/>
<rect x="456" width="2" height="2" rx="1" fill="#242834"/>
<rect x="462" width="2" height="2" rx="1" fill="#242834"/>
<rect x="468" width="2" height="2" rx="1" fill="#242834"/>
<rect x="474" width="2" height="2" rx="1" fill="#242834"/>
<rect x="480" width="2" height="2" rx="1" fill="#242834"/>
<rect x="486" width="2" height="2" rx="1" fill="#242834"/>
<rect x="492" width="2" height="2" rx="1" fill="#242834"/>
<rect x="498" width="2" height="2" rx="1" fill="#242834"/>
<rect x="504" width="2" height="2" rx="1" fill="#242834"/>
<rect x="510" width="2" height="2" rx="1" fill="#242834"/>
<rect x="516" width="2" height="2" rx="1" fill="#242834"/>
<rect x="522" width="2" height="2" rx="1" fill="#242834"/>
<rect x="528" width="2" height="2" rx="1" fill="#242834"/>
<rect x="534" width="2" height="2" rx="1" fill="#242834"/>
<rect x="540" width="2" height="2" rx="1" fill="#242834"/>
<rect x="546" width="2" height="2" rx="1" fill="#242834"/>
<rect x="552" width="2" height="2" rx="1" fill="#242834"/>
<rect x="558" width="2" height="2" rx="1" fill="#242834"/>
<rect x="564" width="2" height="2" rx="1" fill="#242834"/>
<rect x="570" width="2" height="2" rx="1" fill="#242834"/>
<rect x="576" width="2" height="2" rx="1" fill="#242834"/>
<rect x="582" width="2" height="2" rx="1" fill="#242834"/>
<rect x="588" width="2" height="2" rx="1" fill="#242834"/>
<rect x="594" width="2" height="2" rx="1" fill="#242834"/>
<rect x="600" width="2" height="2" rx="1" fill="#242834"/>
<rect x="606" width="2" height="2" rx="1" fill="#242834"/>
<rect x="612" width="2" height="2" rx="1" fill="#242834"/>
<rect x="618" width="2" height="2" rx="1" fill="#242834"/>
<rect x="624" width="2" height="2" rx="1" fill="#242834"/>
<rect x="630" width="2" height="2" rx="1" fill="#242834"/>
<rect x="636" width="2" height="2" rx="1" fill="#242834"/>
<rect x="642" width="2" height="2" rx="1" fill="#242834"/>
<rect x="648" width="2" height="2" rx="1" fill="#242834"/>
<rect x="654" width="2" height="2" rx="1" fill="#242834"/>
<rect x="660" width="2" height="2" rx="1" fill="#242834"/>
<rect x="666" width="2" height="2" rx="1" fill="#242834"/>
<rect x="672" width="2" height="2" rx="1" fill="#242834"/>
<rect x="678" width="2" height="2" rx="1" fill="#242834"/>
<rect x="684" width="2" height="2" rx="1" fill="#242834"/>
<rect x="690" width="2" height="2" rx="1" fill="#242834"/>
<rect x="696" width="2" height="2" rx="1" fill="#242834"/>
<rect x="702" width="2" height="2" rx="1" fill="#242834"/>
<rect x="708" width="2" height="2" rx="1" fill="#242834"/>
<rect x="714" width="2" height="2" rx="1" fill="#242834"/>
<rect x="720" width="2" height="2" rx="1" fill="#242834"/>
<rect x="726" width="2" height="2" rx="1" fill="#242834"/>
<rect x="732" width="2" height="2" rx="1" fill="#242834"/>
<rect x="738" width="2" height="2" rx="1" fill="#242834"/>
<rect x="744" width="2" height="2" rx="1" fill="#242834"/>
<rect x="750" width="2" height="2" rx="1" fill="#242834"/>
<rect x="756" width="2" height="2" rx="1" fill="#242834"/>
<rect x="762" width="2" height="2" rx="1" fill="#242834"/>
<rect x="768" width="2" height="2" rx="1" fill="#242834"/>
<rect x="774" width="2" height="2" rx="1" fill="#242834"/>
<rect x="780" width="2" height="2" rx="1" fill="#242834"/>
<rect x="786" width="2" height="2" rx="1" fill="#242834"/>
<rect x="792" width="2" height="2" rx="1" fill="#242834"/>
<rect x="798" width="2" height="2" rx="1" fill="#242834"/>
<rect x="804" width="2" height="2" rx="1" fill="#242834"/>
<rect x="810" width="2" height="2" rx="1" fill="#242834"/>
<rect x="816" width="2" height="2" rx="1" fill="#242834"/>
<rect x="822" width="2" height="2" rx="1" fill="#242834"/>
<rect x="828" width="2" height="2" rx="1" fill="#242834"/>
<rect x="834" width="2" height="2" rx="1" fill="#242834"/>
<rect x="840" width="2" height="2" rx="1" fill="#242834"/>
<rect x="846" width="2" height="2" rx="1" fill="#242834"/>
<rect x="852" width="2" height="2" rx="1" fill="#242834"/>
<rect x="858" width="2" height="2" rx="1" fill="#242834"/>
<rect x="864" width="2" height="2" rx="1" fill="#242834"/>
<rect x="870" width="2" height="2" rx="1" fill="#242834"/>
<rect x="876" width="2" height="2" rx="1" fill="#242834"/>
<rect x="882" width="2" height="2" rx="1" fill="#242834"/>
<rect x="888" width="2" height="2" rx="1" fill="#242834"/>
<rect x="894" width="2" height="2" rx="1" fill="#242834"/>
<rect x="900" width="2" height="2" rx="1" fill="#242834"/>
<rect x="906" width="2" height="2" rx="1" fill="#242834"/>
<rect x="912" width="2" height="2" rx="1" fill="#242834"/>
<rect x="918" width="2" height="2" rx="1" fill="#242834"/>
<rect x="924" width="2" height="2" rx="1" fill="#242834"/>
<rect y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="6" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="12" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="18" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="24" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="30" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="36" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="42" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="48" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="54" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="60" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="66" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="72" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="78" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="84" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="90" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="96" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="102" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="108" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="114" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="120" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="126" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="132" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="138" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="144" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="150" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="156" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="162" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="168" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="174" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="180" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="186" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="192" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="198" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="204" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="210" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="216" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="222" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="228" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="234" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="240" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="246" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="252" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="258" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="264" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="270" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="276" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="282" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="288" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="294" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="300" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="306" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="312" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="318" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="324" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="330" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="336" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="342" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="348" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="354" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="360" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="366" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="372" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="378" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="384" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="390" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="396" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="402" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="408" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="414" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="420" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="426" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="432" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="438" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="444" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="450" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="456" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="462" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="468" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="474" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="480" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="486" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="492" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="498" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="504" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="510" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="516" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="522" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="528" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="534" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="540" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="546" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="552" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="558" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="564" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="570" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="576" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="582" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="588" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="594" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="600" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="606" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="612" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="618" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="624" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="630" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="636" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="642" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="648" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="654" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="660" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="666" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="672" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="678" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="684" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="690" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="696" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="702" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="708" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="714" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="720" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="726" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="732" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="738" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="744" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="750" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="756" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="762" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="768" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="774" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="780" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="786" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="792" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="798" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="804" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="810" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="816" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="822" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="828" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="834" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="840" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="846" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="852" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="858" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="864" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="870" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="876" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="882" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="888" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="894" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="900" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="906" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="912" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="918" y="6" width="2" height="2" rx="1" fill="#242834"/>
<rect x="924" y="6" width="2" height="2" rx="1" fill="#242834"/>
</svg>

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Extracts unique fenced code block language identifiers from all .md files under frontend/src/
# Usage: bash frontend/scripts/extract-md-languages.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SRC_DIR="$SCRIPT_DIR/../src"
grep -roh '```[a-zA-Z0-9_+-]*' "$SRC_DIR" --include='*.md' \
| sed 's/^```//' \
| grep -v '^$' \
| sort -u

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const axios = require('axios');
const PERMISSIONS_TYPE_FILE = path.join(
__dirname,
'../src/hooks/useAuthZ/permissions.type.ts',
);
const SIGNOZ_INTEGRATION_IMAGE = 'signoz:integration';
const LOCAL_BACKEND_URL = 'http://localhost:8080';
function log(message) {
console.log(`[generate-permissions-type] ${message}`);
}
function getBackendUrlFromDocker() {
try {
const output = execSync(
`docker ps --filter "ancestor=${SIGNOZ_INTEGRATION_IMAGE}" --format "{{.Ports}}"`,
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] },
).trim();
if (!output) {
return null;
}
const portMatch = output.match(/0\.0\.0\.0:(\d+)->8080\/tcp/);
if (portMatch) {
return `http://localhost:${portMatch[1]}`;
}
const ipv6Match = output.match(/:::(\d+)->8080\/tcp/);
if (ipv6Match) {
return `http://localhost:${ipv6Match[1]}`;
}
} catch (err) {
log(`Warning: Could not get port from docker: ${err.message}`);
}
return null;
}
async function checkBackendHealth(url, maxAttempts = 3, delayMs = 1000) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
await axios.get(`${url}/api/v1/health`, {
timeout: 5000,
validateStatus: (status) => status === 200,
});
return true;
} catch (err) {
if (attempt < maxAttempts) {
await new Promise((r) => setTimeout(r, delayMs));
}
}
}
return false;
}
async function discoverBackendUrl() {
const dockerUrl = getBackendUrlFromDocker();
if (dockerUrl) {
log(`Found ${SIGNOZ_INTEGRATION_IMAGE} container, trying ${dockerUrl}...`);
if (await checkBackendHealth(dockerUrl)) {
log(`Backend found at ${dockerUrl} (from py-test-setup)`);
return dockerUrl;
}
log(`Backend at ${dockerUrl} is not responding`);
}
log(`Trying local backend at ${LOCAL_BACKEND_URL}...`);
if (await checkBackendHealth(LOCAL_BACKEND_URL)) {
log(`Backend found at ${LOCAL_BACKEND_URL}`);
return LOCAL_BACKEND_URL;
}
return null;
}
async function fetchResources(backendUrl) {
log('Fetching resources from API...');
const resourcesUrl = `${backendUrl}/api/v1/authz/resources`;
const { data: response } = await axios.get(resourcesUrl);
return response;
}
function transformResponse(apiResponse) {
if (!apiResponse.data) {
throw new Error('Invalid API response: missing data field');
}
const { resources, relations } = apiResponse.data;
return {
status: apiResponse.status || 'success',
data: {
resources: resources,
relations: relations,
},
};
}
function generateTypeScriptFile(data) {
const resourcesStr = data.data.resources
.map(
(r) =>
`\t\t\t{\n\t\t\t\tname: '${r.name}',\n\t\t\t\ttype: '${r.type}',\n\t\t\t}`,
)
.join(',\n');
const relationsStr = Object.entries(data.data.relations)
.map(
([type, relations]) =>
`\t\t\t${type}: [${relations.map((r) => `'${r}'`).join(', ')}]`,
)
.join(',\n');
return `// AUTO GENERATED FILE - DO NOT EDIT - GENERATED BY scripts/generate-permissions-type
export default {
\tstatus: '${data.status}',
\tdata: {
\t\tresources: [
${resourcesStr}
\t\t],
\t\trelations: {
${relationsStr}
\t\t},
\t},
} as const;
`;
}
async function main() {
try {
log('Starting permissions type generation...');
const backendUrl = await discoverBackendUrl();
if (!backendUrl) {
console.error('\n' + '='.repeat(80));
console.error('ERROR: No running SigNoz backend found!');
console.error('='.repeat(80));
console.error(
'\nThe permissions type generator requires a running SigNoz backend.',
);
console.error('\nFor local development, start the backend with:');
console.error(' make go-run-enterprise');
console.error(
'\nFor CI or integration testing, start the test environment with:',
);
console.error(' make py-test-setup');
console.error(
'\nIf running in CI and seeing this error, check that the py-test-setup',
);
console.error('step completed successfully before this step runs.');
console.error('='.repeat(80) + '\n');
process.exit(1);
}
log('Fetching resources...');
const apiResponse = await fetchResources(backendUrl);
log('Transforming response...');
const transformed = transformResponse(apiResponse);
log('Generating TypeScript file...');
const content = generateTypeScriptFile(transformed);
log(`Writing to ${PERMISSIONS_TYPE_FILE}...`);
fs.writeFileSync(PERMISSIONS_TYPE_FILE, content, 'utf8');
const rootDir = path.join(__dirname, '../..');
const relativePath = path.relative(
path.join(rootDir, 'frontend'),
PERMISSIONS_TYPE_FILE,
);
log('Linting generated file...');
execSync(`cd frontend && yarn eslint --fix ${relativePath}`, {
cwd: rootDir,
stdio: 'inherit',
});
log('Successfully generated permissions.type.ts');
} catch (error) {
log(`Error: ${error.message}`);
process.exit(1);
}
}
if (require.main === module) {
main();
}
module.exports = { main };

View File

@@ -27,7 +27,7 @@ const signozPackages = Object.keys(allDeps).filter((dep) =>
const fileContent = `// -------------------------------------------------------------------------
// AUTO-GENERATED FILE
// -------------------------------------------------------------------------
// This file is generated by scripts/update-registry.js automatically
// This file is generated by scripts/update-registry.cjs automatically
// whenever you run 'yarn install' or 'npm install'.
//
// It forces VS Code to index these specific packages to fix auto-import

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Validates that all fenced code block languages used in .md files are registered
# in the syntax highlighter.
# Usage: bash frontend/scripts/validate-md-languages.sh
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SYNTAX_HIGHLIGHTER="$SCRIPT_DIR/../src/components/MarkdownRenderer/syntaxHighlighter.ts"
# Get all languages used in .md files
md_languages=$("$SCRIPT_DIR/extract-md-languages.sh")
# Get all registered languages from syntaxHighlighter.ts
registered_languages=$(grep -oP "registerLanguage\('\K[^']+" "$SYNTAX_HIGHLIGHTER" | sort -u)
missing_languages=()
for lang in $md_languages; do
if ! echo "$registered_languages" | grep -qx "$lang"; then
missing_languages+=("$lang")
fi
done
if [ ${#missing_languages[@]} -gt 0 ]; then
echo "Error: The following languages are used in .md files but not registered in syntaxHighlighter.ts:"
for lang in "${missing_languages[@]}"; do
echo " - $lang"
done
echo ""
echo "Please add them to: frontend/src/components/MarkdownRenderer/syntaxHighlighter.ts"
exit 1
fi
echo "All markdown code block languages are registered in syntaxHighlighter.ts"

View File

@@ -218,9 +218,9 @@ function App(): JSX.Element {
pathname === ROUTES.ONBOARDING ||
pathname.startsWith('/public/dashboard/')
) {
window.Pylon('hideChatBubble');
window.Pylon?.('hideChatBubble');
} else {
window.Pylon('showChatBubble');
window.Pylon?.('showChatBubble');
}
}, [pathname]);

View File

@@ -253,18 +253,12 @@ export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
);
export const Integrations = Loadable(
export const InstalledIntegrations = Loadable(
() =>
import(
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
),
);
export const IntegrationsDetailsPage = Loadable(
() =>
import(
/* webpackChunkName: "IntegrationsDetailsPage" */ 'pages/IntegrationsDetailsPage'
),
);
export const MessagingQueuesMainPage = Loadable(
() =>

View File

@@ -18,8 +18,7 @@ import {
ForgotPassword,
Home,
InfrastructureMonitoring,
Integrations,
IntegrationsDetailsPage,
InstalledIntegrations,
LicensePage,
ListAllALertsPage,
LiveLogs,
@@ -390,17 +389,10 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.INTEGRATIONS_DETAIL,
exact: true,
component: IntegrationsDetailsPage,
isPrivate: true,
key: 'INTEGRATIONS_DETAIL',
},
{
path: ROUTES.INTEGRATIONS,
exact: true,
component: Integrations,
component: InstalledIntegrations,
isPrivate: true,
key: 'INTEGRATIONS',
},

View File

@@ -2,7 +2,7 @@ import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import APIError from 'types/api/error';
// Handles errors from generated API hooks (which use RenderErrorResponseDTO)
// @deprecated Use convertToApiError instead
export function ErrorResponseHandlerForGeneratedAPIs(
error: AxiosError<RenderErrorResponseDTO>,
): never {
@@ -46,3 +46,34 @@ export function ErrorResponseHandlerForGeneratedAPIs(
},
});
}
// convertToApiError converts an AxiosError from generated API
// hooks into an APIError.
export function convertToApiError(
error: AxiosError<RenderErrorResponseDTO> | null,
): APIError | undefined {
if (!error) {
return undefined;
}
const response = error.response;
const errorData = response?.data?.error;
return new APIError({
httpStatusCode: response?.status || error.status || 500,
error: {
code:
errorData?.code ||
String(response?.status || error.code || 'unknown_error'),
message:
errorData?.message ||
response?.statusText ||
error.message ||
'Something went wrong',
url: errorData?.url ?? '',
errors: (errorData?.errors ?? []).map((e) => ({
message: e.message ?? '',
})),
},
});
}

View File

@@ -29,10 +29,6 @@ import type {
UpdateAuthDomainPathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint lists all auth domains
* @summary List all auth domains

View File

@@ -26,10 +26,6 @@ import type {
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* Checks if the authenticated user has permissions for given transactions
* @summary Check permissions

View File

@@ -35,10 +35,6 @@ import type {
UpdatePublicDashboardPathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint deletes the public sharing config and disables the public sharing of a dashboard
* @summary Delete public dashboard

View File

@@ -18,10 +18,6 @@ import type { ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type { GetFeatures200, RenderErrorResponseDTO } from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns the supported features and their details
* @summary Get features

View File

@@ -24,10 +24,6 @@ import type {
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns field keys
* @summary Get field keys

View File

@@ -37,10 +37,6 @@ import type {
UpdateIngestionKeyPathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns the ingestion keys for a workspace
* @summary Get ingestion keys for workspace

View File

@@ -21,10 +21,6 @@ import type {
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns global config
* @summary Get global config

View File

@@ -25,10 +25,6 @@ import type {
RenderErrorResponseDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoints promotes and indexes paths
* @summary Promote and index paths

View File

@@ -42,10 +42,6 @@ import type {
UpdateMetricMetadataPathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns a list of distinct metric names within the specified time range
* @summary List metric names

View File

@@ -25,10 +25,6 @@ import type {
TypesOrganizationDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint returns the organization I belong to
* @summary Get my organization

View File

@@ -32,10 +32,6 @@ import type {
UpdateUserPreferencePathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint lists all org preferences
* @summary List org preferences

View File

@@ -20,10 +20,6 @@ import type {
ReplaceVariables200,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* Execute a composite query over a time range. Supports builder queries (traces, logs, metrics), formulas, trace operators, PromQL, and ClickHouse SQL.
* @summary Query range

View File

@@ -35,10 +35,6 @@ import type {
RoletypesPostableRoleDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint lists all roles
* @summary List roles

View File

@@ -0,0 +1,969 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
CreateServiceAccount201,
CreateServiceAccountKey201,
CreateServiceAccountKeyPathParameters,
DeleteServiceAccountPathParameters,
GetServiceAccount200,
GetServiceAccountPathParameters,
ListServiceAccountKeys200,
ListServiceAccountKeysPathParameters,
ListServiceAccounts200,
RenderErrorResponseDTO,
RevokeServiceAccountKeyPathParameters,
ServiceaccounttypesPostableFactorAPIKeyDTO,
ServiceaccounttypesPostableServiceAccountDTO,
ServiceaccounttypesUpdatableFactorAPIKeyDTO,
ServiceaccounttypesUpdatableServiceAccountDTO,
ServiceaccounttypesUpdatableServiceAccountStatusDTO,
UpdateServiceAccountKeyPathParameters,
UpdateServiceAccountPathParameters,
UpdateServiceAccountStatusPathParameters,
} from '../sigNoz.schemas';
/**
* This endpoint lists the service accounts for an organisation
* @summary List service accounts
*/
export const listServiceAccounts = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListServiceAccounts200>({
url: `/api/v1/service_accounts`,
method: 'GET',
signal,
});
};
export const getListServiceAccountsQueryKey = () => {
return [`/api/v1/service_accounts`] as const;
};
export const getListServiceAccountsQueryOptions = <
TData = Awaited<ReturnType<typeof listServiceAccounts>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccounts>>,
TError,
TData
>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListServiceAccountsQueryKey();
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listServiceAccounts>>
> = ({ signal }) => listServiceAccounts(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccounts>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListServiceAccountsQueryResult = NonNullable<
Awaited<ReturnType<typeof listServiceAccounts>>
>;
export type ListServiceAccountsQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List service accounts
*/
export function useListServiceAccounts<
TData = Awaited<ReturnType<typeof listServiceAccounts>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccounts>>,
TError,
TData
>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListServiceAccountsQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List service accounts
*/
export const invalidateListServiceAccounts = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListServiceAccountsQueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint creates a service account
* @summary Create service account
*/
export const createServiceAccount = (
serviceaccounttypesPostableServiceAccountDTO: BodyType<ServiceaccounttypesPostableServiceAccountDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateServiceAccount201>({
url: `/api/v1/service_accounts`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesPostableServiceAccountDTO,
signal,
});
};
export const getCreateServiceAccountMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccount>>,
TError,
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccount>>,
TError,
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
TContext
> => {
const mutationKey = ['createServiceAccount'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createServiceAccount>>,
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> }
> = (props) => {
const { data } = props ?? {};
return createServiceAccount(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateServiceAccountMutationResult = NonNullable<
Awaited<ReturnType<typeof createServiceAccount>>
>;
export type CreateServiceAccountMutationBody = BodyType<ServiceaccounttypesPostableServiceAccountDTO>;
export type CreateServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create service account
*/
export const useCreateServiceAccount = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccount>>,
TError,
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createServiceAccount>>,
TError,
{ data: BodyType<ServiceaccounttypesPostableServiceAccountDTO> },
TContext
> => {
const mutationOptions = getCreateServiceAccountMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes an existing service account
* @summary Deletes a service account
*/
export const deleteServiceAccount = ({
id,
}: DeleteServiceAccountPathParameters) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/service_accounts/${id}`,
method: 'DELETE',
});
};
export const getDeleteServiceAccountMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccount>>,
TError,
{ pathParams: DeleteServiceAccountPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccount>>,
TError,
{ pathParams: DeleteServiceAccountPathParameters },
TContext
> => {
const mutationKey = ['deleteServiceAccount'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteServiceAccount>>,
{ pathParams: DeleteServiceAccountPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteServiceAccount(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteServiceAccountMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteServiceAccount>>
>;
export type DeleteServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Deletes a service account
*/
export const useDeleteServiceAccount = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteServiceAccount>>,
TError,
{ pathParams: DeleteServiceAccountPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteServiceAccount>>,
TError,
{ pathParams: DeleteServiceAccountPathParameters },
TContext
> => {
const mutationOptions = getDeleteServiceAccountMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint gets an existing service account
* @summary Gets a service account
*/
export const getServiceAccount = (
{ id }: GetServiceAccountPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetServiceAccount200>({
url: `/api/v1/service_accounts/${id}`,
method: 'GET',
signal,
});
};
export const getGetServiceAccountQueryKey = ({
id,
}: GetServiceAccountPathParameters) => {
return [`/api/v1/service_accounts/${id}`] as const;
};
export const getGetServiceAccountQueryOptions = <
TData = Awaited<ReturnType<typeof getServiceAccount>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetServiceAccountPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccount>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetServiceAccountQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getServiceAccount>>
> = ({ signal }) => getServiceAccount({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccount>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetServiceAccountQueryResult = NonNullable<
Awaited<ReturnType<typeof getServiceAccount>>
>;
export type GetServiceAccountQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Gets a service account
*/
export function useGetServiceAccount<
TData = Awaited<ReturnType<typeof getServiceAccount>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetServiceAccountPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getServiceAccount>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetServiceAccountQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Gets a service account
*/
export const invalidateGetServiceAccount = async (
queryClient: QueryClient,
{ id }: GetServiceAccountPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetServiceAccountQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint updates an existing service account
* @summary Updates a service account
*/
export const updateServiceAccount = (
{ id }: UpdateServiceAccountPathParameters,
serviceaccounttypesUpdatableServiceAccountDTO: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/service_accounts/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesUpdatableServiceAccountDTO,
});
};
export const getUpdateServiceAccountMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccount>>,
TError,
{
pathParams: UpdateServiceAccountPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccount>>,
TError,
{
pathParams: UpdateServiceAccountPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
},
TContext
> => {
const mutationKey = ['updateServiceAccount'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateServiceAccount>>,
{
pathParams: UpdateServiceAccountPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateServiceAccount(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateServiceAccountMutationResult = NonNullable<
Awaited<ReturnType<typeof updateServiceAccount>>
>;
export type UpdateServiceAccountMutationBody = BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
export type UpdateServiceAccountMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Updates a service account
*/
export const useUpdateServiceAccount = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccount>>,
TError,
{
pathParams: UpdateServiceAccountPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateServiceAccount>>,
TError,
{
pathParams: UpdateServiceAccountPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountDTO>;
},
TContext
> => {
const mutationOptions = getUpdateServiceAccountMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint lists the service account keys
* @summary List service account keys
*/
export const listServiceAccountKeys = (
{ id }: ListServiceAccountKeysPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListServiceAccountKeys200>({
url: `/api/v1/service_accounts/${id}/keys`,
method: 'GET',
signal,
});
};
export const getListServiceAccountKeysQueryKey = ({
id,
}: ListServiceAccountKeysPathParameters) => {
return [`/api/v1/service_accounts/${id}/keys`] as const;
};
export const getListServiceAccountKeysQueryOptions = <
TData = Awaited<ReturnType<typeof listServiceAccountKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: ListServiceAccountKeysPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccountKeys>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListServiceAccountKeysQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listServiceAccountKeys>>
> = ({ signal }) => listServiceAccountKeys({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccountKeys>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListServiceAccountKeysQueryResult = NonNullable<
Awaited<ReturnType<typeof listServiceAccountKeys>>
>;
export type ListServiceAccountKeysQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List service account keys
*/
export function useListServiceAccountKeys<
TData = Awaited<ReturnType<typeof listServiceAccountKeys>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: ListServiceAccountKeysPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listServiceAccountKeys>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListServiceAccountKeysQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List service account keys
*/
export const invalidateListServiceAccountKeys = async (
queryClient: QueryClient,
{ id }: ListServiceAccountKeysPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListServiceAccountKeysQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint creates a service account key
* @summary Create a service account key
*/
export const createServiceAccountKey = (
{ id }: CreateServiceAccountKeyPathParameters,
serviceaccounttypesPostableFactorAPIKeyDTO: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateServiceAccountKey201>({
url: `/api/v1/service_accounts/${id}/keys`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesPostableFactorAPIKeyDTO,
signal,
});
};
export const getCreateServiceAccountKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountKey>>,
TError,
{
pathParams: CreateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountKey>>,
TError,
{
pathParams: CreateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
},
TContext
> => {
const mutationKey = ['createServiceAccountKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createServiceAccountKey>>,
{
pathParams: CreateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return createServiceAccountKey(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateServiceAccountKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof createServiceAccountKey>>
>;
export type CreateServiceAccountKeyMutationBody = BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
export type CreateServiceAccountKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create a service account key
*/
export const useCreateServiceAccountKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createServiceAccountKey>>,
TError,
{
pathParams: CreateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createServiceAccountKey>>,
TError,
{
pathParams: CreateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesPostableFactorAPIKeyDTO>;
},
TContext
> => {
const mutationOptions = getCreateServiceAccountKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint revokes an existing service account key
* @summary Revoke a service account key
*/
export const revokeServiceAccountKey = ({
id,
fid,
}: RevokeServiceAccountKeyPathParameters) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/service_accounts/${id}/keys/${fid}`,
method: 'DELETE',
});
};
export const getRevokeServiceAccountKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof revokeServiceAccountKey>>,
TError,
{ pathParams: RevokeServiceAccountKeyPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof revokeServiceAccountKey>>,
TError,
{ pathParams: RevokeServiceAccountKeyPathParameters },
TContext
> => {
const mutationKey = ['revokeServiceAccountKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof revokeServiceAccountKey>>,
{ pathParams: RevokeServiceAccountKeyPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return revokeServiceAccountKey(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type RevokeServiceAccountKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof revokeServiceAccountKey>>
>;
export type RevokeServiceAccountKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Revoke a service account key
*/
export const useRevokeServiceAccountKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof revokeServiceAccountKey>>,
TError,
{ pathParams: RevokeServiceAccountKeyPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof revokeServiceAccountKey>>,
TError,
{ pathParams: RevokeServiceAccountKeyPathParameters },
TContext
> => {
const mutationOptions = getRevokeServiceAccountKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint updates an existing service account key
* @summary Updates a service account key
*/
export const updateServiceAccountKey = (
{ id, fid }: UpdateServiceAccountKeyPathParameters,
serviceaccounttypesUpdatableFactorAPIKeyDTO: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/service_accounts/${id}/keys/${fid}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesUpdatableFactorAPIKeyDTO,
});
};
export const getUpdateServiceAccountKeyMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountKey>>,
TError,
{
pathParams: UpdateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountKey>>,
TError,
{
pathParams: UpdateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
},
TContext
> => {
const mutationKey = ['updateServiceAccountKey'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateServiceAccountKey>>,
{
pathParams: UpdateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateServiceAccountKey(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateServiceAccountKeyMutationResult = NonNullable<
Awaited<ReturnType<typeof updateServiceAccountKey>>
>;
export type UpdateServiceAccountKeyMutationBody = BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
export type UpdateServiceAccountKeyMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Updates a service account key
*/
export const useUpdateServiceAccountKey = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountKey>>,
TError,
{
pathParams: UpdateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateServiceAccountKey>>,
TError,
{
pathParams: UpdateServiceAccountKeyPathParameters;
data: BodyType<ServiceaccounttypesUpdatableFactorAPIKeyDTO>;
},
TContext
> => {
const mutationOptions = getUpdateServiceAccountKeyMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint updates an existing service account status
* @summary Updates a service account status
*/
export const updateServiceAccountStatus = (
{ id }: UpdateServiceAccountStatusPathParameters,
serviceaccounttypesUpdatableServiceAccountStatusDTO: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/service_accounts/${id}/status`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: serviceaccounttypesUpdatableServiceAccountStatusDTO,
});
};
export const getUpdateServiceAccountStatusMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
TError,
{
pathParams: UpdateServiceAccountStatusPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
TError,
{
pathParams: UpdateServiceAccountStatusPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
},
TContext
> => {
const mutationKey = ['updateServiceAccountStatus'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
{
pathParams: UpdateServiceAccountStatusPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateServiceAccountStatus(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateServiceAccountStatusMutationResult = NonNullable<
Awaited<ReturnType<typeof updateServiceAccountStatus>>
>;
export type UpdateServiceAccountStatusMutationBody = BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
export type UpdateServiceAccountStatusMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Updates a service account status
*/
export const useUpdateServiceAccountStatus = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
TError,
{
pathParams: UpdateServiceAccountStatusPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateServiceAccountStatus>>,
TError,
{
pathParams: UpdateServiceAccountStatusPathParameters;
data: BodyType<ServiceaccounttypesUpdatableServiceAccountStatusDTO>;
},
TContext
> => {
const mutationOptions = getUpdateServiceAccountStatusMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -33,10 +33,6 @@ import type {
RotateSession200,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint creates a session for a user using google callback
* @summary Create session by google callback

View File

@@ -2090,6 +2090,154 @@ export interface RoletypesRoleDTO {
updatedAt?: Date;
}
export interface ServiceaccounttypesFactorAPIKeyDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type integer
* @minimum 0
*/
expires_at: number;
/**
* @type string
*/
id: string;
/**
* @type string
*/
key: string;
/**
* @type string
* @format date-time
*/
last_used: Date;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
service_account_id: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
}
export interface ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO {
/**
* @type string
*/
id: string;
/**
* @type string
*/
key: string;
}
export interface ServiceaccounttypesPostableFactorAPIKeyDTO {
/**
* @type integer
* @minimum 0
*/
expires_at: number;
/**
* @type string
*/
name: string;
}
export interface ServiceaccounttypesPostableServiceAccountDTO {
/**
* @type string
*/
email: string;
/**
* @type string
*/
name: string;
/**
* @type array
*/
roles: string[];
}
export interface ServiceaccounttypesServiceAccountDTO {
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
email: string;
/**
* @type string
*/
id: string;
/**
* @type string
*/
name: string;
/**
* @type string
*/
orgID: string;
/**
* @type array
*/
roles: string[];
/**
* @type string
*/
status: string;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
}
export interface ServiceaccounttypesUpdatableFactorAPIKeyDTO {
/**
* @type integer
* @minimum 0
*/
expires_at: number;
/**
* @type string
*/
name: string;
}
export interface ServiceaccounttypesUpdatableServiceAccountDTO {
/**
* @type string
*/
email: string;
/**
* @type string
*/
name: string;
/**
* @type array
*/
roles: string[];
}
export interface ServiceaccounttypesUpdatableServiceAccountStatusDTO {
/**
* @type string
*/
status: string;
}
export enum TelemetrytypesFieldContextDTO {
metric = 'metric',
log = 'log',
@@ -3050,6 +3198,78 @@ export type PatchObjectsPathParameters = {
id: string;
relation: string;
};
export type ListServiceAccounts200 = {
/**
* @type array
*/
data: ServiceaccounttypesServiceAccountDTO[];
/**
* @type string
*/
status: string;
};
export type CreateServiceAccount201 = {
data: TypesIdentifiableDTO;
/**
* @type string
*/
status: string;
};
export type DeleteServiceAccountPathParameters = {
id: string;
};
export type GetServiceAccountPathParameters = {
id: string;
};
export type GetServiceAccount200 = {
data: ServiceaccounttypesServiceAccountDTO;
/**
* @type string
*/
status: string;
};
export type UpdateServiceAccountPathParameters = {
id: string;
};
export type ListServiceAccountKeysPathParameters = {
id: string;
};
export type ListServiceAccountKeys200 = {
/**
* @type array
*/
data: ServiceaccounttypesFactorAPIKeyDTO[];
/**
* @type string
*/
status: string;
};
export type CreateServiceAccountKeyPathParameters = {
id: string;
};
export type CreateServiceAccountKey201 = {
data: ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO;
/**
* @type string
*/
status: string;
};
export type RevokeServiceAccountKeyPathParameters = {
id: string;
fid: string;
};
export type UpdateServiceAccountKeyPathParameters = {
id: string;
fid: string;
};
export type UpdateServiceAccountStatusPathParameters = {
id: string;
};
export type ListUsers200 = {
/**
* @type array
@@ -3231,6 +3451,11 @@ export type ListMetricsParams = {
* @description undefined
*/
searchText?: string;
/**
* @type string
* @description undefined
*/
source?: string;
};
export type ListMetrics200 = {

View File

@@ -51,10 +51,6 @@ import type {
UpdateUserPathParameters,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint changes the password by id
* @summary Change password

View File

@@ -26,10 +26,6 @@ import type {
ZeustypesPostableProfileDTO,
} from '../sigNoz.schemas';
type AwaitedInput<T> = PromiseLike<T> | T;
type Awaited<O> = O extends AwaitedInput<infer T> ? T : never;
/**
* This endpoint gets the host info from zeus.
* @summary Get host info from Zeus.

View File

@@ -5,13 +5,13 @@ import {
ServiceData,
UpdateServiceConfigPayload,
UpdateServiceConfigResponse,
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
} from 'container/CloudIntegrationPage/ServicesSection/types';
import {
AccountConfigPayload,
AccountConfigResponse,
AWSAccountConfigPayload,
ConnectionParams,
ConnectionUrlResponse,
} from 'types/api/integrations/aws';
import { ConnectionParams } from 'types/api/integrations/types';
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
const response = await axios.get('/cloud-integrations/aws/accounts');
@@ -60,7 +60,7 @@ export const generateConnectionUrl = async (params: {
export const updateAccountConfig = async (
accountId: string,
payload: AWSAccountConfigPayload,
payload: AccountConfigPayload,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/aws/accounts/${accountId}/config`,

View File

@@ -1,122 +0,0 @@
import axios from 'api';
import {
CloudAccount,
ServiceData,
} from 'container/Integrations/CloudIntegration/AmazonWebServices/types';
import {
AzureCloudAccountConfig,
AzureService,
AzureServiceConfigPayload,
} from 'container/Integrations/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
AccountConfigResponse,
AWSAccountConfigPayload,
} from 'types/api/integrations/aws';
import {
AzureAccountConfig,
ConnectionParams,
IAzureDeploymentCommands,
} from 'types/api/integrations/types';
export const getCloudIntegrationAccounts = async (
cloudServiceId: string,
): Promise<CloudAccount[]> => {
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/accounts`,
);
return response.data.data.accounts;
};
export const getCloudIntegrationServices = async (
cloudServiceId: string,
cloudAccountId?: string,
): Promise<AzureService[]> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/services`,
{
params,
},
);
return response.data.data.services;
};
export const getCloudIntegrationServiceDetails = async (
cloudServiceId: string,
serviceId: string,
cloudAccountId?: string,
): Promise<ServiceData> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/services/${serviceId}`,
{ params },
);
return response.data.data;
};
export const updateAccountConfig = async (
cloudServiceId: string,
accountId: string,
payload: AWSAccountConfigPayload | AzureAccountConfig,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/config`,
payload,
);
return response.data;
};
export const updateServiceConfig = async (
cloudServiceId: string,
serviceId: string,
payload: AzureServiceConfigPayload,
): Promise<AzureServiceConfigPayload> => {
const response = await axios.post<AzureServiceConfigPayload>(
`/cloud-integrations/${cloudServiceId}/services/${serviceId}/config`,
payload,
);
return response.data;
};
export const getConnectionParams = async (
cloudServiceId: string,
): Promise<ConnectionParams> => {
const response = await axios.get(
`/cloud-integrations/${cloudServiceId}/accounts/generate-connection-params`,
);
return response.data.data;
};
export const getAzureDeploymentCommands = async (params: {
agent_config: ConnectionParams;
account_config: AzureCloudAccountConfig;
}): Promise<IAzureDeploymentCommands> => {
const response = await axios.post(
`/cloud-integrations/azure/accounts/generate-connection-url`,
params,
);
return response.data.data;
};
export const removeIntegrationAccount = async ({
cloudServiceId,
accountId,
}: {
cloudServiceId: string;
accountId: string;
}): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
const response = await axios.post(
`/cloud-integrations/${cloudServiceId}/accounts/${accountId}/disconnect`,
);
return response.data;
};

View File

@@ -1,54 +0,0 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { TreemapViewType } from 'container/MetricsExplorer/Summary/types';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
export interface MetricsTreeMapPayload {
filters: TagFilter;
limit?: number;
treemap?: TreemapViewType;
}
export interface MetricsTreeMapResponse {
status: string;
data: {
[TreemapViewType.TIMESERIES]: TimeseriesData[];
[TreemapViewType.SAMPLES]: SamplesData[];
};
}
export interface TimeseriesData {
percentage: number;
total_value: number;
metric_name: string;
}
export interface SamplesData {
percentage: number;
metric_name: string;
}
export const getMetricsTreeMap = async (
props: MetricsTreeMapPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
): Promise<SuccessResponse<MetricsTreeMapResponse> | ErrorResponse> => {
try {
const response = await axios.post('/metrics/treemap', props, {
signal,
headers,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data,
params: props,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -1,36 +0,0 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Temporality } from './getMetricDetails';
import { MetricType } from './getMetricsList';
export interface UpdateMetricMetadataProps {
description: string;
metricType: MetricType;
temporality?: Temporality;
isMonotonic?: boolean;
unit?: string;
}
export interface UpdateMetricMetadataResponse {
success: boolean;
message: string;
}
const updateMetricMetadata = async (
metricName: string,
props: UpdateMetricMetadataProps,
): Promise<SuccessResponse<UpdateMetricMetadataResponse> | ErrorResponse> => {
const response = await axios.post(`/metrics/${metricName}/metadata`, {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default updateMetricMetadata;

View File

@@ -1,7 +1,7 @@
// -------------------------------------------------------------------------
// AUTO-GENERATED FILE
// -------------------------------------------------------------------------
// This file is generated by scripts/update-registry.js automatically
// This file is generated by scripts/update-registry.cjs automatically
// whenever you run 'yarn install' or 'npm install'.
//
// It forces VS Code to index these specific packages to fix auto-import
@@ -18,8 +18,6 @@ import '@signozhq/checkbox';
import '@signozhq/combobox';
import '@signozhq/command';
import '@signozhq/design-tokens';
import '@signozhq/dialog';
import '@signozhq/drawer';
import '@signozhq/icons';
import '@signozhq/input';
import '@signozhq/popover';
@@ -28,5 +26,4 @@ import '@signozhq/resizable';
import '@signozhq/sonner';
import '@signozhq/switch';
import '@signozhq/table';
import '@signozhq/tabs';
import '@signozhq/tooltip';

View File

@@ -1,109 +0,0 @@
.cloud-integration-accounts {
padding: 0px 16px;
display: flex;
flex-direction: column;
gap: 16px;
.selected-cloud-integration-account-section {
display: flex;
flex-direction: column;
gap: 16px;
.selected-cloud-integration-account-section-header {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
border-radius: 4px;
border: 1px solid var(--l3-background);
background: var(--l1-background);
.selected-cloud-integration-account-section-header-title {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
.selected-cloud-integration-account-status {
display: flex;
border-right: 1px solid var(--l3-background);
border-radius: none;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
line-height: 32px;
}
.selected-cloud-integration-account-section-header-title-text {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
.azure-cloud-account-selector {
.ant-select {
background-color: transparent !important;
border: none !important;
box-shadow: none !important;
.ant-select-selector {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
}
}
}
}
.selected-cloud-integration-account-settings {
display: flex;
flex-direction: row;
gap: 16px;
line-height: 32px;
margin-right: 8px;
cursor: pointer;
}
}
.account-settings-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.add-new-cloud-integration-account-button {
display: flex;
flex-direction: row;
gap: 16px;
line-height: 32px;
margin-right: 8px;
cursor: pointer;
}
}
}
.cloud-integration-accounts-drawer-content {
height: 100%;
max-height: calc(100vh - 120px); // Account for drawer header and padding
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
&::-webkit-scrollbar {
width: 0.2rem;
}
.edit-account-content,
.add-new-account-content {
flex: 1;
min-height: 0; // Allows flex children to shrink below content size
}
}

View File

@@ -1,165 +0,0 @@
import { useState } from 'react';
import { Button } from '@signozhq/button';
import { Color } from '@signozhq/design-tokens';
import { DrawerWrapper } from '@signozhq/drawer';
import { Select } from 'antd';
import ConnectNewAzureAccount from 'container/Integrations/CloudIntegration/AzureServices/AzureAccount/ConnectNewAzureAccount';
import EditAzureAccount from 'container/Integrations/CloudIntegration/AzureServices/AzureAccount/EditAzureAccount';
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
import { CloudAccount } from 'container/Integrations/types';
import { useGetConnectionParams } from 'hooks/integration/useGetConnectionParams';
import useAxiosError from 'hooks/useAxiosError';
import { Dot, PencilLine, Plus } from 'lucide-react';
import './CloudIntegrationAccounts.styles.scss';
export type DrawerMode = 'edit' | 'add';
export default function CloudIntegrationAccounts({
selectedAccount,
accounts,
isLoadingAccounts,
onSelectAccount,
refetchAccounts,
}: {
selectedAccount: CloudAccount | null;
accounts: CloudAccount[];
isLoadingAccounts: boolean;
onSelectAccount: (account: CloudAccount) => void;
refetchAccounts: () => void;
}): JSX.Element {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
const [mode, setMode] = useState<DrawerMode>('add');
const handleDrawerOpenChange = (open: boolean): void => {
setIsDrawerOpen(open);
};
const handleEditAccount = (): void => {
setMode('edit');
setIsDrawerOpen(true);
};
const handleAddNewAccount = (): void => {
setMode('add');
setIsDrawerOpen(true);
};
const handleError = useAxiosError();
const {
data: connectionParams,
isLoading: isConnectionParamsLoading,
} = useGetConnectionParams({
cloudServiceId: INTEGRATION_TYPES.AZURE,
options: { onError: handleError },
});
const handleSelectAccount = (value: string): void => {
const account = accounts.find(
(account) => account.cloud_account_id === value,
);
if (account) {
onSelectAccount(account);
}
};
const handleAccountConnected = (): void => {
refetchAccounts();
};
const handleAccountUpdated = (): void => {
refetchAccounts();
};
const renderDrawerContent = (): JSX.Element => {
return (
<div className="cloud-integration-accounts-drawer-content">
{mode === 'edit' ? (
<div className="edit-account-content">
<EditAzureAccount
selectedAccount={selectedAccount as CloudAccount}
connectionParams={connectionParams || {}}
isConnectionParamsLoading={isConnectionParamsLoading}
onAccountUpdated={handleAccountUpdated}
/>
</div>
) : (
<div className="add-new-account-content">
<ConnectNewAzureAccount
connectionParams={connectionParams || {}}
isConnectionParamsLoading={isConnectionParamsLoading}
onAccountConnected={handleAccountConnected}
/>
</div>
)}
</div>
);
};
return (
<div className="cloud-integration-accounts">
{selectedAccount && (
<div className="selected-cloud-integration-account-section">
<div className="selected-cloud-integration-account-section-header">
<div className="selected-cloud-integration-account-section-header-title">
<div className="selected-cloud-integration-account-status">
<Dot size={24} color={Color.BG_FOREST_500} />
</div>
<div className="selected-cloud-integration-account-section-header-title-text">
Subscription ID :
<span className="azure-cloud-account-selector">
<Select
value={selectedAccount?.cloud_account_id}
options={accounts.map((account) => ({
label: account.cloud_account_id,
value: account.cloud_account_id,
}))}
onChange={handleSelectAccount}
loading={isLoadingAccounts}
placeholder="Select Account"
/>
</span>
</div>
</div>
<div className="selected-cloud-integration-account-settings">
<Button
variant="link"
color="secondary"
prefixIcon={<PencilLine size={14} />}
onClick={handleEditAccount}
>
Edit Account
</Button>
<Button
variant="link"
color="secondary"
prefixIcon={<Plus size={14} />}
onClick={handleAddNewAccount}
>
Add New Account
</Button>
</div>
</div>
</div>
)}
<div className="account-settings-container">
<DrawerWrapper
open={isDrawerOpen}
onOpenChange={handleDrawerOpenChange}
type="panel"
header={{
title: mode === 'add' ? 'Connect with Azure' : 'Edit Azure Account',
}}
content={renderDrawerContent()}
showCloseButton
allowOutsideClick={mode === 'edit'}
direction="right"
/>
</div>
</div>
);
}

View File

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

View File

@@ -1,50 +0,0 @@
.cloud-integrations-header-section {
display: flex;
flex-direction: column;
gap: 16px;
border-bottom: 1px solid var(--l3-background);
.cloud-integrations-header {
display: flex;
flex-direction: row;
align-items: center;
padding: 16px;
padding-bottom: 0px;
gap: 16px;
.cloud-integrations-title-section {
display: flex;
flex-direction: column;
gap: 4px;
.cloud-integrations-title {
display: flex;
align-items: center;
gap: 16px;
.cloud-integrations-icon {
width: 40px;
height: 40px;
}
color: var(--l1-foreground);
font-family: Inter;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 32px; /* 200% */
letter-spacing: -0.08px;
}
.cloud-integrations-description {
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
}
}

View File

@@ -1,61 +0,0 @@
import {
AWS_INTEGRATION,
AZURE_INTEGRATION,
} from 'container/Integrations/constants';
import { CloudAccount, IntegrationType } from 'container/Integrations/types';
import CloudIntegrationAccounts from '../CloudIntegrationAccounts';
import './CloudIntegrationsHeader.styles.scss';
export default function CloudIntegrationsHeader({
cloudServiceId,
selectedAccount,
accounts,
isLoadingAccounts,
onSelectAccount,
refetchAccounts,
}: {
selectedAccount: CloudAccount | null;
accounts: CloudAccount[] | [];
isLoadingAccounts: boolean;
onSelectAccount: (account: CloudAccount) => void;
cloudServiceId: IntegrationType;
refetchAccounts: () => void;
}): JSX.Element {
const INTEGRATION_DATA =
cloudServiceId === IntegrationType.AWS_SERVICES
? AWS_INTEGRATION
: AZURE_INTEGRATION;
return (
<div className="cloud-integrations-header-section">
<div className="cloud-integrations-header">
<div className="cloud-integrations-title-section">
<div className="cloud-integrations-title">
<img
className="cloud-integrations-icon"
src={INTEGRATION_DATA.icon}
alt={INTEGRATION_DATA.icon_alt}
/>
{INTEGRATION_DATA.title}
</div>
<div className="cloud-integrations-description">
{INTEGRATION_DATA.description}
</div>
</div>
</div>
<div className="cloud-integrations-accounts-list">
<CloudIntegrationAccounts
selectedAccount={selectedAccount}
accounts={accounts}
isLoadingAccounts={isLoadingAccounts}
onSelectAccount={onSelectAccount}
refetchAccounts={refetchAccounts}
/>
</div>
</div>
);
}

View File

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

View File

@@ -1,34 +0,0 @@
.cloud-service-data-collected {
display: flex;
flex-direction: column;
gap: 16px;
.cloud-service-data-collected-table {
display: flex;
flex-direction: column;
gap: 8px;
.cloud-service-data-collected-table-heading {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
color: var(--l2-foreground);
/* Bifrost (Ancient)/Content/sm */
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.cloud-service-data-collected-table-logs {
border-radius: 6px;
border: 1px solid var(--l3-background);
background: var(--l1-background);
}
}
}

View File

@@ -1,39 +0,0 @@
.code-block-container {
position: relative;
border-radius: 4px;
overflow: hidden;
.code-block-copy-btn {
position: absolute;
top: 50%;
right: 8px;
transform: translateY(-50%);
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
padding: 6px 8px;
color: var(--bg-vanilla-100);
transition: color 0.15s ease;
&.copied {
background-color: var(--bg-robin-500);
}
}
// CodeMirror wrapper
.code-block-editor {
border-radius: 4px;
.cm-editor {
border-radius: 4px;
font-size: 13px;
line-height: 1.5;
font-family: 'Space Mono', monospace;
}
.cm-scroller {
font-family: 'Space Mono', monospace;
}
}
}

View File

@@ -1,145 +0,0 @@
import { useCallback, useMemo, useState } from 'react';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import { javascript } from '@codemirror/lang-javascript';
import { Button } from '@signozhq/button';
import { dracula } from '@uiw/codemirror-theme-dracula';
import { githubLight } from '@uiw/codemirror-theme-github';
import CodeMirror, {
EditorState,
EditorView,
Extension,
} from '@uiw/react-codemirror';
import cx from 'classnames';
import { useIsDarkMode } from 'hooks/useDarkMode';
import './CodeBlock.styles.scss';
export type CodeBlockLanguage =
| 'javascript'
| 'typescript'
| 'js'
| 'ts'
| 'json'
| 'bash'
| 'shell'
| 'text';
export type CodeBlockTheme = 'light' | 'dark' | 'auto';
interface CodeBlockProps {
/** The code content to display */
value: string;
/** Language for syntax highlighting */
language?: CodeBlockLanguage;
/** Theme: 'light' | 'dark' | 'auto' (follows app dark mode when 'auto') */
theme?: CodeBlockTheme;
/** Show line numbers */
lineNumbers?: boolean;
/** Show copy button */
showCopyButton?: boolean;
/** Custom class name for the container */
className?: string;
/** Max height in pixels - enables scrolling when content exceeds */
maxHeight?: number | string;
/** Callback when copy is clicked */
onCopy?: (copiedText: string) => void;
}
const LANGUAGE_EXTENSION_MAP: Record<
CodeBlockLanguage,
ReturnType<typeof javascript> | undefined
> = {
javascript: javascript({ jsx: true }),
typescript: javascript({ jsx: true }),
js: javascript({ jsx: true }),
ts: javascript({ jsx: true }),
json: javascript(), // JSON is valid JS; proper json() would require @codemirror/lang-json
bash: undefined,
shell: undefined,
text: undefined,
};
function CodeBlock({
value,
language = 'text',
theme: themeProp = 'auto',
lineNumbers = true,
showCopyButton = true,
className,
maxHeight,
onCopy,
}: CodeBlockProps): JSX.Element {
const isDarkMode = useIsDarkMode();
const [isCopied, setIsCopied] = useState(false);
const resolvedDark = themeProp === 'auto' ? isDarkMode : themeProp === 'dark';
const theme = resolvedDark ? dracula : githubLight;
const extensions = useMemo((): Extension[] => {
const langExtension = LANGUAGE_EXTENSION_MAP[language];
return [
EditorState.readOnly.of(true),
EditorView.editable.of(false),
EditorView.lineWrapping,
...(langExtension ? [langExtension] : []),
];
}, [language]);
const handleCopy = useCallback((): void => {
navigator.clipboard.writeText(value).then(() => {
setIsCopied(true);
onCopy?.(value);
setTimeout(() => setIsCopied(false), 2000);
});
}, [value, onCopy]);
return (
<div className={cx('code-block-container', className)}>
{showCopyButton && (
<Button
variant="solid"
size="xs"
color="secondary"
className={cx('code-block-copy-btn', { copied: isCopied })}
onClick={handleCopy}
aria-label={isCopied ? 'Copied' : 'Copy code'}
title={isCopied ? 'Copied' : 'Copy code'}
>
{isCopied ? <CheckOutlined /> : <CopyOutlined />}
</Button>
)}
<CodeMirror
className="code-block-editor"
value={value}
theme={theme}
readOnly
editable={false}
extensions={extensions}
basicSetup={{
lineNumbers,
highlightActiveLineGutter: false,
highlightActiveLine: false,
highlightSelectionMatches: true,
drawSelection: true,
syntaxHighlighting: true,
bracketMatching: true,
history: false,
foldGutter: false,
autocompletion: false,
defaultKeymap: false,
searchKeymap: true,
historyKeymap: false,
foldKeymap: false,
completionKeymap: false,
closeBrackets: false,
indentOnInput: false,
}}
style={{
maxHeight: maxHeight ?? 'auto',
}}
/>
</div>
);
}
export default CodeBlock;

View File

@@ -1,2 +0,0 @@
export type { CodeBlockLanguage, CodeBlockTheme } from './CodeBlock';
export { default as CodeBlock } from './CodeBlock';

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`DraggableTableRow Snapshot test should render DraggableTableRow 1`] = `
<DocumentFragment>

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Editor renders correctly with custom props 1`] = `
<div>

View File

@@ -0,0 +1,321 @@
import { ReactElement } from 'react';
import {
AuthtypesGettableTransactionDTO,
AuthtypesTransactionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { ENVIRONMENT } from 'constants/env';
import { BrandedPermission } from 'hooks/useAuthZ/types';
import { buildPermission } from 'hooks/useAuthZ/utils';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { render, screen, waitFor } from 'tests/test-utils';
import { GuardAuthZ } from './GuardAuthZ';
const BASE_URL = ENVIRONMENT.baseURL || '';
const AUTHZ_CHECK_URL = `${BASE_URL}/api/v1/authz/check`;
function authzMockResponse(
payload: AuthtypesTransactionDTO[],
authorizedByIndex: boolean[],
): { data: AuthtypesGettableTransactionDTO[]; status: string } {
return {
data: payload.map((txn, i) => ({
relation: txn.relation,
object: txn.object,
authorized: authorizedByIndex[i] ?? false,
})),
status: 'success',
};
}
describe('GuardAuthZ', () => {
const TestChild = (): ReactElement => <div>Protected Content</div>;
const LoadingFallback = (): ReactElement => <div>Loading...</div>;
const ErrorFallback = (error: Error): ReactElement => (
<div>Error occurred: {error.message}</div>
);
const NoPermissionFallback = (_response: {
requiredPermissionName: BrandedPermission;
}): ReactElement => <div>Access denied</div>;
const NoPermissionFallbackWithSuggestions = (response: {
requiredPermissionName: BrandedPermission;
}): ReactElement => (
<div>
Access denied. Required permission: {response.requiredPermissionName}
</div>
);
it('should render children when permission is granted', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
render(
<GuardAuthZ relation="read" object="dashboard:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText('Protected Content')).toBeInTheDocument();
});
});
it('should render fallbackOnLoading when loading', () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (_req, res, ctx) => {
return res(
ctx.delay('infinite'),
ctx.status(200),
ctx.json({ data: [], status: 'success' }),
);
}),
);
render(
<GuardAuthZ
relation="read"
object="dashboard:*"
fallbackOnLoading={<LoadingFallback />}
>
<TestChild />
</GuardAuthZ>,
);
expect(screen.getByText('Loading...')).toBeInTheDocument();
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render null when loading and no fallbackOnLoading provided', () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (_req, res, ctx) => {
return res(
ctx.delay('infinite'),
ctx.status(200),
ctx.json({ data: [], status: 'success' }),
);
}),
);
const { container } = render(
<GuardAuthZ relation="read" object="dashboard:*">
<TestChild />
</GuardAuthZ>,
);
expect(container.firstChild).toBeNull();
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render fallbackOnError when API error occurs', async () => {
const errorMessage = 'Internal Server Error';
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: errorMessage }));
}),
);
render(
<GuardAuthZ
relation="read"
object="dashboard:*"
fallbackOnError={ErrorFallback}
>
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText(/Error occurred:/)).toBeInTheDocument();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should pass error object to fallbackOnError function', async () => {
const errorMessage = 'Network request failed';
let receivedError: Error | null = null;
const errorFallbackWithCapture = (error: Error): ReactElement => {
receivedError = error;
return <div>Captured error: {error.message}</div>;
};
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: errorMessage }));
}),
);
render(
<GuardAuthZ
relation="read"
object="dashboard:*"
fallbackOnError={errorFallbackWithCapture}
>
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(receivedError).not.toBeNull();
});
expect(receivedError).toBeInstanceOf(Error);
expect(screen.getByText(/Captured error:/)).toBeInTheDocument();
});
it('should render null when error occurs and no fallbackOnError provided', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
}),
);
const { container } = render(
<GuardAuthZ relation="read" object="dashboard:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(container.firstChild).toBeNull();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render fallbackOnNoPermissions when permission is denied', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [false])));
}),
);
render(
<GuardAuthZ
relation="update"
object="dashboard:123"
fallbackOnNoPermissions={NoPermissionFallback}
>
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText('Access denied')).toBeInTheDocument();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render null when permission is denied and no fallbackOnNoPermissions provided', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [false])));
}),
);
const { container } = render(
<GuardAuthZ relation="update" object="dashboard:123">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(container.firstChild).toBeNull();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should render null when permissions object is null', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(200), ctx.json({ data: [], status: 'success' }));
}),
);
const { container } = render(
<GuardAuthZ relation="read" object="dashboard:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(container.firstChild).toBeNull();
});
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should pass requiredPermissionName to fallbackOnNoPermissions', async () => {
const permission = buildPermission('update', 'dashboard:123');
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [false])));
}),
);
render(
<GuardAuthZ
relation="update"
object="dashboard:123"
fallbackOnNoPermissions={NoPermissionFallbackWithSuggestions}
>
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(
screen.getByText(/Access denied. Required permission:/),
).toBeInTheDocument();
});
expect(
screen.getAllByText(
new RegExp(permission.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')),
).length,
).toBeGreaterThan(0);
expect(screen.queryByText('Protected Content')).not.toBeInTheDocument();
});
it('should handle different relation and object combinations', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const { rerender } = render(
<GuardAuthZ relation="read" object="dashboard:*">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText('Protected Content')).toBeInTheDocument();
});
rerender(
<GuardAuthZ relation="delete" object="dashboard:456">
<TestChild />
</GuardAuthZ>,
);
await waitFor(() => {
expect(screen.getByText('Protected Content')).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,50 @@
import { ReactElement } from 'react';
import {
AuthZObject,
AuthZRelation,
BrandedPermission,
} from 'hooks/useAuthZ/types';
import { useAuthZ } from 'hooks/useAuthZ/useAuthZ';
import { buildPermission } from 'hooks/useAuthZ/utils';
export type GuardAuthZProps<R extends AuthZRelation> = {
children: ReactElement;
relation: R;
object: AuthZObject<R>;
fallbackOnLoading?: JSX.Element;
fallbackOnError?: (error: Error) => JSX.Element;
fallbackOnNoPermissions?: (response: {
requiredPermissionName: BrandedPermission;
}) => JSX.Element;
};
export function GuardAuthZ<R extends AuthZRelation>({
children,
relation,
object,
fallbackOnLoading,
fallbackOnError,
fallbackOnNoPermissions,
}: GuardAuthZProps<R>): JSX.Element | null {
const permission = buildPermission<R>(relation, object);
const { permissions, isLoading, error } = useAuthZ([permission]);
if (isLoading) {
return fallbackOnLoading ?? null;
}
if (error) {
return fallbackOnError?.(error) ?? null;
}
if (!permissions?.[permission]?.isGranted) {
return (
fallbackOnNoPermissions?.({
requiredPermissionName: permission,
}) ?? null
);
}
return children;
}

View File

@@ -3,8 +3,8 @@ import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/sonner';
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
const [activeTab, setActiveTab] = useState('feedback');

View File

@@ -4,8 +4,8 @@ import { toast } from '@signozhq/sonner';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
import FeedbackModal from '../FeedbackModal';
@@ -30,7 +30,7 @@ jest.mock('hooks/useGetTenantLicense', () => ({
useGetTenantLicense: jest.fn(),
}));
jest.mock('container/Integrations/utils', () => ({
jest.mock('pages/Integrations/utils', () => ({
handleContactSupport: jest.fn(),
}));

View File

@@ -2,13 +2,12 @@
import ReactMarkdown from 'react-markdown';
import { CodeProps } from 'react-markdown/lib/ast-to-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { a11yDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import logEvent from 'api/common/logEvent';
import { isEmpty } from 'lodash-es';
import rehypeRaw from 'rehype-raw';
import CodeCopyBtn from './CodeCopyBtn/CodeCopyBtn';
import SyntaxHighlighter, { a11yDark } from './syntaxHighlighter';
interface LinkProps {
href: string;

View File

@@ -0,0 +1,34 @@
import { PrismLight as SyntaxHighlighter } from 'react-syntax-highlighter';
import bash from 'react-syntax-highlighter/dist/esm/languages/prism/bash';
import docker from 'react-syntax-highlighter/dist/esm/languages/prism/docker';
import elixir from 'react-syntax-highlighter/dist/esm/languages/prism/elixir';
import go from 'react-syntax-highlighter/dist/esm/languages/prism/go';
import javascript from 'react-syntax-highlighter/dist/esm/languages/prism/javascript';
import json from 'react-syntax-highlighter/dist/esm/languages/prism/json';
import jsx from 'react-syntax-highlighter/dist/esm/languages/prism/jsx';
import rust from 'react-syntax-highlighter/dist/esm/languages/prism/rust';
import swift from 'react-syntax-highlighter/dist/esm/languages/prism/swift';
import tsx from 'react-syntax-highlighter/dist/esm/languages/prism/tsx';
import typescript from 'react-syntax-highlighter/dist/esm/languages/prism/typescript';
import yaml from 'react-syntax-highlighter/dist/esm/languages/prism/yaml';
import a11yDark from 'react-syntax-highlighter/dist/esm/styles/prism/a11y-dark';
SyntaxHighlighter.registerLanguage('bash', bash);
SyntaxHighlighter.registerLanguage('docker', docker);
SyntaxHighlighter.registerLanguage('dockerfile', docker);
SyntaxHighlighter.registerLanguage('elixir', elixir);
SyntaxHighlighter.registerLanguage('go', go);
SyntaxHighlighter.registerLanguage('javascript', javascript);
SyntaxHighlighter.registerLanguage('js', javascript);
SyntaxHighlighter.registerLanguage('json', json);
SyntaxHighlighter.registerLanguage('jsx', jsx);
SyntaxHighlighter.registerLanguage('rust', rust);
SyntaxHighlighter.registerLanguage('swift', swift);
SyntaxHighlighter.registerLanguage('ts', typescript);
SyntaxHighlighter.registerLanguage('tsx', tsx);
SyntaxHighlighter.registerLanguage('typescript', typescript);
SyntaxHighlighter.registerLanguage('yaml', yaml);
SyntaxHighlighter.registerLanguage('yml', yaml);
export default SyntaxHighlighter;
export { a11yDark };

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`MessageTip custom action 1`] = `
.c0 {

View File

@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Not Found page test should render Not Found page without errors 1`] = `
<DocumentFragment>

View File

@@ -60,11 +60,30 @@
gap: 8px;
margin-left: 108px;
position: relative;
/* Vertical dashed line connecting query elements */
&::after {
content: '';
position: absolute;
left: -28px;
top: 0;
bottom: 0;
width: 1px;
background: repeating-linear-gradient(
to bottom,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
}
.code-mirror-where-clause,
.query-aggregation-container,
.query-add-ons,
.metrics-aggregation-section-content {
.metrics-aggregation-section-content,
.metrics-container {
position: relative;
&::before {
@@ -102,6 +121,10 @@
.qb-elements-container {
margin-left: 0px;
&::after {
display: none;
}
.code-mirror-where-clause,
.query-aggregation-container,
.query-add-ons,
@@ -333,28 +356,7 @@
text-transform: uppercase;
&::before {
content: '';
height: 120px;
content: '';
position: absolute;
left: 0;
top: 31px;
bottom: 0;
width: 1px;
background: repeating-linear-gradient(
to bottom,
#1d212d,
#1d212d 4px,
transparent 4px,
transparent 8px
);
left: 15px;
}
&.has-trace-operator {
&::before {
height: 0px;
}
display: none;
}
}
@@ -462,10 +464,21 @@
.qb-content-section {
.qb-elements-container {
&::after {
background: repeating-linear-gradient(
to bottom,
var(--bg-vanilla-300),
var(--bg-vanilla-300) 4px,
transparent 4px,
transparent 8px
);
}
.code-mirror-where-clause,
.query-aggregation-container,
.query-add-ons,
.metrics-aggregation-section-content {
.metrics-aggregation-section-content,
.metrics-container {
&::before {
border-left: 6px dotted var(--bg-vanilla-300);
}
@@ -529,18 +542,6 @@
.qb-entity-options {
.options {
.query-name {
&::before {
background: repeating-linear-gradient(
to bottom,
var(--bg-vanilla-300),
var(--bg-vanilla-300) 4px,
transparent 4px,
transparent 8px
);
}
}
.formula-name {
&::before {
background: repeating-linear-gradient(

View File

@@ -207,6 +207,7 @@ export const QueryBuilderV2 = memo(function QueryBuilderV2({
queryVariant={config?.queryVariant || 'dropdown'}
showOnlyWhereClause={showOnlyWhereClause}
isListViewPanel={isListViewPanel}
signalSource={currentQuery.builder.queryData[0].source as 'meter' | ''}
onSignalSourceChange={onSignalSourceChange || ((): void => {})}
signalSourceChangeEnabled={signalSourceChangeEnabled}
queriesCount={1}

View File

@@ -1,14 +1,13 @@
import { memo, useCallback, useMemo, useState } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Select } from 'antd';
import {
initialQueriesMap,
initialQueryMeterWithType,
PANEL_TYPES,
} from 'constants/queryBuilder';
import { AggregatorFilter } from 'container/QueryBuilder/filters';
import { MetricNameSelector } from 'container/QueryBuilder/filters';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useQueryOperations } from 'hooks/queryBuilder/useQueryBuilderOperations';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { SelectOption } from 'types/common/select';
@@ -44,21 +43,12 @@ export const MetricsSelect = memo(function MetricsSelect({
signalSourceChangeEnabled: boolean;
savePreviousQuery: boolean;
}): JSX.Element {
const [attributeKeys, setAttributeKeys] = useState<BaseAutocompleteData[]>([]);
const { handleChangeAggregatorAttribute } = useQueryOperations({
index,
query,
entityVersion: version,
});
const handleAggregatorAttributeChange = useCallback(
(value: BaseAutocompleteData, isEditMode?: boolean) => {
handleChangeAggregatorAttribute(value, isEditMode, attributeKeys || []);
},
[handleChangeAggregatorAttribute, attributeKeys],
);
const {
updateAllQueriesOperators,
handleSetQueryData,
@@ -164,12 +154,10 @@ export const MetricsSelect = memo(function MetricsSelect({
/>
)}
<AggregatorFilter
onChange={handleAggregatorAttributeChange}
<MetricNameSelector
onChange={handleChangeAggregatorAttribute}
query={query}
index={index}
signalSource={signalSource || ''}
setAttributeKeys={setAttributeKeys}
/>
</div>
);

View File

@@ -202,8 +202,8 @@ function QueryAddOns({
} else {
filteredAddOns = Object.values(ADD_ONS);
// Filter out group_by for metrics data source
if (query.dataSource === DataSource.METRICS) {
// Filter out group_by for metrics data source (handled in MetricsAggregateSection)
filteredAddOns = filteredAddOns.filter(
(addOn) => addOn.key !== ADD_ONS_KEYS.GROUP_BY,
);

View File

@@ -43,6 +43,7 @@ jest.mock(
);
jest.mock('container/QueryBuilder/filters', () => ({
AggregatorFilter: (): JSX.Element => <div />,
MetricNameSelector: (): JSX.Element => <div />,
}));
// Mock hooks
jest.mock('hooks/queryBuilder/useQueryBuilder');

View File

@@ -0,0 +1,41 @@
.guard-authz-error-no-authz {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 24px;
.guard-authz-error-no-authz-content {
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 8px;
max-width: 500px;
}
img {
width: 32px;
height: 32px;
}
h3 {
font-size: 18px;
color: var(--l1-foreground);
line-height: 18px;
}
p {
font-size: 14px;
color: var(--l3-foreground);
line-height: 18px;
span {
background-color: var(--l3-background);
white-space: nowrap;
padding: 0 2px;
}
}
}

View File

@@ -0,0 +1,472 @@
import { ReactElement } from 'react';
import type { RouteComponentProps } from 'react-router-dom';
import {
AuthtypesGettableTransactionDTO,
AuthtypesTransactionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { ENVIRONMENT } from 'constants/env';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
import { render, screen, waitFor } from 'tests/test-utils';
import { createGuardedRoute } from './createGuardedRoute';
const BASE_URL = ENVIRONMENT.baseURL || '';
const AUTHZ_CHECK_URL = `${BASE_URL}/api/v1/authz/check`;
function authzMockResponse(
payload: AuthtypesTransactionDTO[],
authorizedByIndex: boolean[],
): { data: AuthtypesGettableTransactionDTO[]; status: string } {
return {
data: payload.map((txn, i) => ({
relation: txn.relation,
object: txn.object,
authorized: authorizedByIndex[i] ?? false,
})),
status: 'success',
};
}
describe('createGuardedRoute', () => {
const TestComponent = ({ testProp }: { testProp: string }): ReactElement => (
<div>Test Component: {testProp}</div>
);
it('should render component when permission is granted', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:*',
);
const mockMatch = {
params: {},
isExact: true,
path: '/dashboard',
url: '/dashboard',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
});
it('should substitute route parameters in object string', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:{id}',
);
const mockMatch = {
params: { id: '123' },
isExact: true,
path: '/dashboard/:id',
url: '/dashboard/123',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
});
it('should handle multiple route parameters', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = (await req.json()) as AuthtypesTransactionDTO[];
const txn = payload[0];
const responseData: AuthtypesGettableTransactionDTO[] = [
{
relation: txn.relation,
object: {
resource: {
name: txn.object.resource.name,
type: txn.object.resource.type,
},
selector: '123:456',
},
authorized: true,
},
];
return res(
ctx.status(200),
ctx.json({ data: responseData, status: 'success' }),
);
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'update',
'dashboard:{id}:{version}',
);
const mockMatch = {
params: { id: '123', version: '456' },
isExact: true,
path: '/dashboard/:id/:version',
url: '/dashboard/123/456',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
});
it('should keep placeholder when route parameter is missing', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:{id}',
);
const mockMatch = {
params: {},
isExact: true,
path: '/dashboard',
url: '/dashboard',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
});
it('should render loading fallback when loading', () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (_req, res, ctx) => {
return res(
ctx.delay('infinite'),
ctx.status(200),
ctx.json({ data: [], status: 'success' }),
);
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:*',
);
const mockMatch = {
params: {},
isExact: true,
path: '/dashboard',
url: '/dashboard',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
expect(screen.getByText('SigNoz')).toBeInTheDocument();
expect(
screen.queryByText('Test Component: test-value'),
).not.toBeInTheDocument();
});
it('should render error fallback when API error occurs', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, (_req, res, ctx) => {
return res(ctx.status(500), ctx.json({ error: 'Internal Server Error' }));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:*',
);
const mockMatch = {
params: {},
isExact: true,
path: '/dashboard',
url: '/dashboard',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument();
});
expect(
screen.queryByText('Test Component: test-value'),
).not.toBeInTheDocument();
});
it('should render no permissions fallback when permission is denied', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [false])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'update',
'dashboard:{id}',
);
const mockMatch = {
params: { id: '123' },
isExact: true,
path: '/dashboard/:id',
url: '/dashboard/123',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
const heading = document.querySelector('h3');
expect(heading).toBeInTheDocument();
expect(heading?.textContent).toMatch(/permission to view/i);
});
expect(screen.getByText('update')).toBeInTheDocument();
expect(screen.getByText('dashboard:123')).toBeInTheDocument();
expect(
screen.queryByText('Test Component: test-value'),
).not.toBeInTheDocument();
});
it('should pass all props to wrapped component', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const ComponentWithMultipleProps = ({
prop1,
prop2,
prop3,
}: {
prop1: string;
prop2: number;
prop3: boolean;
}): ReactElement => (
<div>
{prop1} - {prop2} - {prop3.toString()}
</div>
);
const GuardedComponent = createGuardedRoute(
ComponentWithMultipleProps,
'read',
'dashboard:*',
);
const mockMatch = {
params: {},
isExact: true,
path: '/dashboard',
url: '/dashboard',
};
const props = {
prop1: 'value1',
prop2: 42,
prop3: true,
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('value1 - 42 - true')).toBeInTheDocument();
});
});
it('should memoize resolved object based on route params', async () => {
let requestCount = 0;
const requestedObjects: string[] = [];
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
requestCount++;
const payload = (await req.json()) as AuthtypesTransactionDTO[];
const obj = payload[0]?.object;
const name = obj?.resource?.name;
const selector = obj?.selector ?? '*';
const objectStr =
obj?.resource?.type === 'metaresources' ? name : `${name}:${selector}`;
requestedObjects.push(objectStr ?? '');
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'read',
'dashboard:{id}',
);
const mockMatch1 = {
params: { id: '123' },
isExact: true,
path: '/dashboard/:id',
url: '/dashboard/123',
};
const props1 = {
testProp: 'test-value-1',
match: mockMatch1,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
const { unmount } = render(<GuardedComponent {...props1} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value-1')).toBeInTheDocument();
});
expect(requestCount).toBe(1);
expect(requestedObjects).toContain('dashboard:123');
unmount();
const mockMatch2 = {
params: { id: '456' },
isExact: true,
path: '/dashboard/:id',
url: '/dashboard/456',
};
const props2 = {
testProp: 'test-value-2',
match: mockMatch2,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props2} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value-2')).toBeInTheDocument();
});
expect(requestCount).toBe(2);
expect(requestedObjects).toContain('dashboard:456');
});
it('should handle different relation types', async () => {
server.use(
rest.post(AUTHZ_CHECK_URL, async (req, res, ctx) => {
const payload = await req.json();
return res(ctx.status(200), ctx.json(authzMockResponse(payload, [true])));
}),
);
const GuardedComponent = createGuardedRoute(
TestComponent,
'delete',
'dashboard:{id}',
);
const mockMatch = {
params: { id: '789' },
isExact: true,
path: '/dashboard/:id',
url: '/dashboard/789',
};
const props = {
testProp: 'test-value',
match: mockMatch,
location: ({} as unknown) as RouteComponentProps['location'],
history: ({} as unknown) as RouteComponentProps['history'],
};
render(<GuardedComponent {...props} />);
await waitFor(() => {
expect(screen.getByText('Test Component: test-value')).toBeInTheDocument();
});
});
});

View File

@@ -0,0 +1,73 @@
import { ComponentType, ReactElement, useMemo } from 'react';
import { RouteComponentProps } from 'react-router-dom';
import {
AuthZObject,
AuthZRelation,
BrandedPermission,
} from 'hooks/useAuthZ/types';
import { parsePermission } from 'hooks/useAuthZ/utils';
import ErrorBoundaryFallback from '../../pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
import AppLoading from '../AppLoading/AppLoading';
import { GuardAuthZ } from '../GuardAuthZ/GuardAuthZ';
import './createGuardedRoute.styles.scss';
const onErrorFallback = (): JSX.Element => <ErrorBoundaryFallback />;
function OnNoPermissionsFallback(response: {
requiredPermissionName: BrandedPermission;
}): ReactElement {
const { relation, object } = parsePermission(response.requiredPermissionName);
return (
<div className="guard-authz-error-no-authz">
<div className="guard-authz-error-no-authz-content">
<img src="/Icons/no-data.svg" alt="No permission" />
<h3>Uh-oh! You dont have permission to view this page.</h3>
<p>
You need the following permission to view this page:
<br />
Relation: <span>{relation}</span>
<br />
Object: <span>{object}</span>
<br />
Ask your SigNoz administrator to grant access.
</p>
</div>
</div>
);
}
// eslint-disable-next-line @typescript-eslint/ban-types
export function createGuardedRoute<P extends object, R extends AuthZRelation>(
Component: ComponentType<P>,
relation: R,
object: AuthZObject<R>,
): ComponentType<P & RouteComponentProps<Record<string, string>>> {
return function GuardedRouteComponent(
props: P & RouteComponentProps<Record<string, string>>,
): ReactElement {
const resolvedObject = useMemo(() => {
const paramPattern = /\{([^}]+)\}/g;
return object.replace(paramPattern, (match, paramName) => {
const paramValue = props.match?.params?.[paramName];
return paramValue !== undefined ? paramValue : match;
}) as AuthZObject<R>;
}, [props.match?.params]);
return (
<GuardAuthZ
relation={relation}
object={resolvedObject}
fallbackOnLoading={<AppLoading />}
fallbackOnError={onErrorFallback}
fallbackOnNoPermissions={(response): ReactElement => (
<OnNoPermissionsFallback {...response} />
)}
>
<Component {...props} />
</GuardAuthZ>
);
};
}

View File

@@ -29,7 +29,6 @@ export enum LOCALSTORAGE {
DONT_SHOW_SLOW_API_WARNING = 'DONT_SHOW_SLOW_API_WARNING',
METRICS_LIST_OPTIONS = 'METRICS_LIST_OPTIONS',
SHOW_EXCEPTIONS_QUICK_FILTERS = 'SHOW_EXCEPTIONS_QUICK_FILTERS',
BANNER_DISMISSED = 'BANNER_DISMISSED',
QUICK_FILTERS_SETTINGS_ANNOUNCEMENT = 'QUICK_FILTERS_SETTINGS_ANNOUNCEMENT',
FUNNEL_STEPS = 'FUNNEL_STEPS',
SPAN_DETAILS_PINNED_ATTRIBUTES = 'SPAN_DETAILS_PINNED_ATTRIBUTES',

View File

@@ -1,4 +1,5 @@
// ** Helpers
import { MetrictypesTypeDTO } from 'api/generated/services/sigNoz.schemas';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { createNewBuilderItemName } from 'lib/newQueryBuilder/createNewBuilderItemName';
import { IAttributeValuesResponse } from 'types/api/queryBuilder/getAttributesValues';
@@ -177,7 +178,7 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
{
metricName: '',
temporality: '',
timeAggregation: MetricAggregateOperator.COUNT,
timeAggregation: MetricAggregateOperator.AVG,
spaceAggregation: MetricAggregateOperator.SUM,
reduceTo: ReduceOperators.AVG,
},
@@ -225,7 +226,7 @@ export const initialQueryBuilderFormMeterValues: IBuilderQuery = {
{
metricName: '',
temporality: '',
timeAggregation: MeterAggregateOperator.COUNT,
timeAggregation: MeterAggregateOperator.AVG,
spaceAggregation: MeterAggregateOperator.SUM,
reduceTo: ReduceOperators.AVG,
},
@@ -371,6 +372,31 @@ export enum ATTRIBUTE_TYPES {
EXPONENTIAL_HISTOGRAM = 'ExponentialHistogram',
}
const METRIC_TYPE_TO_ATTRIBUTE_TYPE: Record<
MetrictypesTypeDTO,
ATTRIBUTE_TYPES
> = {
[MetrictypesTypeDTO.sum]: ATTRIBUTE_TYPES.SUM,
[MetrictypesTypeDTO.gauge]: ATTRIBUTE_TYPES.GAUGE,
[MetrictypesTypeDTO.histogram]: ATTRIBUTE_TYPES.HISTOGRAM,
[MetrictypesTypeDTO.summary]: ATTRIBUTE_TYPES.GAUGE,
[MetrictypesTypeDTO.exponentialhistogram]:
ATTRIBUTE_TYPES.EXPONENTIAL_HISTOGRAM,
};
export function toAttributeType(
metricType: MetrictypesTypeDTO | undefined,
isMonotonic?: boolean,
): ATTRIBUTE_TYPES | '' {
if (!metricType) {
return '';
}
if (metricType === MetrictypesTypeDTO.sum && isMonotonic === false) {
return ATTRIBUTE_TYPES.GAUGE;
}
return METRIC_TYPE_TO_ATTRIBUTE_TYPE[metricType] || '';
}
export type IQueryBuilderState = 'search';
export const QUERY_BUILDER_SEARCH_VALUES = {

View File

@@ -1,19 +1,4 @@
export const REACT_QUERY_KEY = {
// Cloud Integration Query Keys
CLOUD_INTEGRATION_ACCOUNTS: 'CLOUD_INTEGRATION_ACCOUNTS',
CLOUD_INTEGRATION_SERVICES: 'CLOUD_INTEGRATION_SERVICES',
CLOUD_INTEGRATION_SERVICE_DETAILS: 'CLOUD_INTEGRATION_SERVICE_DETAILS',
CLOUD_INTEGRATION_ACCOUNT_STATUS: 'CLOUD_INTEGRATION_ACCOUNT_STATUS',
CLOUD_INTEGRATION_UPDATE_ACCOUNT_CONFIG:
'CLOUD_INTEGRATION_UPDATE_ACCOUNT_CONFIG',
CLOUD_INTEGRATION_UPDATE_SERVICE_CONFIG:
'CLOUD_INTEGRATION_UPDATE_SERVICE_CONFIG',
CLOUD_INTEGRATION_GENERATE_CONNECTION_URL:
'CLOUD_INTEGRATION_GENERATE_CONNECTION_URL',
CLOUD_INTEGRATION_GET_CONNECTION_PARAMS:
'CLOUD_INTEGRATION_GET_CONNECTION_PARAMS',
CLOUD_INTEGRATION_GET_DEPLOYMENT_COMMANDS:
'CLOUD_INTEGRATION_GET_DEPLOYMENT_COMMANDS',
GET_PUBLIC_DASHBOARD: 'GET_PUBLIC_DASHBOARD',
GET_PUBLIC_DASHBOARD_META: 'GET_PUBLIC_DASHBOARD_META',
GET_PUBLIC_DASHBOARD_WIDGET_DATA: 'GET_PUBLIC_DASHBOARD_WIDGET_DATA',
@@ -64,7 +49,6 @@ export const REACT_QUERY_KEY = {
// Metrics Explorer Query Keys
GET_METRICS_LIST: 'GET_METRICS_LIST',
GET_METRICS_TREE_MAP: 'GET_METRICS_TREE_MAP',
GET_METRICS_LIST_FILTER_KEYS: 'GET_METRICS_LIST_FILTER_KEYS',
GET_METRICS_LIST_FILTER_VALUES: 'GET_METRICS_LIST_FILTER_VALUES',
GET_METRIC_DETAILS: 'GET_METRIC_DETAILS',

View File

@@ -65,7 +65,6 @@ const ROUTES = {
WORKSPACE_SUSPENDED: '/workspace-suspended',
SHORTCUTS: '/settings/shortcuts',
INTEGRATIONS: '/integrations',
INTEGRATIONS_DETAIL: '/integrations/:integrationId',
MESSAGING_QUEUES_BASE: '/messaging-queues',
MESSAGING_QUEUES_KAFKA: '/messaging-queues/kafka',
MESSAGING_QUEUES_KAFKA_DETAIL: '/messaging-queues/kafka/detail',

View File

@@ -74,7 +74,7 @@ describe('Alert Channels Settings List page', () => {
});
await waitFor(() => {
expect(successNotification).toBeCalledWith({
expect(successNotification).toHaveBeenCalledWith({
message: 'Success',
description: 'channel_delete_success',
});

View File

@@ -0,0 +1,24 @@
import {
IntegrationType,
RequestIntegrationBtn,
} from 'pages/Integrations/RequestIntegrationBtn';
import Header from './Header/Header';
import HeroSection from './HeroSection/HeroSection';
import ServicesTabs from './ServicesSection/ServicesTabs';
function CloudIntegrationPage(): JSX.Element {
return (
<div>
<Header />
<HeroSection />
<RequestIntegrationBtn
type={IntegrationType.AWS_SERVICES}
message="Can't find the AWS service you're looking for? Request more integrations"
/>
<ServicesTabs />
</div>
);
}
export default CloudIntegrationPage;

View File

@@ -48,7 +48,7 @@
.lightMode {
.cloud-header {
border-bottom: 1px solid var(--bg-vanilla-300);
border-bottom: 1px solid var(--bg-slate-300);
&__breadcrumb-title {
color: var(--bg-ink-400);

View File

@@ -1,13 +1,11 @@
import { Link } from 'react-router-dom';
import { Button } from '@signozhq/button';
import Breadcrumb from 'antd/es/breadcrumb';
import ROUTES from 'constants/routes';
import { IntegrationType } from 'container/Integrations/types';
import { Blocks, LifeBuoy } from 'lucide-react';
import './Header.styles.scss';
function Header({ title }: { title: IntegrationType }): JSX.Element {
function Header(): JSX.Element {
return (
<div className="cloud-header">
<div className="cloud-header__navigation">
@@ -25,26 +23,25 @@ function Header({ title }: { title: IntegrationType }): JSX.Element {
),
},
{
title: <div className="cloud-header__breadcrumb-title">{title}</div>,
title: (
<div className="cloud-header__breadcrumb-title">
Amazon Web Services
</div>
),
},
]}
/>
</div>
<div className="cloud-header__actions">
<Button
variant="solid"
size="sm"
color="secondary"
onClick={(): void => {
window.open(
'https://signoz.io/blog/native-aws-integrations-with-autodiscovery/',
'_blank',
);
}}
prefixIcon={<LifeBuoy size={12} />}
<a
href="https://signoz.io/blog/native-aws-integrations-with-autodiscovery/"
target="_blank"
rel="noopener noreferrer"
className="cloud-header__help"
>
<LifeBuoy size={12} />
Get Help
</Button>
</a>
</div>
</div>
);

View File

@@ -1,10 +1,8 @@
.hero-section {
padding: 16px;
height: 308px;
padding: 26px 16px;
display: flex;
flex-direction: column;
gap: 16px;
gap: 24px;
position: relative;
overflow: hidden;
background-position: right;
@@ -12,37 +10,35 @@
background-repeat: no-repeat;
border-bottom: 1px solid var(--bg-slate-500);
&__icon {
height: fit-content;
background-color: var(--bg-ink-400);
padding: 12px;
border: 1px solid var(--bg-ink-300);
border-radius: 6px;
width: 60px;
height: 60px;
display: flex;
align-items: center;
img {
width: 100%;
}
}
&__details {
display: flex;
flex-direction: column;
gap: 12px;
&-header {
display: flex;
flex-direction: row;
align-items: center;
gap: 16px;
&__icon {
height: fit-content;
background-color: var(--bg-ink-400);
padding: 12px;
border: 1px solid var(--bg-ink-300);
border-radius: 6px;
width: 60px;
height: 60px;
}
&__title {
color: var(--bg-vanilla-100);
font-size: 24px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.12px;
}
.title {
color: var(--bg-vanilla-100);
font-size: 24px;
font-weight: 500;
line-height: 20px;
letter-spacing: -0.12px;
}
&__description {
.description {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;

View File

@@ -0,0 +1,34 @@
import { useIsDarkMode } from 'hooks/useDarkMode';
import AccountActions from './components/AccountActions';
import './HeroSection.style.scss';
function HeroSection(): JSX.Element {
const isDarkMode = useIsDarkMode();
return (
<div
className="hero-section"
style={
isDarkMode
? {
backgroundImage: `url('/Images/integrations-hero-bg.png')`,
}
: {}
}
>
<div className="hero-section__icon">
<img src="/Logos/aws-dark.svg" alt="aws-logo" />
</div>
<div className="hero-section__details">
<div className="title">Amazon Web Services</div>
<div className="description">
One-click setup for AWS monitoring with SigNoz
</div>
<AccountActions />
</div>
</div>
);
}
export default HeroSection;

View File

@@ -4,56 +4,14 @@
&-with-account {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16px;
border-radius: 4px;
border: 1px solid var(--l3-background);
background: var(--l1-background);
.selected-cloud-integration-account-status {
display: flex;
align-items: center;
justify-content: center;
border-right: 1px solid var(--l3-background);
border-radius: none;
width: 32px;
}
&-selector-container {
display: flex;
flex-direction: row;
align-items: center;
.account-selector-label {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 16px;
padding: 8px 16px;
}
.account-selector {
.ant-select {
background-color: transparent !important;
border: none !important;
box-shadow: none !important;
.ant-select-selector {
background: transparent !important;
border: none !important;
box-shadow: none !important;
}
}
}
}
flex-direction: column;
gap: 10px;
}
}
&__input-skeleton {
width: 300px;
margin-bottom: 16px;
}
&__new-account-button-skeleton {
@@ -64,13 +22,11 @@
&__account-settings-button-skeleton {
width: 140px;
}
&__action-buttons {
display: flex;
align-items: center;
gap: 8px;
}
&__action-button {
font-family: 'Inter';
border-radius: 2px;
@@ -98,6 +54,44 @@
}
}
.cloud-account-selector {
border-radius: 2px;
border: 1px solid var(--bg-ink-300);
background: linear-gradient(
139deg,
rgba(18, 19, 23, 0.8) 0%,
rgba(18, 19, 23, 0.9) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
-webkit-backdrop-filter: blur(20px);
backdrop-filter: blur(20px);
.ant-select-selector {
border-color: var(--bg-slate-400) !important;
background: var(--bg-ink-300) !important;
padding: 6px 8px !important;
}
.ant-select-selection-item {
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 16px;
}
.account-option-item {
display: flex;
align-items: center;
justify-content: space-between;
&__selected {
display: flex;
align-items: center;
justify-content: center;
height: 14px;
width: 14px;
background-color: rgba(192, 193, 195, 0.2); /* #C0C1C3 with 0.2 opacity */
border-radius: 2px;
}
}
}
.lightMode {
.hero-section__action-button {
&.primary {

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