Compare commits

...

8 Commits

Author SHA1 Message Date
Yunus M
ee02d96114 refactor: remove light mode styles from various components and update color variables 2026-04-24 00:44:33 +05:30
SagarRajput-7
c595506a09 feat: base path config setup and index.html setup as go template for BE injection (#11026)
* feat: base path config setup and plugin for gotmpl generation at build time

* feat: changed output path to dir level

* feat: refactor the interceptor and added gotmpl into gitignore

* feat: removed plugin and serving the index.html only as the template

* feat: updated the html template

* feat: updated base path utils and fixed navigation and translations

* feat: code refactor around feedbacks

* feat: applied suggested patch changes

* feat: code refactor around feedbacks

* feat(base-path): mirgate rule to oxlint

* feat(base-path): fix lint issues

* feat(base-path): configure local dev setup
2026-04-23 17:08:00 +00:00
swapnil-signoz
21ce7a663a feat: adding cloud integration azure types and openapi spec (#11005)
* refactor: moving types to cloud provider specific namespace/pkg

* refactor: separating cloud provider types

* refactor: using upper case key for AWS

* feat: adding cloud integration azure types

* feat: adding azure services

* refactor: updating omitempty tags

* refactor: updating azure integration config

* feat: completing azure types

* refactor: lint issues

* refactor: updating command key

* fix: handle optional connection URL in AWS integration

* refactor: updating strategy struct

* refactor: updating azure blob storage service name

* refactor: update Azure service identifiers

* refactor: updating types
2026-04-23 14:48:04 +00:00
Abhi kumar
e6e2f95ec2 fix: minor fixes in tooltip ui (#11077)
* fix: minor fixes in tooltip ui

* chore: minor changes

* chore: minor fixes
2026-04-23 13:32:57 +00:00
Vikrant Gupta
afe85c48f9 feat(authz): add support for delete role (#11044)
* feat(authz): add support for delete role

* feat(authz): register config and return error on cleanup failure

* feat(authz): take user and serviceaccount DI for assignee checks

* feat(authz): add the example yaml

* feat(authz): move to callbacks instead of DI
2026-04-23 13:25:19 +00:00
Pandey
aeadeacc70 chore(tests): bump deps to close 8 dependabot alerts (#11076)
Bumps direct pins pytest>=9.0.3 (GHSA-6w46-j5rx-g56g) and requests>=2.33.0
(GHSA-gc5v-m9x4-r6x2). uv lock --upgrade then refreshes everything
transitive, which covers:

- cryptography 46.0.3 -> 46.0.7 (GHSA-r6ph-v2qm-q3c2 high, GHSA-p423-j2cm-9vmq
  medium, GHSA-m959-cc7f-wv43 low)
- python-dotenv 1.2.1 -> 1.2.2 (GHSA-mf9w-mj56-hr94)
- Pygments 2.19.2 -> 2.20.0 (GHSA-5239-wwwm-4pmq)
- jwcrypto 1.5.6 -> 1.5.7 (GHSA-fjrm-76x2-c4q4 — PyPI has 1.5.7, GitHub's
  advisory hasn't catalogued the patched version yet)

Risk: python-keycloak majored 6.0.0 -> 7.1.1. The 7.0 release tightens
return-type handling and can now raise TypeError on mismatch. Imports
collect cleanly (499 tests) but only the callbackauthn suite exercises
KeycloakAdmin at runtime — watch that job in CI.
2026-04-23 12:56:29 +00:00
Vikrant Gupta
6996d41b01 fix(serviceaccount): status code for deleted service accounts (#11075)
* fix(serviceaccount): status code for deleted service accounts

* fix(authz): plural relation endpoints
2026-04-23 12:53:52 +00:00
Pandey
f62024ad3f chore: modern fmts and lints for tests/ (#11074)
* chore(frontend): remove stale e2e scaffold

frontend/e2e/ held an unused settings-only test-plan scaffold from Oct 2025.
Active Playwright specs live at tests/e2e/. Drop the directory, the orphan
playwright.config.ts, the @playwright/test dependency, and the tsconfig
references that pinned them.

* chore(e2e): migrate formatter from prettier to oxfmt

Swap tests/e2e/ onto oxfmt — same tool the frontend adopted in #11057. Style
matches frontend/.oxfmtrc.json (tabs, tabWidth:1) so the two TS trees stay
visually consistent. Drops .prettierrc.json and .prettierignore, adds the
fmt/fmt:check yarn scripts, and reformats the existing specs.

* chore(e2e): migrate linter from eslint to oxlint

Drop eslint + @typescript-eslint plugins in favour of oxlint 1.59 + tsgolint
— same toolchain the frontend adopted in #10176. The .oxlintrc.json mirrors
frontend/.oxlintrc.json with plugins scoped to a Playwright TS codebase
(eslint, typescript, unicorn, import, promise).

Divergence: eslint-plugin-playwright is not ported. Its rules depend on
ESLint APIs (context.getAncestors) that oxlint's JS plugin shim does not
implement, so the five playwright/* rules are dropped in this migration.

* ci(e2e): add fmtlint job

Mirror integrationci.yaml's fmtlint job for e2e. Runs oxfmt --check and
oxlint on tests/e2e/ under the same safe-to-e2e label gating as the
existing test job.

* chore(integration): migrate python tooling from black/pylint/isort/autoflake to ruff

Replace the four-tool stack with ruff — same motivation as the oxfmt/oxlint
swap on the TS side. One tool covers formatting (ruff format), import
sorting (I), unused-import/variable cleanup (F401/F841), and the pylint
rules we actually care about (E/W/F/UP/B/PL).

Rule set mirrors the intent of the prior pylint config: too-many-* checks
and magic-value-comparison stay disabled, dangerous-default-value (now
B006) stays muted. A handful of newly-surfaced codes (B011/B024/B905/E741/
UP047/PLC0206/PLW2901) are also muted to keep this a pure tool swap — each
deserves its own review before enabling.

Divergence: ruff caps line-length at 320, so the prior pylint value of 400
drops to 320. Nothing in tree exceeds 320, so no lines wrap.

No changes to integrationci.yaml — both fmt/lint steps still call
make py-fmt / make py-lint, which now dispatch to ruff.

* chore(e2e): restore playwright lint rules via oxlint jsPlugin

eslint-plugin-playwright@2.x was rewritten against ESLint 8's
context.sourceCode.getAncestors() API, which oxlint's JS plugin shim
exposes. The 0.16.x version previously ruled out by context.getAncestors()
missing is no longer a blocker. Bump to 2.10.2, re-add it as a jsPlugin,
and restore the five rules dropped in the initial oxlint migration:
expect-expect, no-conditional-in-test, no-page-pause, no-wait-for-timeout,
prefer-web-first-assertions.

Rule count: 104 → 109.

* chore(frontend): remove stale e2e prompt

frontend/prompts/generate-e2e-test.md is leftover from the same Oct 2025
scaffold removed in ebf735dcc. It references frontend/e2e/utils/login.util.ts,
which no longer exists, and is not wired into anything.

* chore(e2e): make .env.local write layout explicit

The single f-string with inline \n escapes read as a wall of text after
ruff's line-length allowance collapsed it onto one line. Switch to a
triple-quoted f-string so the generated .env.local structure is visible
in source. Byte-for-byte identical output.

* chore(e2e): write .env.local one key per line

Open the file with a context manager and emit each key with its own
f.write call. Same output as before, but each key-value pair is a
discrete statement.
2026-04-23 12:01:42 +00:00
289 changed files with 4065 additions and 11209 deletions

View File

@@ -9,6 +9,27 @@ on:
- labeled
jobs:
fmtlint:
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'))) && contains(github.event.pull_request.labels.*.name, 'safe-to-e2e')
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v4
- name: node
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: install
run: |
cd tests/e2e && yarn install --frozen-lockfile
- name: fmt
run: |
cd tests/e2e && yarn fmt:check
- name: lint
run: |
cd tests/e2e && yarn lint
test:
strategy:
fail-fast: false

View File

@@ -201,14 +201,12 @@ docker-buildx-enterprise: go-build-enterprise js-build
# python commands
##############################################################
.PHONY: py-fmt
py-fmt: ## Run black across the shared tests project
@cd tests && uv run black .
py-fmt: ## Run ruff format across the shared tests project
@cd tests && uv run ruff format .
.PHONY: py-lint
py-lint: ## Run lint across the shared tests project
@cd tests && uv run isort .
@cd tests && uv run autoflake .
@cd tests && uv run pylint .
py-lint: ## Run ruff check across the shared tests project
@cd tests && uv run ruff check --fix .
.PHONY: py-test-setup
py-test-setup: ## Bring up the shared SigNoz backend used by integration and e2e tests

View File

@@ -92,7 +92,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(ctx context.Context, providerSettings factory.ProviderSettings, store authtypes.AuthNStore, licensing licensing.Licensing) (map[authtypes.AuthNProvider]authn.AuthN, error) {
return signoz.NewAuthNs(ctx, providerSettings, store, licensing)
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
func(ctx context.Context, sqlstore sqlstore.SQLStore, _ licensing.Licensing, _ []authz.OnBeforeRoleDelete, _ dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
if err != nil {
return nil, err

View File

@@ -137,12 +137,12 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return authNs, nil
},
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
func(ctx context.Context, sqlstore sqlstore.SQLStore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, dashboardModule dashboard.Module) (factory.ProviderFactory[authz.AuthZ, authz.Config], error) {
openfgaDataStore, err := openfgaserver.NewSQLStore(sqlstore)
if err != nil {
return nil, err
}
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, dashboardModule), nil
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, onBeforeRoleDelete, dashboardModule), nil
},
func(store sqlstore.SQLStore, settings factory.ProviderSettings, analytics analytics.Analytics, orgGetter organization.Getter, queryParser queryparser.QueryParser, querier querier.Querier, licensing licensing.Licensing) dashboard.Module {
return impldashboard.NewModule(pkgimpldashboard.NewStore(store), settings, analytics, orgGetter, queryParser, querier, licensing)

View File

@@ -407,3 +407,11 @@ cloudintegration:
agent:
# The version of the cloud integration agent.
version: v0.0.8
##################### Authz #################################
authz:
# Specifies the authz provider to use.
provider: openfga
openfga:
# maximum tuples allowed per openfga write operation.
max_tuples_per_write: 100

View File

@@ -668,8 +668,8 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSAccountConfig'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureAccountConfig'
type: object
CloudintegrationtypesAgentReport:
nullable: true
@@ -693,6 +693,90 @@ components:
nullable: true
type: array
type: object
CloudintegrationtypesAzureAccountConfig:
properties:
deploymentRegion:
type: string
resourceGroups:
items:
type: string
type: array
required:
- deploymentRegion
- resourceGroups
type: object
CloudintegrationtypesAzureConnectionArtifact:
properties:
cliCommand:
type: string
cloudPowerShellCommand:
type: string
required:
- cliCommand
- cloudPowerShellCommand
type: object
CloudintegrationtypesAzureIntegrationConfig:
properties:
deploymentRegion:
type: string
resourceGroups:
items:
type: string
type: array
telemetryCollectionStrategy:
items:
$ref: '#/components/schemas/CloudintegrationtypesAzureTelemetryCollectionStrategy'
type: array
required:
- deploymentRegion
- resourceGroups
- telemetryCollectionStrategy
type: object
CloudintegrationtypesAzureLogsCollectionStrategy:
properties:
categoryGroups:
items:
type: string
type: array
required:
- categoryGroups
type: object
CloudintegrationtypesAzureMetricsCollectionStrategy:
type: object
CloudintegrationtypesAzureServiceConfig:
properties:
logs:
$ref: '#/components/schemas/CloudintegrationtypesAzureServiceLogsConfig'
metrics:
$ref: '#/components/schemas/CloudintegrationtypesAzureServiceMetricsConfig'
required:
- logs
- metrics
type: object
CloudintegrationtypesAzureServiceLogsConfig:
properties:
enabled:
type: boolean
type: object
CloudintegrationtypesAzureServiceMetricsConfig:
properties:
enabled:
type: boolean
type: object
CloudintegrationtypesAzureTelemetryCollectionStrategy:
properties:
logs:
$ref: '#/components/schemas/CloudintegrationtypesAzureLogsCollectionStrategy'
metrics:
$ref: '#/components/schemas/CloudintegrationtypesAzureMetricsCollectionStrategy'
resourceProvider:
type: string
resourceType:
type: string
required:
- resourceProvider
- resourceType
type: object
CloudintegrationtypesCloudIntegrationService:
nullable: true
properties:
@@ -737,8 +821,8 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSConnectionArtifact'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureConnectionArtifact'
type: object
CloudintegrationtypesCredentials:
properties:
@@ -910,8 +994,8 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSPostableAccountConfig'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureAccountConfig'
type: object
CloudintegrationtypesPostableAgentCheckIn:
properties:
@@ -934,8 +1018,8 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSIntegrationConfig'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureIntegrationConfig'
type: object
CloudintegrationtypesService:
properties:
@@ -972,8 +1056,8 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSServiceConfig'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureServiceConfig'
type: object
CloudintegrationtypesServiceID:
enum:
@@ -990,6 +1074,8 @@ components:
- s3sync
- sns
- sqs
- storageaccountsblob
- cdnprofile
type: string
CloudintegrationtypesServiceMetadata:
properties:
@@ -1018,16 +1104,32 @@ components:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSTelemetryCollectionStrategy'
required:
- aws
azure:
$ref: '#/components/schemas/CloudintegrationtypesAzureTelemetryCollectionStrategy'
type: object
CloudintegrationtypesUpdatableAccount:
properties:
config:
$ref: '#/components/schemas/CloudintegrationtypesAccountConfig'
$ref: '#/components/schemas/CloudintegrationtypesUpdatableAccountConfig'
required:
- config
type: object
CloudintegrationtypesUpdatableAccountConfig:
properties:
aws:
$ref: '#/components/schemas/CloudintegrationtypesAWSAccountConfig'
azure:
$ref: '#/components/schemas/CloudintegrationtypesUpdatableAzureAccountConfig'
type: object
CloudintegrationtypesUpdatableAzureAccountConfig:
properties:
resourceGroups:
items:
type: string
type: array
required:
- resourceGroups
type: object
CloudintegrationtypesUpdatableService:
properties:
config:
@@ -7807,7 +7909,7 @@ paths:
summary: Patch role
tags:
- role
/api/v1/roles/{id}/relation/{relation}/objects:
/api/v1/roles/{id}/relations/{relation}/objects:
get:
deprecated: false
description: Gets all objects connected to the specified role via a given relation

View File

@@ -20,20 +20,23 @@ import (
)
type provider struct {
pkgAuthzService authz.AuthZ
openfgaServer *openfgaserver.Server
licensing licensing.Licensing
store authtypes.RoleStore
registry []authz.RegisterTypeable
config authz.Config
pkgAuthzService authz.AuthZ
openfgaServer *openfgaserver.Server
licensing licensing.Licensing
store authtypes.RoleStore
registry []authz.RegisterTypeable
settings factory.ScopedProviderSettings
onBeforeRoleDelete []authz.OnBeforeRoleDelete
}
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry ...authz.RegisterTypeable) factory.ProviderFactory[authz.AuthZ, authz.Config] {
return factory.NewProviderFactory(factory.MustNewName("openfga"), func(ctx context.Context, ps factory.ProviderSettings, config authz.Config) (authz.AuthZ, error) {
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, registry)
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, licensing, onBeforeRoleDelete, registry)
})
}
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, licensing licensing.Licensing, onBeforeRoleDelete []authz.OnBeforeRoleDelete, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
pkgOpenfgaAuthzProvider := pkgopenfgaauthz.NewProviderFactory(sqlstore, openfgaSchema, openfgaDataStore)
pkgAuthzService, err := pkgOpenfgaAuthzProvider.New(ctx, settings, config)
if err != nil {
@@ -45,12 +48,17 @@ func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings,
return nil, err
}
scopedSettings := factory.NewScopedProviderSettings(settings, "github.com/SigNoz/signoz/ee/authz/openfgaauthz")
return &provider{
pkgAuthzService: pkgAuthzService,
openfgaServer: openfgaServer,
licensing: licensing,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
registry: registry,
config: config,
pkgAuthzService: pkgAuthzService,
openfgaServer: openfgaServer,
licensing: licensing,
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
registry: registry,
settings: scopedSettings,
onBeforeRoleDelete: onBeforeRoleDelete,
}, nil
}
@@ -78,14 +86,18 @@ func (provider *provider) BatchCheck(ctx context.Context, tupleReq map[string]*o
return provider.openfgaServer.BatchCheck(ctx, tupleReq)
}
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
return provider.openfgaServer.ListObjects(ctx, subject, relation, typeable)
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
return provider.openfgaServer.ListObjects(ctx, subject, relation, objectType)
}
func (provider *provider) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
return provider.openfgaServer.Write(ctx, additions, deletions)
}
func (provider *provider) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
return provider.openfgaServer.ReadTuples(ctx, tupleKey)
}
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
return provider.pkgAuthzService.Get(ctx, orgID, id)
}
@@ -146,7 +158,7 @@ func (provider *provider) Create(ctx context.Context, orgID valuer.UUID, role *a
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
return provider.store.Create(ctx, role)
}
func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) (*authtypes.Role, error) {
@@ -163,10 +175,10 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
}
if existingRole != nil {
return authtypes.NewRoleFromStorableRole(existingRole), nil
return existingRole, nil
}
err = provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
err = provider.store.Create(ctx, role)
if err != nil {
return nil, err
}
@@ -175,14 +187,13 @@ func (provider *provider) GetOrCreate(ctx context.Context, orgID valuer.UUID, ro
}
func (provider *provider) GetResources(_ context.Context) []*authtypes.Resource {
typeables := make([]authtypes.Typeable, 0)
for _, register := range provider.registry {
typeables = append(typeables, register.MustGetTypeables()...)
}
typeables = append(typeables, provider.MustGetTypeables()...)
resources := make([]*authtypes.Resource, 0)
for _, typeable := range typeables {
for _, register := range provider.registry {
for _, typeable := range register.MustGetTypeables() {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
}
for _, typeable := range provider.MustGetTypeables() {
resources = append(resources, &authtypes.Resource{Name: typeable.Name(), Type: typeable.Type()})
}
@@ -201,21 +212,23 @@ func (provider *provider) GetObjects(ctx context.Context, orgID valuer.UUID, id
}
objects := make([]*authtypes.Object, 0)
for _, resource := range provider.GetResources(ctx) {
if slices.Contains(authtypes.TypeableRelations[resource.Type], relation) {
resourceObjects, err := provider.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
relation,
authtypes.MustNewTypeableFromType(resource.Type, resource.Name),
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
for _, objectType := range provider.getUniqueTypes() {
if !slices.Contains(authtypes.TypeableRelations[objectType], relation) {
continue
}
resourceObjects, err := provider.
ListObjects(
ctx,
authtypes.MustNewSubject(authtypes.TypeableRole, storableRole.Name, orgID, &authtypes.RelationAssignee),
relation,
objectType,
)
if err != nil {
return nil, err
}
objects = append(objects, resourceObjects...)
}
return objects, nil
@@ -227,7 +240,7 @@ func (provider *provider) Patch(ctx context.Context, orgID valuer.UUID, role *au
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
return provider.store.Update(ctx, orgID, authtypes.NewStorableRoleFromRole(role))
return provider.store.Update(ctx, orgID, role)
}
func (provider *provider) PatchObjects(ctx context.Context, orgID valuer.UUID, name string, relation authtypes.Relation, additions, deletions []*authtypes.Object) error {
@@ -260,17 +273,26 @@ func (provider *provider) Delete(ctx context.Context, orgID valuer.UUID, id valu
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
}
storableRole, err := provider.store.Get(ctx, orgID, id)
role, err := provider.store.Get(ctx, orgID, id)
if err != nil {
return err
}
role := authtypes.NewRoleFromStorableRole(storableRole)
err = role.ErrIfManaged()
if err != nil {
return err
}
for _, cb := range provider.onBeforeRoleDelete {
if err := cb(ctx, orgID, id); err != nil {
return err
}
}
if err := provider.deleteTuples(ctx, role.Name, orgID); err != nil {
return errors.WithAdditionalf(err, "failed to delete tuples for the role: %s", role.Name)
}
return provider.store.Delete(ctx, orgID, id)
}
@@ -346,3 +368,62 @@ func (provider *provider) getManagedRoleTransactionTuples(orgID valuer.UUID) ([]
return tuples, nil
}
func (provider *provider) deleteTuples(ctx context.Context, roleName string, orgID valuer.UUID) error {
subject := authtypes.MustNewSubject(authtypes.TypeableRole, roleName, orgID, &authtypes.RelationAssignee)
tuples := make([]*openfgav1.TupleKey, 0)
for _, objectType := range provider.getUniqueTypes() {
typeTuples, err := provider.ReadTuples(ctx, &openfgav1.ReadRequestTupleKey{
User: subject,
Object: objectType.StringValue() + ":",
})
if err != nil {
return err
}
tuples = append(tuples, typeTuples...)
}
if len(tuples) == 0 {
return nil
}
for idx := 0; idx < len(tuples); idx += provider.config.OpenFGA.MaxTuplesPerWrite {
end := idx + provider.config.OpenFGA.MaxTuplesPerWrite
if end > len(tuples) {
end = len(tuples)
}
err := provider.Write(ctx, nil, tuples[idx:end])
if err != nil {
return err
}
}
return nil
}
func (provider *provider) getUniqueTypes() []authtypes.Type {
seen := make(map[string]struct{})
uniqueTypes := make([]authtypes.Type, 0)
for _, register := range provider.registry {
for _, typeable := range register.MustGetTypeables() {
typeKey := typeable.Type().StringValue()
if _, ok := seen[typeKey]; ok {
continue
}
seen[typeKey] = struct{}{}
uniqueTypes = append(uniqueTypes, typeable.Type())
}
}
for _, typeable := range provider.MustGetTypeables() {
typeKey := typeable.Type().StringValue()
if _, ok := seen[typeKey]; ok {
continue
}
seen[typeKey] = struct{}{}
uniqueTypes = append(uniqueTypes, typeable.Type())
}
return uniqueTypes
}

View File

@@ -110,10 +110,14 @@ func (server *Server) BatchCheck(ctx context.Context, tupleReq map[string]*openf
return server.pkgAuthzService.BatchCheck(ctx, tupleReq)
}
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
return server.pkgAuthzService.ListObjects(ctx, subject, relation, typeable)
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
return server.pkgAuthzService.ListObjects(ctx, subject, relation, objectType)
}
func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey, deletions []*openfgav1.TupleKey) error {
return server.pkgAuthzService.Write(ctx, additions, deletions)
}
func (server *Server) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
return server.pkgAuthzService.ReadTuples(ctx, tupleKey)
}

View File

@@ -286,6 +286,8 @@
// Prevents useStore.getState() - export standalone actions instead
"signoz/no-navigator-clipboard": "error",
// Prevents navigator.clipboard - use useCopyToClipboard hook instead (disabled in tests via override)
"signoz/no-raw-absolute-path": "warn",
// Prevents window.open(path), window.location.origin + path, window.location.href = path
"no-restricted-imports": [
"error",
{
@@ -597,8 +599,9 @@
"rules": {
"import/first": "off",
// Should ignore due to mocks
"signoz/no-navigator-clipboard": "off"
// Tests can use navigator.clipboard directly
"signoz/no-navigator-clipboard": "off",
// Tests can use navigator.clipboard directly,
"signoz/no-raw-absolute-path":"off"
}
},
{

View File

@@ -1,3 +1,4 @@
// oxlint-disable-next-line typescript/no-require-imports
const path = require('path');
module.exports = {

View File

@@ -1,29 +0,0 @@
# SigNoz E2E Test Plan
This directory contains the structured test plan for the SigNoz application. Each subfolder corresponds to a main module or feature area, and contains scenario files for all user journeys, edge cases, and cross-module flows. These documents serve as the basis for generating Playwright MCP-driven E2E tests.
## Structure
- Each main module (e.g., logs, traces, dashboards, alerts, settings, etc.) has its own folder or markdown file.
- Each file contains detailed scenario templates, including preconditions, step-by-step actions, and expected outcomes.
- Use these documents to write, review, and update test cases as the application evolves.
## Folders & Files
- `logs/` — Logs module scenarios
- `traces/` — Traces module scenarios
- `metrics/` — Metrics module scenarios
- `dashboards/` — Dashboards module scenarios
- `alerts/` — Alerts module scenarios
- `services/` — Services module scenarios
- `settings/` — Settings and all sub-settings scenarios
- `onboarding/` — Onboarding and signup flows
- `navigation/` — Navigation, sidebar, and cross-module flows
- `exceptions/` — Exception and error handling scenarios
- `external-apis/` — External API monitoring scenarios
- `messaging-queues/` — Messaging queue scenarios
- `infrastructure/` — Infrastructure monitoring scenarios
- `help-support/` — Help & support scenarios
- `user-preferences/` — User preferences and personalization scenarios
- `service-map/` — Service map scenarios
- `saved-views/` — Saved views scenarios

View File

@@ -1,16 +0,0 @@
# Settings Module Test Plan
This folder contains E2E test scenarios for the Settings module and all sub-settings.
## Scenario Categories
- General settings (org/workspace, branding, version info)
- Billing settings
- Members & SSO
- Custom domain
- Integrations
- Notification channels
- API keys
- Ingestion
- Account settings (profile, password, preferences)
- Keyboard shortcuts

View File

@@ -1,43 +0,0 @@
# Account Settings E2E Scenarios (Updated)
## 1. Update Name
- **Precondition:** User is logged in
- **Steps:**
1. Click 'Update name' button
2. Edit name field in the modal/dialog
3. Save changes
- **Expected:** Name is updated in the UI
## 2. Update Email
- **Note:** The email field is not editable in the current UI.
## 3. Reset Password
- **Precondition:** User is logged in
- **Steps:**
1. Click 'Reset password' button
2. Complete reset flow (modal/dialog or external flow)
- **Expected:** Password is reset
## 4. Toggle 'Adapt to my timezone'
- **Precondition:** User is logged in
- **Steps:**
1. Toggle 'Adapt to my timezone' switch
- **Expected:** Timezone adapts accordingly (UI feedback/confirmation should be checked)
## 5. Toggle Theme (Dark/Light)
- **Precondition:** User is logged in
- **Steps:**
1. Toggle theme radio buttons ('Dark', 'Light Beta')
- **Expected:** Theme changes
## 6. Toggle Sidebar Always Open
- **Precondition:** User is logged in
- **Steps:**
1. Toggle 'Keep the primary sidebar always open' switch
- **Expected:** Sidebar remains open/closed as per toggle

View File

@@ -1,26 +0,0 @@
# API Keys E2E Scenarios (Updated)
## 1. Create a New API Key
- **Precondition:** User is admin
- **Steps:**
1. Click 'New Key' button
2. Enter details in the modal/dialog
3. Click 'Save'
- **Expected:** API key is created and listed in the table
## 2. Revoke an API Key
- **Precondition:** API key exists
- **Steps:**
1. In the table, locate the API key row
2. Click the revoke/delete button (icon button in the Action column)
3. Confirm if prompted
- **Expected:** API key is revoked/removed from the table
## 3. View API Key Usage
- **Precondition:** API key exists
- **Steps:**
1. View the 'Last used' and 'Expired' columns in the table
- **Expected:** Usage data is displayed for each API key

View File

@@ -1,17 +0,0 @@
# Billing Settings E2E Scenarios (Updated)
## 1. View Billing Information
- **Precondition:** User is admin
- **Steps:**
1. Navigate to Billing Settings
2. Wait for the billing chart/data to finish loading
- **Expected:**
- Billing heading and subheading are displayed
- Usage/cost table is visible with columns: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
- "Download CSV" and "Manage Billing" buttons are present and enabled after loading
- Test clicking "Download CSV" and "Manage Billing" for expected behavior (e.g., file download, navigation, or modal)
> Note: If these features are expected to trigger specific flows, document the observed behavior for each button.

View File

@@ -1,18 +0,0 @@
# Custom Domain E2E Scenarios (Updated)
## 1. Add or Update Custom Domain
- **Precondition:** User is admin
- **Steps:**
1. Click 'Customize teams URL' button
2. In the 'Customize your teams URL' dialog, enter the preferred subdomain
3. Click 'Apply Changes'
- **Expected:** Domain is set/updated for the team (UI feedback/confirmation should be checked)
## 2. Verify Domain Ownership
- **Note:** No explicit 'Verify' button or flow is present in the current UI. If verification is required, it may be handled automatically or via support.
## 3. Remove a Custom Domain
- **Note:** No explicit 'Remove' button or flow is present in the current UI. The only available action is to update the subdomain.

View File

@@ -1,31 +0,0 @@
# General Settings E2E Scenarios
## 1. View General Settings
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to General Settings
- **Expected:** General settings are displayed
## 2. Update Organization/Workspace Name
- **Precondition:** User is admin
- **Steps:**
1. Edit organization/workspace name
2. Save changes
- **Expected:** Name is updated and visible
## 3. Update Logo or Branding
- **Precondition:** User is admin
- **Steps:**
1. Upload new logo/branding
2. Save changes
- **Expected:** Branding is updated
## 4. View Version/Build Info
- **Precondition:** User is logged in
- **Steps:**
1. View version/build info section
- **Expected:** Version/build info is displayed

View File

@@ -1,20 +0,0 @@
# Ingestion E2E Scenarios (Updated)
## 1. View Ingestion Sources
- **Precondition:** User is admin
- **Steps:**
1. Navigate to the Integrations page
- **Expected:** List of available data sources/integrations is displayed
## 2. Configure Ingestion Sources
- **Precondition:** User is admin
- **Steps:**
1. Click 'Configure' for a data source/integration
2. Complete the configuration flow (modal or page, as available)
- **Expected:** Source is configured (UI feedback/confirmation should be checked)
## 3. Disable/Enable Ingestion
- **Note:** No visible enable/disable toggle for ingestion sources in the current UI. Ingestion is managed via the Integrations configuration flows.

View File

@@ -1,51 +0,0 @@
# Integrations E2E Scenarios (Updated)
## 1. View List of Available Integrations
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to Integrations
- **Expected:** List of integrations is displayed, each with a name, description, and 'Configure' button
## 2. Search Integrations by Name/Type
- **Precondition:** Integrations exist
- **Steps:**
1. Enter search/filter criteria in the 'Search for an integration...' box
- **Expected:** Only matching integrations are shown
## 3. Connect a New Integration
- **Precondition:** User is admin
- **Steps:**
1. Click 'Configure' for an integration
2. Complete the configuration flow (modal or page, as available)
- **Expected:** Integration is connected/configured (UI feedback/confirmation should be checked)
## 4. Disconnect an Integration
- **Note:** No visible 'Disconnect' button in the main list. This may be available in the configuration flow for a connected integration.
## 5. Configure Integration Settings
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.
## 6. Test Integration Connection
- **Note:** No visible 'Test Connection' button in the main list. This may be available in the configuration flow.
## 7. View Integration Status/Logs
- **Note:** No visible status/logs section in the main list. This may be available in the configuration flow.
## 8. Filter Integrations by Category
- **Note:** No explicit category filter in the current UI, only a search box.
## 9. View Integration Documentation/Help
- **Note:** No visible 'Help/Docs' button in the main list. This may be available in the configuration flow.
## 10. Update Integration Configuration
- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration.

View File

@@ -1,19 +0,0 @@
# Keyboard Shortcuts E2E Scenarios (Updated)
## 1. View Keyboard Shortcuts
- **Precondition:** User is logged in
- **Steps:**
1. Navigate to Keyboard Shortcuts
- **Expected:** Shortcuts are displayed in categorized tables (Global, Logs Explorer, Query Builder, Dashboard)
## 2. Customize Keyboard Shortcuts (if supported)
- **Note:** Customization is not available in the current UI. Shortcuts are view-only.
## 3. Use Keyboard Shortcuts for Navigation/Actions
- **Precondition:** User is logged in
- **Steps:**
1. Use shortcut for navigation/action (e.g., shift+s for Services, cmd+enter for running query)
- **Expected:** Navigation/action is performed as per shortcut

View File

@@ -1,49 +0,0 @@
# Members & SSO E2E Scenarios (Updated)
## 1. Invite a New Member
- **Precondition:** User is admin
- **Steps:**
1. Click 'Invite Members' button
2. In the 'Invite team members' dialog, enter email address, name (optional), and select role
3. (Optional) Click 'Add another team member' to invite more
4. Click 'Invite team members' to send invite(s)
- **Expected:** Pending invite appears in the 'Pending Invites' table
## 2. Remove a Member
- **Precondition:** User is admin, member exists
- **Steps:**
1. In the 'Members' table, locate the member row
2. Click 'Delete' in the Action column
3. Confirm removal if prompted
- **Expected:** Member is removed from the table
## 3. Update Member Roles
- **Precondition:** User is admin, member exists
- **Steps:**
1. In the 'Members' table, locate the member row
2. Click 'Edit' in the Action column
3. Change role in the edit dialog/modal
4. Save changes
- **Expected:** Member role is updated in the table
## 4. Configure SSO
- **Precondition:** User is admin
- **Steps:**
1. In the 'Authenticated Domains' section, locate the domain row
2. Click 'Configure SSO' or 'Edit Google Auth' as available
3. Complete SSO provider configuration in the modal/dialog
4. Save settings
- **Expected:** SSO is configured for the domain
## 5. Login via SSO
- **Precondition:** SSO is configured
- **Steps:**
1. Log out from the app
2. On the login page, click 'Login with SSO'
3. Complete SSO login flow
- **Expected:** User is logged in via SSO

View File

@@ -1,39 +0,0 @@
# Notification Channels E2E Scenarios (Updated)
## 1. Add a New Notification Channel
- **Precondition:** User is admin
- **Steps:**
1. Click 'New Alert Channel' button
2. In the 'New Notification Channel' form, fill in required fields (Name, Type, Webhook URL, etc.)
3. (Optional) Toggle 'Send resolved alerts'
4. (Optional) Click 'Test' to send a test notification
5. Click 'Save' to add the channel
- **Expected:** Channel is added and listed in the table
## 2. Test Notification Channel
- **Precondition:** Channel is being created or edited
- **Steps:**
1. In the 'New Notification Channel' or 'Edit Notification Channel' form, click 'Test'
- **Expected:** Test notification is sent (UI feedback/confirmation should be checked)
## 3. Remove a Notification Channel
- **Precondition:** Channel is added
- **Steps:**
1. In the table, locate the channel row
2. Click 'Delete' in the Action column
3. Confirm removal if prompted
- **Expected:** Channel is removed from the table
## 4. Update Notification Channel Settings
- **Precondition:** Channel is added
- **Steps:**
1. In the table, locate the channel row
2. Click 'Edit' in the Action column
3. In the 'Edit Notification Channel' form, update fields as needed
4. (Optional) Click 'Test' to send a test notification
5. Click 'Save' to update the channel
- **Expected:** Settings are updated

View File

@@ -1,199 +0,0 @@
# SigNoz Test Plan Validation Report
This report documents the validation of the E2E test plan against the current live application using Playwright MCP. Each module is reviewed for coverage, gaps, and required updates.
---
## Home Module
- **Coverage:**
- Widgets for logs, traces, metrics, dashboards, alerts, services, saved views, onboarding checklist
- Quick access buttons: Explore Logs, Create dashboard, Create an alert
- **Gaps/Updates:**
- Add scenarios for checklist interactions (e.g., “Ill do this later”, progress tracking)
- Add scenarios for Saved Views and cross-module links
- Add scenario for onboarding checklist completion
---
## Logs Module
- **Coverage:**
- Explorer, Pipelines, Views tabs
- Filtering by service, environment, severity, host, k8s, etc.
- Search, save view, create alert, add to dashboard, export, view mode switching
- **Gaps/Updates:**
- Add scenario for quick filter customization
- Add scenario for “Old Explorer” button
- Add scenario for frequency chart toggle
- Add scenario for “Stage & Run Query” workflow
---
## Traces Module
- **Coverage:**
- Tabs: Explorer, Funnels, Views
- Filtering by name, error status, duration, environment, function, service, RPC, status code, HTTP, trace ID, etc.
- Search, save view, create alert, add to dashboard, export, view mode switching (List, Traces, Time Series, Table)
- Pagination, quick filter customization, group by, aggregation
- **Gaps/Updates:**
- Add scenario for quick filter customization
- Add scenario for “Stage & Run Query” workflow
- Add scenario for all view modes (List, Traces, Time Series, Table)
- Add scenario for group by/aggregation
- Add scenario for trace detail navigation (clicking on trace row)
- Add scenario for Funnels tab (create/edit/delete funnel)
- Add scenario for Views tab (manage saved views)
---
## Metrics Module
- **Coverage:**
- Tabs: Summary, Explorer, Views
- Filtering by metric, type, unit, etc.
- Search, save view, add to dashboard, export, view mode switching (chart, table, proportion view)
- Pagination, group by, aggregation, custom queries
- **Gaps/Updates:**
- Add scenario for Proportion View in Summary
- Add scenario for all view modes (chart, table, proportion)
- Add scenario for group by/aggregation
- Add scenario for custom queries in Explorer
- Add scenario for Views tab (manage saved views)
---
## Dashboards Module
- **Coverage:**
- List, search, and filter dashboards
- Create new dashboard (button and template link)
- Edit, delete, and view dashboard details
- Add/edit/delete widgets (implied by dashboard detail)
- Pagination through dashboards
- **Gaps/Updates:**
- Add scenario for browsing dashboard templates (external link)
- Add scenario for requesting new template
- Add scenario for dashboard owner and creation info
- Add scenario for dashboard tags and filtering by tags
- Add scenario for dashboard sharing (if available)
- Add scenario for dashboard image/preview
---
## Messaging Queues Module
- **Coverage:**
- Overview tab: queue metrics, filters (Service Name, Span Name, Msg System, Destination, Kind)
- Search across all columns
- Pagination of queue data
- Sync and Share buttons
- Tabs for Kafka and Celery
- **Gaps/Updates:**
- Add scenario for Kafka tab (detailed metrics, actions)
- Add scenario for Celery tab (detailed metrics, actions)
- Add scenario for filter combinations and edge cases
- Add scenario for sharing queue data
- Add scenario for time range selection
---
## External APIs Module
- **Coverage:**
- Accessed via side navigation under MORE
- Explorer tab: domain, endpoints, last used, rate, error %, avg. latency
- Filters: Deployment Environment, Service Name, Rpc Method, Show IP addresses
- Table pagination
- Share and Stage & Run Query buttons
- **Gaps/Updates:**
- Add scenario for customizing quick filters
- Add scenario for running and staging queries
- Add scenario for sharing API data
- Add scenario for edge cases in filters and table data
---
## Alerts Module
- **Coverage:**
- Alert Rules tab: list, search, create (New Alert), edit, delete, enable/disable, severity, labels, actions
- Triggered Alerts tab (visible in tablist)
- Configuration tab (visible in tablist)
- Table pagination
- **Gaps/Updates:**
- Add scenario for triggered alerts (view, acknowledge, resolve)
- Add scenario for alert configuration (settings, integrations)
- Add scenario for edge cases in alert creation and management
- Add scenario for searching and filtering alerts
---
## Integrations Module
- **Coverage:**
- Integrations tab: list, search, configure (e.g., AWS), request new integration
- One-click setup for AWS monitoring
- Request more integrations (form)
- **Gaps/Updates:**
- Add scenario for configuring integrations (step-by-step)
- Add scenario for searching and filtering integrations
- Add scenario for requesting new integrations
- Add scenario for edge cases (e.g., failed configuration)
---
## Exceptions Module
- **Coverage:**
- All Exceptions: list, search, filter (Deployment Environment, Service Name, Host Name, K8s Cluster/Deployment/Namespace, Net Peer Name)
- Table: Exception Type, Error Message, Count, Last Seen, First Seen, Application
- Pagination
- Exception detail links
- Share and Stage & Run Query buttons
- **Gaps/Updates:**
- Add scenario for exception detail view
- Add scenario for advanced filtering and edge cases
- Add scenario for sharing and running queries
- Add scenario for error grouping and navigation
---
## Service Map Module
- **Coverage:**
- Service Map visualization (main graph)
- Filters: environment, resource attributes
- Time range selection
- Sync and Share buttons
- **Gaps/Updates:**
- Add scenario for interacting with the map (zoom, pan, select service)
- Add scenario for filtering and edge cases
- Add scenario for sharing the map
- Add scenario for time range and environment combinations
---
## Billing Module
- **Coverage:**
- Billing overview: cost monitoring, invoices, CSV download (disabled), manage billing (disabled)
- Teams Cloud section
- Billing table: Unit, Data Ingested, Price per Unit, Cost (Billing period to date)
- **Gaps/Updates:**
- Add scenario for invoice download and management (when enabled)
- Add scenario for cost monitoring and edge cases
- Add scenario for billing table data validation
- Add scenario for permissions and access control
---
## Usage Explorer Module
- **Status:**
- Not accessible in the current environment. Removing from test plan flows.
---
## [Next modules will be filled as validation proceeds]

View File

@@ -1,42 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Account Settings - View and Assert Static Controls', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Assert General section and controls (confirmed by DOM)
await expect(
page.getByLabel('My Settings').getByText('General'),
).toBeVisible();
await expect(page.getByText('Manage your account settings.')).toBeVisible();
await expect(page.getByRole('button', { name: 'Update name' })).toBeVisible();
await expect(
page.getByRole('button', { name: 'Reset password' }),
).toBeVisible();
// Assert User Preferences section and controls (confirmed by DOM)
await expect(page.getByText('User Preferences')).toBeVisible();
await expect(
page.getByText('Tailor the SigNoz console to work according to your needs.'),
).toBeVisible();
await expect(page.getByText('Select your theme')).toBeVisible();
const themeSelector = page.getByTestId('theme-selector');
await expect(themeSelector.getByText('Dark')).toBeVisible();
await expect(themeSelector.getByText('Light')).toBeVisible();
await expect(themeSelector.getByText('System')).toBeVisible();
await expect(page.getByTestId('timezone-adaptation-switch')).toBeVisible();
await expect(page.getByTestId('side-nav-pinned-switch')).toBeVisible();
});

View File

@@ -1,42 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('API Keys Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click API Keys tab in the settings sidebar (by data-testid)
await page.getByTestId('api-keys').click();
// Assert heading and subheading
await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible();
await expect(
page.getByText('Create and manage API keys for the SigNoz API'),
).toBeVisible();
// Assert presence of New Key button
const newKeyBtn = page.getByRole('button', { name: 'New Key' });
await expect(newKeyBtn).toBeVisible();
// Assert table columns
await expect(page.getByText('Last used').first()).toBeVisible();
await expect(page.getByText('Expired').first()).toBeVisible();
// Assert at least one API key row with action buttons
// Select the first action cell's first button (icon button)
const firstActionCell = page.locator('table tr').nth(1).locator('td').last();
const deleteBtn = firstActionCell.locator('button').first();
await expect(deleteBtn).toBeVisible();
});

View File

@@ -1,71 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
// E2E: Billing Settings - View Billing Information and Button Actions
test('View Billing Information and Button Actions', async ({
page,
context,
}) => {
// Ensure user is logged in
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Billing tab in the settings sidebar (by data-testid)
await page.getByTestId('billing').click();
// Wait for billing chart/data to finish loading
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert visibility of subheading (unique)
await expect(
page.getByText(
'Manage your billing information, invoices, and monitor costs.',
),
).toBeVisible();
// Assert visibility of Teams Cloud heading
await expect(page.getByRole('heading', { name: 'Teams Cloud' })).toBeVisible();
// Assert presence of summary and detailed tables
await expect(page.getByText('TOTAL SPENT')).toBeVisible();
await expect(page.getByText('Data Ingested')).toBeVisible();
await expect(page.getByText('Price per Unit')).toBeVisible();
await expect(page.getByText('Cost (Billing period to date)')).toBeVisible();
// Assert presence of alert and note
await expect(
page.getByText('Your current billing period is from', { exact: false }),
).toBeVisible();
await expect(
page.getByText('Billing metrics are updated once every 24 hours.'),
).toBeVisible();
// Test Download CSV button
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'cloud-download Download CSV' }).click(),
]);
// Optionally, check download file name
expect(download.suggestedFilename()).toContain('billing_usage');
// Test Manage Billing button (opens Stripe in new tab)
const [newPage] = await Promise.all([
context.waitForEvent('page'),
page.getByTestId('header-billing-button').click(),
]);
await newPage.waitForLoadState();
expect(newPage.url()).toContain('stripe.com');
await newPage.close();
});

View File

@@ -1,52 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Custom Domain Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Custom Domain tab in the settings sidebar (by data-testid)
await page.getByTestId('custom-domain').click();
// Wait for custom domain chart/data to finish loading
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert heading and subheading
await expect(
page.getByRole('heading', { name: 'Custom Domain Settings' }),
).toBeVisible();
await expect(
page.getByText('Personalize your workspace domain effortlessly.'),
).toBeVisible();
// Assert presence of Customize teams URL button
const customizeBtn = page.getByRole('button', {
name: 'Customize teams URL',
});
await expect(customizeBtn).toBeVisible();
await customizeBtn.click();
// Assert modal/dialog fields and buttons
await expect(
page.getByRole('dialog', { name: 'Customize your teams URL' }),
).toBeVisible();
await expect(page.getByLabel('Teams URL subdomain')).toBeVisible();
await expect(
page.getByRole('button', { name: 'Apply Changes' }),
).toBeVisible();
await expect(page.getByRole('button', { name: 'Close' })).toBeVisible();
// Close the modal
await page.getByRole('button', { name: 'Close' }).click();
});

View File

@@ -1,32 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('View General Settings', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click General tab in the settings sidebar (by data-testid)
await page.getByTestId('general').click();
// Wait for General tab to be visible
await page.getByRole('tabpanel', { name: 'General' }).waitFor();
// Assert visibility of definitive/static elements
await expect(page.getByRole('heading', { name: 'Metrics' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Traces' })).toBeVisible();
await expect(page.getByRole('heading', { name: 'Logs' })).toBeVisible();
await expect(page.getByText('Please')).toBeVisible();
await expect(page.getByRole('link', { name: 'email us' })).toBeVisible();
});

View File

@@ -1,48 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Ingestion Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Ingestion tab in the settings sidebar (by data-testid)
await page.getByTestId('ingestion').click();
// Assert heading and subheading (Integrations page)
await expect(
page.getByRole('heading', { name: 'Integrations' }),
).toBeVisible();
await expect(
page.getByText('Manage Integrations for this workspace'),
).toBeVisible();
// Assert presence of search box
await expect(
page.getByPlaceholder('Search for an integration...'),
).toBeVisible();
// Assert at least one data source with Configure button
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
await expect(configureBtn).toBeVisible();
// Assert Request more integrations section
await expect(
page.getByText(
"Can't find what youre looking for? Request more integrations",
),
).toBeVisible();
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
});

View File

@@ -1,48 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Integrations Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Integrations tab in the settings sidebar (by data-testid)
await page.getByTestId('integrations').click();
// Assert heading and subheading
await expect(
page.getByRole('heading', { name: 'Integrations' }),
).toBeVisible();
await expect(
page.getByText('Manage Integrations for this workspace'),
).toBeVisible();
// Assert presence of search box
await expect(
page.getByPlaceholder('Search for an integration...'),
).toBeVisible();
// Assert at least one integration with Configure button
const configureBtn = page.getByRole('button', { name: 'Configure' }).first();
await expect(configureBtn).toBeVisible();
// Assert Request more integrations section
await expect(
page.getByText(
"Can't find what youre looking for? Request more integrations",
),
).toBeVisible();
await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible();
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();
});

View File

@@ -1,56 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Members & SSO Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Members & SSO tab in the settings sidebar (by data-testid)
await page.getByTestId('members-sso').click();
// Assert headings and tables
await expect(
page.getByRole('heading', { name: /Members \(\d+\)/ }),
).toBeVisible();
await expect(
page.getByRole('heading', { name: /Pending Invites \(\d+\)/ }),
).toBeVisible();
await expect(
page.getByRole('heading', { name: 'Authenticated Domains' }),
).toBeVisible();
// Assert Invite Members button is visible and clickable
const inviteBtn = page.getByRole('button', { name: /Invite Members/ });
await expect(inviteBtn).toBeVisible();
await inviteBtn.click();
// Assert Invite Members modal/dialog appears (modal title is unique)
await expect(page.getByText('Invite team members').first()).toBeVisible();
// Close the modal (use unique 'Close' button)
await page.getByRole('button', { name: 'Close' }).click();
// Assert Edit and Delete buttons are present for at least one member
const editBtn = page.getByRole('button', { name: /Edit/ }).first();
const deleteBtn = page.getByRole('button', { name: /Delete/ }).first();
await expect(editBtn).toBeVisible();
await expect(deleteBtn).toBeVisible();
// Assert Add Domains button is visible
await expect(page.getByRole('button', { name: /Add Domains/ })).toBeVisible();
// Assert Configure SSO or Edit Google Auth button is visible for at least one domain
const ssoBtn = page
.getByRole('button', { name: /Configure SSO|Edit Google Auth/ })
.first();
await expect(ssoBtn).toBeVisible();
});

View File

@@ -1,57 +0,0 @@
import { expect, test } from '@playwright/test';
import { ensureLoggedIn } from '../../../utils/login.util';
test('Notification Channels Settings - View and Interact', async ({ page }) => {
await ensureLoggedIn(page);
// 1. Open the sidebar settings menu using data-testid
await page.getByTestId('settings-nav-item').click();
// 2. Click Account Settings in the dropdown (by role/name or data-testid if available)
await page.getByRole('menuitem', { name: 'Account Settings' }).click();
// Assert the main tabpanel/heading (confirmed by DOM)
await expect(page.getByTestId('settings-page-title')).toBeVisible();
// Focus on the settings page sidenav
await page.getByTestId('settings-page-sidenav').focus();
// Click Notification Channels tab in the settings sidebar (by data-testid)
await page.getByTestId('notification-channels').click();
// Wait for loading to finish
await page.getByText('loading').first().waitFor({ state: 'hidden' });
// Assert presence of New Alert Channel button
const newChannelBtn = page.getByRole('button', { name: /New Alert Channel/ });
await expect(newChannelBtn).toBeVisible();
// Assert table columns
await expect(page.getByText('Name')).toBeVisible();
await expect(page.getByText('Type')).toBeVisible();
await expect(page.getByText('Action')).toBeVisible();
// Click New Alert Channel and assert modal fields/buttons
await newChannelBtn.click();
await expect(
page.getByRole('heading', { name: 'New Notification Channel' }),
).toBeVisible();
await expect(page.getByLabel('Name')).toBeVisible();
await expect(page.getByLabel('Type')).toBeVisible();
await expect(page.getByLabel('Webhook URL')).toBeVisible();
await expect(
page.getByRole('switch', { name: 'Send resolved alerts' }),
).toBeVisible();
await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Test' })).toBeVisible();
await expect(page.getByRole('button', { name: 'Back' })).toBeVisible();
// Close modal
await page.getByRole('button', { name: 'Back' }).click();
// Assert Edit and Delete buttons for at least one channel
const editBtn = page.getByRole('button', { name: 'Edit' }).first();
const deleteBtn = page.getByRole('button', { name: 'Delete' }).first();
await expect(editBtn).toBeVisible();
await expect(deleteBtn).toBeVisible();
});

View File

@@ -1,35 +0,0 @@
import { Page } from '@playwright/test';
// Read credentials from environment variables
const username = process.env.LOGIN_USERNAME;
const password = process.env.LOGIN_PASSWORD;
const baseURL = process.env.BASE_URL;
/**
* Ensures the user is logged in. If not, performs the login steps.
* Follows the MCP process step-by-step.
*/
export async function ensureLoggedIn(page: Page): Promise<void> {
// if already in home page, return
if (await page.url().includes('/home')) {
return;
}
if (!username || !password) {
throw new Error(
'E2E_EMAIL and E2E_PASSWORD environment variables must be set.',
);
}
await page.goto(`${baseURL}/login`);
await page.getByTestId('email').click();
await page.getByTestId('email').fill(username);
await page.getByTestId('initiate_login').click();
await page.getByTestId('password').click();
await page.getByTestId('password').fill(password);
await page.getByRole('button', { name: 'Login' }).click();
await page
.getByText('Hello there, Welcome to your')
.waitFor({ state: 'visible' });
}

View File

@@ -2,6 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<base href="[[.BaseHref]]" />
<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate, max-age: 0"
@@ -59,7 +60,7 @@
<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" />
<link data-react-helmet="true" rel="shortcut icon" href="favicon.ico" />
</head>
<body data-theme="default">
<script>
@@ -136,7 +137,7 @@
})(document, 'script');
}
</script>
<link rel="stylesheet" href="/css/uPlot.min.css" />
<link rel="stylesheet" href="css/uPlot.min.css" />
<script type="module" src="./src/index.tsx"></script>
</body>
</html>

View File

@@ -44,7 +44,6 @@
"@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",

View File

@@ -1,95 +0,0 @@
import { defineConfig, devices } from '@playwright/test';
import dotenv from 'dotenv';
import path from 'path';
// Read from ".env" file.
dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e/tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Run tests in parallel even in CI - optimized for GitHub Actions free tier */
workers: process.env.CI ? 2 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL:
process.env.SIGNOZ_E2E_BASE_URL || 'https://app.us.staging.signoz.cloud',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
colorScheme: 'dark',
locale: 'en-US',
viewport: { width: 1280, height: 720 },
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: {
launchOptions: { args: ['--start-maximized'] },
viewport: null,
colorScheme: 'dark',
locale: 'en-US',
baseURL: 'https://app.us.staging.signoz.cloud',
trace: 'on-first-retry',
},
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});

View File

@@ -0,0 +1,152 @@
/**
* Rule: no-raw-absolute-path
*
* Catches patterns that break at runtime when the app is served from a
* sub-path (e.g. /signoz/):
*
* 1. window.open(path, '_blank')
* → use openInNewTab(path) which calls withBasePath internally
*
* 2. window.location.origin + path / `${window.location.origin}${path}`
* → use getAbsoluteUrl(path)
*
* 3. frontendBaseUrl: window.location.origin (bare origin usage)
* → use getBaseUrl() to include the base path
*
* 4. window.location.href = path
* → use withBasePath(path) or navigate() for internal navigation
*
* External URLs (first arg starts with "http") are explicitly allowed.
*/
function isOriginAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'origin' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isHrefAccess(node) {
return (
node.type === 'MemberExpression' &&
!node.computed &&
node.property.name === 'href' &&
node.object.type === 'MemberExpression' &&
!node.object.computed &&
node.object.property.name === 'location' &&
node.object.object.type === 'Identifier' &&
node.object.object.name === 'window'
);
}
function isExternalUrl(node) {
if (node.type === 'Literal' && typeof node.value === 'string') {
return node.value.startsWith('http://') || node.value.startsWith('https://');
}
if (node.type === 'TemplateLiteral' && node.quasis.length > 0) {
const raw = node.quasis[0].value.raw;
return raw.startsWith('http://') || raw.startsWith('https://');
}
return false;
}
// window.open(withBasePath(x)) and window.open(getAbsoluteUrl(x)) are already safe.
function isSafeHelperCall(node) {
return (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'withBasePath' || node.callee.name === 'getAbsoluteUrl')
);
}
export default {
meta: {
type: 'suggestion',
docs: {
description:
'Disallow raw window.open and origin-concatenation patterns that miss the runtime base path',
category: 'Base Path Safety',
},
schema: [],
messages: {
windowOpen:
'Use openInNewTab(path) instead of window.open(path, "_blank") — openInNewTab prepends the base path automatically.',
originConcat:
'Use getAbsoluteUrl(path) instead of window.location.origin + path — getAbsoluteUrl prepends the base path automatically.',
originDirect:
'Use getBaseUrl() instead of window.location.origin — getBaseUrl includes the base path.',
hrefAssign:
'Use withBasePath(path) or navigate() instead of window.location.href = path — ensures the base path is included.',
},
},
create(context) {
return {
// window.open(path, ...) — allow only external first-arg URLs
CallExpression(node) {
const { callee, arguments: args } = node;
if (
callee.type !== 'MemberExpression' ||
callee.object.type !== 'Identifier' ||
callee.object.name !== 'window' ||
callee.property.name !== 'open'
)
{return;}
if (args.length === 0) {return;}
if (isExternalUrl(args[0])) {return;}
if (isSafeHelperCall(args[0])) {return;}
context.report({ node, messageId: 'windowOpen' });
},
// window.location.origin + path
BinaryExpression(node) {
if (node.operator !== '+') {return;}
if (isOriginAccess(node.left) || isOriginAccess(node.right)) {
context.report({ node, messageId: 'originConcat' });
}
},
// `${window.location.origin}${path}`
TemplateLiteral(node) {
if (node.expressions.some(isOriginAccess)) {
context.report({ node, messageId: 'originConcat' });
}
},
// window.location.origin used directly (not in concatenation)
// Catches: frontendBaseUrl: window.location.origin
MemberExpression(node) {
if (!isOriginAccess(node)) {return;}
const parent = node.parent;
// Skip if parent is BinaryExpression with + (handled by BinaryExpression visitor)
if (parent.type === 'BinaryExpression' && parent.operator === '+') {return;}
// Skip if inside TemplateLiteral (handled by TemplateLiteral visitor)
if (parent.type === 'TemplateLiteral') {return;}
context.report({ node, messageId: 'originDirect' });
},
// window.location.href = path
AssignmentExpression(node) {
if (node.operator !== '=') {return;}
if (!isHrefAccess(node.left)) {return;}
// Allow external URLs
if (isExternalUrl(node.right)) {return;}
// Allow safe helper calls
if (isSafeHelperCall(node.right)) {return;}
context.report({ node, messageId: 'hrefAssign' });
},
};
},
};

View File

@@ -8,6 +8,7 @@
import noZustandGetStateInHooks from './rules/no-zustand-getstate-in-hooks.mjs';
import noNavigatorClipboard from './rules/no-navigator-clipboard.mjs';
import noUnsupportedAssetPattern from './rules/no-unsupported-asset-pattern.mjs';
import noRawAbsolutePath from './rules/no-raw-absolute-path.mjs';
export default {
meta: {
@@ -17,5 +18,6 @@ export default {
'no-zustand-getstate-in-hooks': noZustandGetStateInHooks,
'no-navigator-clipboard': noNavigatorClipboard,
'no-unsupported-asset-pattern': noUnsupportedAssetPattern,
'no-raw-absolute-path': noRawAbsolutePath,
},
};

View File

@@ -1,16 +0,0 @@
RULE: All test code for this repo must be generated by following the step-by-step Playwright MCP process as described below.
- You are a playwright test generator.
- You are given a scenario and you need to generate a playwright test for it.
- Use login util if not logged in.
- DO NOT generate test code based on the scenario alone.
- DO run steps one by one using the tools provided by the Playwright MCP.
- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history
- Gather correct selectors before writing the test
- DO NOT valiate for dynamic content in the tests, only validate for the correctness with meta data
- Always inspect the DOM at each navigation or interaction step to determine the correct selector for the next action. Do not assume selectors, confirm via inspection before proceeding.
- Assert visibility of definitive/static elements in the UI (such as labels, headings, or section titles) rather than dynamic values or content that may change between runs.
- Save generated test file in the tests directory
- Execute the test file and iterate until the test passes

View File

@@ -2,6 +2,7 @@ import { initReactI18next } from 'react-i18next';
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import Backend from 'i18next-http-backend';
import { getBasePath } from 'utils/basePath';
import cacheBursting from '../../i18n-translations-hash.json';
@@ -24,7 +25,7 @@ i18n
const ns = namespace[0];
const pathkey = `/${language}/${ns}`;
const hash = cacheBursting[pathkey as keyof typeof cacheBursting] || '';
return `/locales/${language}/${namespace}.json?h=${hash}`;
return `${getBasePath()}locales/${language}/${namespace}.json?h=${hash}`;
},
},
react: {

View File

@@ -471,7 +471,7 @@ export const getObjects = (
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetObjects200>({
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
method: 'GET',
signal,
});
@@ -481,7 +481,7 @@ export const getGetObjectsQueryKey = ({
id,
relation,
}: GetObjectsPathParameters) => {
return [`/api/v1/roles/${id}/relation/${relation}/objects`] as const;
return [`/api/v1/roles/${id}/relations/${relation}/objects`] as const;
};
export const getGetObjectsQueryOptions = <
@@ -574,7 +574,7 @@ export const patchObjects = (
authtypesPatchableObjectsDTO: BodyType<AuthtypesPatchableObjectsDTO>,
) => {
return GeneratedAPIInstance<string>({
url: `/api/v1/roles/${id}/relation/${relation}/objects`,
url: `/api/v1/roles/${id}/relations/${relation}/objects`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: authtypesPatchableObjectsDTO,

View File

@@ -795,7 +795,8 @@ export interface CloudintegrationtypesAccountDTO {
}
export interface CloudintegrationtypesAccountConfigDTO {
aws: CloudintegrationtypesAWSAccountConfigDTO;
aws?: CloudintegrationtypesAWSAccountConfigDTO;
azure?: CloudintegrationtypesAzureAccountConfigDTO;
}
/**
@@ -829,6 +830,86 @@ export interface CloudintegrationtypesAssetsDTO {
dashboards?: CloudintegrationtypesDashboardDTO[] | null;
}
export interface CloudintegrationtypesAzureAccountConfigDTO {
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
resourceGroups: string[];
}
export interface CloudintegrationtypesAzureConnectionArtifactDTO {
/**
* @type string
*/
cliCommand: string;
/**
* @type string
*/
cloudPowerShellCommand: string;
}
export interface CloudintegrationtypesAzureIntegrationConfigDTO {
/**
* @type string
*/
deploymentRegion: string;
/**
* @type array
*/
resourceGroups: string[];
/**
* @type array
*/
telemetryCollectionStrategy: CloudintegrationtypesAzureTelemetryCollectionStrategyDTO[];
}
export interface CloudintegrationtypesAzureLogsCollectionStrategyDTO {
/**
* @type array
*/
categoryGroups: string[];
}
export interface CloudintegrationtypesAzureMetricsCollectionStrategyDTO {
[key: string]: unknown;
}
export interface CloudintegrationtypesAzureServiceConfigDTO {
logs: CloudintegrationtypesAzureServiceLogsConfigDTO;
metrics: CloudintegrationtypesAzureServiceMetricsConfigDTO;
}
export interface CloudintegrationtypesAzureServiceLogsConfigDTO {
/**
* @type boolean
*/
enabled?: boolean;
}
export interface CloudintegrationtypesAzureServiceMetricsConfigDTO {
/**
* @type boolean
*/
enabled?: boolean;
}
export interface CloudintegrationtypesAzureTelemetryCollectionStrategyDTO {
logs?: CloudintegrationtypesAzureLogsCollectionStrategyDTO;
metrics?: CloudintegrationtypesAzureMetricsCollectionStrategyDTO;
/**
* @type string
*/
resourceProvider: string;
/**
* @type string
*/
resourceType: string;
}
/**
* @nullable
*/
@@ -890,7 +971,8 @@ export interface CloudintegrationtypesCollectedMetricDTO {
}
export interface CloudintegrationtypesConnectionArtifactDTO {
aws: CloudintegrationtypesAWSConnectionArtifactDTO;
aws?: CloudintegrationtypesAWSConnectionArtifactDTO;
azure?: CloudintegrationtypesAzureConnectionArtifactDTO;
}
export interface CloudintegrationtypesCredentialsDTO {
@@ -1074,7 +1156,8 @@ export interface CloudintegrationtypesPostableAccountDTO {
}
export interface CloudintegrationtypesPostableAccountConfigDTO {
aws: CloudintegrationtypesAWSPostableAccountConfigDTO;
aws?: CloudintegrationtypesAWSPostableAccountConfigDTO;
azure?: CloudintegrationtypesAzureAccountConfigDTO;
}
/**
@@ -1109,7 +1192,8 @@ export interface CloudintegrationtypesPostableAgentCheckInDTO {
}
export interface CloudintegrationtypesProviderIntegrationConfigDTO {
aws: CloudintegrationtypesAWSIntegrationConfigDTO;
aws?: CloudintegrationtypesAWSIntegrationConfigDTO;
azure?: CloudintegrationtypesAzureIntegrationConfigDTO;
}
export interface CloudintegrationtypesServiceDTO {
@@ -1137,7 +1221,8 @@ export interface CloudintegrationtypesServiceDTO {
}
export interface CloudintegrationtypesServiceConfigDTO {
aws: CloudintegrationtypesAWSServiceConfigDTO;
aws?: CloudintegrationtypesAWSServiceConfigDTO;
azure?: CloudintegrationtypesAzureServiceConfigDTO;
}
export enum CloudintegrationtypesServiceIDDTO {
@@ -1154,6 +1239,8 @@ export enum CloudintegrationtypesServiceIDDTO {
s3sync = 's3sync',
sns = 'sns',
sqs = 'sqs',
storageaccountsblob = 'storageaccountsblob',
cdnprofile = 'cdnprofile',
}
export interface CloudintegrationtypesServiceMetadataDTO {
/**
@@ -1186,11 +1273,24 @@ export interface CloudintegrationtypesSupportedSignalsDTO {
}
export interface CloudintegrationtypesTelemetryCollectionStrategyDTO {
aws: CloudintegrationtypesAWSTelemetryCollectionStrategyDTO;
aws?: CloudintegrationtypesAWSTelemetryCollectionStrategyDTO;
azure?: CloudintegrationtypesAzureTelemetryCollectionStrategyDTO;
}
export interface CloudintegrationtypesUpdatableAccountDTO {
config: CloudintegrationtypesAccountConfigDTO;
config: CloudintegrationtypesUpdatableAccountConfigDTO;
}
export interface CloudintegrationtypesUpdatableAccountConfigDTO {
aws?: CloudintegrationtypesAWSAccountConfigDTO;
azure?: CloudintegrationtypesUpdatableAzureAccountConfigDTO;
}
export interface CloudintegrationtypesUpdatableAzureAccountConfigDTO {
/**
* @type array
*/
resourceGroups: string[];
}
export interface CloudintegrationtypesUpdatableServiceDTO {

View File

@@ -1,5 +1,6 @@
import {
interceptorRejected,
interceptorsRequestBasePath,
interceptorsRequestResponse,
interceptorsResponse,
} from 'api';
@@ -17,6 +18,7 @@ export const GeneratedAPIInstance = <T>(
return generatedAPIAxiosInstance({ ...config }).then(({ data }) => data);
};
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
generatedAPIAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
generatedAPIAxiosInstance.interceptors.response.use(
interceptorsResponse,

View File

@@ -11,6 +11,7 @@ import axios, {
import { ENVIRONMENT } from 'constants/env';
import { Events } from 'constants/events';
import { LOCALSTORAGE } from 'constants/localStorage';
import { getBasePath } from 'utils/basePath';
import { eventEmitter } from 'utils/getEventEmitter';
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4, apiV5 } from './apiV1';
@@ -67,6 +68,39 @@ export const interceptorsRequestResponse = (
return value;
};
// Strips the leading '/' from path and joins with base — idempotent if already prefixed.
// e.g. prependBase('/signoz/', '/api/v1/') → '/signoz/api/v1/'
function prependBase(base: string, path: string): string {
return path.startsWith(base) ? path : base + path.slice(1);
}
// Prepends the runtime base path to outgoing requests so API calls work under
// a URL prefix (e.g. /signoz/api/v1/…). No-op for root deployments and dev
// (dev baseURL is a full http:// URL, not an absolute path).
export const interceptorsRequestBasePath = (
value: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const basePath = getBasePath();
if (basePath === '/') {
return value;
}
if (value.baseURL?.startsWith('/')) {
// Production relative baseURL: '/api/v1/' → '/signoz/api/v1/'
value.baseURL = prependBase(basePath, value.baseURL);
} else if (value.baseURL?.startsWith('http')) {
// Dev absolute baseURL (VITE_FRONTEND_API_ENDPOINT): 'https://host/api/v1/' → 'https://host/signoz/api/v1/'
const url = new URL(value.baseURL);
url.pathname = prependBase(basePath, url.pathname);
value.baseURL = url.toString();
} else if (!value.baseURL && value.url?.startsWith('/')) {
// Orval-generated client (empty baseURL, path in url): '/api/signoz/v1/rules' → '/signoz/api/signoz/v1/rules'
value.url = prependBase(basePath, value.url);
}
return value;
};
export const interceptorRejected = async (
value: AxiosResponse<any>,
): Promise<AxiosResponse<any>> => {
@@ -133,6 +167,7 @@ const instance = axios.create({
});
instance.interceptors.request.use(interceptorsRequestResponse);
instance.interceptors.request.use(interceptorsRequestBasePath);
instance.interceptors.response.use(interceptorsResponse, interceptorRejected);
export const AxiosAlertManagerInstance = axios.create({
@@ -147,6 +182,7 @@ ApiV2Instance.interceptors.response.use(
interceptorRejected,
);
ApiV2Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV2Instance.interceptors.request.use(interceptorsRequestBasePath);
// axios V3
export const ApiV3Instance = axios.create({
@@ -158,6 +194,7 @@ ApiV3Instance.interceptors.response.use(
interceptorRejected,
);
ApiV3Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV3Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V4
@@ -170,6 +207,7 @@ ApiV4Instance.interceptors.response.use(
interceptorRejected,
);
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV4Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios V5
@@ -182,6 +220,7 @@ ApiV5Instance.interceptors.response.use(
interceptorRejected,
);
ApiV5Instance.interceptors.request.use(interceptorsRequestResponse);
ApiV5Instance.interceptors.request.use(interceptorsRequestBasePath);
//
// axios Base
@@ -194,6 +233,7 @@ LogEventAxiosInstance.interceptors.response.use(
interceptorRejectedBase,
);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestResponse);
LogEventAxiosInstance.interceptors.request.use(interceptorsRequestBasePath);
//
AxiosAlertManagerInstance.interceptors.response.use(
@@ -201,6 +241,7 @@ AxiosAlertManagerInstance.interceptors.response.use(
interceptorRejected,
);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestResponse);
AxiosAlertManagerInstance.interceptors.request.use(interceptorsRequestBasePath);
export { apiV1 };
export default instance;

View File

@@ -74,7 +74,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -99,36 +99,6 @@
}
}
.lightMode {
.auth-error-container {
.error-content {
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
@keyframes horizontal-shaking {
0% {
transform: translateX(0);

View File

@@ -87,23 +87,3 @@
background: var(--l3-background);
flex-shrink: 0;
}
.lightMode {
.auth-footer-content {
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
}
.auth-footer-icon {
filter: brightness(0) saturate(100%) invert(25%) sepia(8%) saturate(518%)
hue-rotate(192deg) brightness(80%) contrast(95%);
opacity: 0.9;
}
.auth-footer-text {
color: var(--text-neutral-light-200);
}
.auth-footer-link-icon {
color: var(--text-neutral-light-100);
}
}

View File

@@ -143,28 +143,3 @@
}
}
}
.lightMode {
.bg-dot-pattern {
background: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 1px
);
background-size: 12px 12px;
}
.auth-page-gradient {
background: radial-gradient(
ellipse at center top,
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
transparent 60%
);
opacity: 0.8;
filter: blur(200px);
@media (min-width: 768px) {
filter: blur(300px);
}
}
}

View File

@@ -238,18 +238,7 @@
height: 2px;
bottom: 0;
left: 0;
background-color: var(--l1-foreground);
}
}
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
background: unset;
}
}
.configure-option-Info {
border: 1px dashed var(--bg-robin-400);
background-color: var(--l1-background);
color: var(--l1-foreground);
}
}

View File

@@ -12,6 +12,7 @@ import { AppState } from 'store/reducers';
import { Query, TagFilterItem } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, MetricAggregateOperator } from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { withBasePath } from 'utils/basePath';
export interface NavigateToExplorerProps {
filters: TagFilterItem[];
@@ -133,7 +134,7 @@ export function useNavigateToExplorer(): (
QueryParams.compositeQuery
}=${JSONCompositeQuery}`;
window.open(newExplorerPath, sameTab ? '_self' : '_blank');
window.open(withBasePath(newExplorerPath), sameTab ? '_self' : '_blank');
},
[
prepareQuery,

View File

@@ -77,11 +77,11 @@
width: 280px;
&::placeholder {
color: white;
color: var(--l1-foreground);
}
&:focus::placeholder {
color: rgba($color: #ffffff, $alpha: 0.4);
color: rgba($color: var(--l1-foreground), $alpha: 0.4);
}
}
}
@@ -113,42 +113,6 @@
}
}
.lightMode {
.time-options-container {
.time-options-item {
&.active {
background-color: rgba($color: #ffffff, $alpha: 0.2);
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
&:hover {
cursor: pointer;
background-color: rgba($color: #ffffff, $alpha: 0.3);
}
}
}
.timeSelection-input {
display: flex;
gap: 8px;
align-items: center;
padding: 4px 8px;
padding-left: 0px !important;
input::placeholder {
color: var(---bg-ink-300);
}
input:focus::placeholder {
color: rgba($color: #000000, $alpha: 0.4);
}
}
}
.date-time-popover__footer {
border-top: 1px solid var(--l1-border);
padding: 8px 14px;
@@ -300,34 +264,3 @@
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
.lightMode {
.timezone-container {
.timezone {
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--l1-foreground);
}
}
}
.custom-time-picker {
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
}
}
}
.timezone-badge {
background: rgb(179 179 179 / 15%);
}
.time-input-suffix-icon-badge {
background: rgb(179 179 179 / 15%);
&:hover {
background: rgb(179 179 179 / 20%);
}
}
}

View File

@@ -129,20 +129,3 @@ $item-spacing: 8px;
width: 15px;
}
}
.lightMode {
.timezone-picker {
&__search {
.search-icon {
stroke: var(--l1-foreground);
}
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--l1-foreground);
}
}
}
}

View File

@@ -1,9 +1,5 @@
.dropdown-button {
color: #fff;
}
.dropdown-button--dark {
color: #000;
color: var(--l1-foreground);
}
.dropdown-icon {

View File

@@ -1,7 +1,6 @@
import { useState } from 'react';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import './DropDown.styles.scss';
@@ -12,8 +11,6 @@ function DropDown({
element: JSX.Element[];
onDropDownItemClick?: MenuProps['onClick'];
}): JSX.Element {
const isDarkMode = useIsDarkMode();
const items: MenuProps['items'] = element.map(
(e: JSX.Element, index: number) => ({
label: e,
@@ -35,7 +32,7 @@ function DropDown({
>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
className={`dropdown-button`}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);

View File

@@ -196,17 +196,3 @@
opacity: 0.5;
}
}
.lightMode {
.members-table {
.ant-table-tbody {
> tr.members-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -167,22 +167,3 @@
padding-right: 8px;
}
}
.lightMode {
.config-btn {
&.missing-config-btn {
background: var(--bg-amber-100);
color: var(--bg-amber-500);
&:hover {
color: var(--bg-amber-600) !important;
}
}
.missing-config-btn {
.config-btn-content {
border-right: 1px solid var(--bg-amber-600);
}
}
}
}

View File

@@ -16,7 +16,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Base styles are for dark mode
@@ -48,10 +48,6 @@ $custom-border-color: #2c3044;
visibility: visible !important;
pointer-events: none;
z-index: 2;
.lightMode & {
color: rgba(0, 0, 0, 0.85) !important;
}
}
&.ant-select-focused .ant-select-selection-placeholder {
@@ -67,10 +63,6 @@ $custom-border-color: #2c3044;
color: var(--l2-foreground);
z-index: 1;
pointer-events: none;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
}
.ant-select-selector {
@@ -114,7 +106,7 @@ $custom-border-color: #2c3044;
}
.ant-select-selection-placeholder {
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l2-foreground) 45%, transparent);
}
// Customize tags in multiselect (dark mode by default)
@@ -180,7 +172,9 @@ $custom-border-color: #2c3044;
.custom-multiselect-dropdown-container {
z-index: 1050 !important;
padding: 0;
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.5), 0 6px 16px 0 rgba(0, 0, 0, 0.4),
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.5),
0 6px 16px 0 rgba(0, 0, 0, 0.4),
0 9px 28px 8px rgba(0, 0, 0, 0.3);
background-color: var(--l2-background);
border: 1px solid var(--l1-border);
@@ -215,7 +209,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
}
@@ -573,7 +567,7 @@ $custom-border-color: #2c3044;
.empty-message {
padding: 12px;
text-align: center;
color: color-mix(in srgb, var(--border) 45%, transparent);
color: color-mix(in srgb, var(--l1-foreground) 45%, transparent);
}
.status-message {
@@ -804,8 +798,10 @@ $custom-border-color: #2c3044;
.custom-multiselect-dropdown-container {
background-color: var(--l1-background);
border: 1px solid var(--l1-border);
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
box-shadow:
0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08),
0 9px 28px 8px rgba(0, 0, 0, 0.05);
.empty-message {
color: var(--l2-foreground);
@@ -976,11 +972,9 @@ $custom-border-color: #2c3044;
font-weight: 500;
z-index: 2;
pointer-events: none;
transition: opacity 0.2s ease, visibility 0.2s ease;
.lightMode & {
color: rgba(0, 0, 0, 0.85);
}
transition:
opacity 0.2s ease,
visibility 0.2s ease;
}
&:focus-within .all-text {

View File

@@ -249,57 +249,6 @@
}
}
.lightMode {
.query-aggregation-container {
.aggregation-container {
.query-aggregation-select-container {
.query-aggregation-select-editor {
.cm-editor {
.cm-tooltip-autocomplete {
background: var(--l1-background) !important;
border: 1px solid var(--l1-border) !important;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
backdrop-filter: none;
ul {
li {
&:hover,
&[aria-selected='true'] {
background: var(--l3-background) !important;
}
}
}
}
.cm-line {
::-moz-selection {
background: var(--l2-background) !important;
opacity: 0.5 !important;
}
::selection {
background: var(--l1-background) !important;
opacity: 0.5 !important;
}
.cm-function {
color: var(--primary-background) !important;
}
}
}
}
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-background);
border: none;
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1);
}
}
}
.query-aggregation-error-popover {
.ant-popover-inner {
background-color: var(--l1-border);

View File

@@ -121,44 +121,3 @@
}
}
}
.lightMode {
.qb-trace-operator {
&-arrow {
&::before {
background: repeating-linear-gradient(
to right,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
&::after {
background-color: var(--l3-background);
}
}
&.non-list-view {
&::before {
background: repeating-linear-gradient(
to bottom,
var(--l3-background),
var(--l3-background) 4px,
transparent 4px,
transparent 8px
);
}
}
&-label-with-input {
border: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
.label {
color: var(--l1-foreground) !important;
border-right: 1px solid var(--l1-border) !important;
background: var(--l1-background) !important;
}
}
}
}

View File

@@ -152,7 +152,7 @@
width: 100%;
height: 100%;
background: radial-gradient(circle, #fff 10%, transparent 0);
background: radial-gradient(circle, var(--l1-foreground) 10%, transparent 0);
background-size: 12px 12px;
opacity: 1;

View File

@@ -197,17 +197,3 @@
background-color: var(--l1-border);
}
}
.lightMode {
.sa-table {
.ant-table-tbody {
> tr.sa-table-row--tinted > td {
background: rgba(0, 0, 0, 0.015);
}
> tr:hover > td {
background: rgba(0, 0, 0, 0.03) !important;
}
}
}
}

View File

@@ -169,9 +169,12 @@
gap: 3px;
background: var(--l1-border);
border-radius: 20px;
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
0px 66px 18px 0px rgba(0, 0, 0, 0.01), 0px 37px 22px 0px rgba(0, 0, 0, 0.03),
0px 17px 17px 0px rgba(0, 0, 0, 0.04), 0px 4px 9px 0px rgba(0, 0, 0, 0.04);
box-shadow:
0px 103px 12px 0px rgba(0, 0, 0, 0.01),
0px 66px 18px 0px rgba(0, 0, 0, 0.01),
0px 37px 22px 0px rgba(0, 0, 0, 0.03),
0px 17px 17px 0px rgba(0, 0, 0, 0.04),
0px 4px 9px 0px rgba(0, 0, 0, 0.04);
}
&__scroll-hint-text {
@@ -183,29 +186,3 @@
letter-spacing: -0.06px;
}
}
.lightMode {
.warning-content {
&__warning-code {
color: var(--l2-foreground);
}
&__warning-message {
color: var(--l1-foreground);
}
&__message-item {
color: var(--l1-foreground);
}
&__message-badge {
&-label-text {
color: var(--l1-foreground);
}
.key-value-label__value {
color: var(--l1-foreground);
}
}
&__docs-button {
background: var(--l1-background);
color: var(--l2-foreground);
}
}
}

View File

@@ -157,9 +157,3 @@
.view-all-drawer {
border-radius: 4px;
}
.lightMode {
.ant-table {
background: inherit;
}
}

View File

@@ -134,7 +134,9 @@
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
> th:not(:last-child):not(.ant-table-selection-column):not(
.ant-table-row-expand-icon-cell
):not([colspan])::before {
background-color: transparent;
}
@@ -224,54 +226,3 @@
}
}
}
.lightMode {
.api-quick-filter-left-section {
.api-quick-filters-header {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.api-module-right-section {
.toolbar {
border-bottom: 1px solid var(--bg-vanilla-300);
}
}
.no-filtered-domains-message-container {
.no-filtered-domains-message-content {
.no-filtered-domains-message {
.no-domain-title {
color: var(--l1-foreground);
}
.no-domain-subtitle {
color: var(--l2-foreground);
.attribute {
font-family: 'Space Mono';
}
}
}
}
}
.api-monitoring-domain-list-table {
.ant-table {
.ant-table-cell {
color: var(--l1-foreground);
}
.table-row-light {
background: none;
}
.table-row-dark {
background: none;
}
.round-metric-tag {
color: var(--l1-foreground);
}
}
}
}

View File

@@ -28,7 +28,7 @@
}
.dashboard-title {
color: #fff;
color: var(--l1-foreground);
font-family: Inter;
font-size: 16px;
font-style: normal;
@@ -463,135 +463,3 @@
}
}
}
.lightMode {
.dashboard-description-container {
color: var(--l1-foreground);
.dashboard-details {
.left-section {
.dashboard-title {
color: var(--l1-foreground);
}
}
.right-section {
.icons {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
.configure-button {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
}
}
}
.dashboard-description-section {
color: var(--l1-foreground);
}
}
.dashboard-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
}
.menu-content {
display: flex;
flex-direction: column;
.section-1 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-bottom: 1px solid var(--l1-border);
.ant-btn {
color: var(--l1-foreground);
}
}
}
}
.rename-dashboard {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.dashboard-content {
.name-text {
color: var(--l1-foreground);
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
.section-naming {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.section-naming-content {
.name-text {
color: var(--l1-foreground);
}
.section-name-input {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}
}
.ant-modal-footer {
.dashboard-rename {
.cancel-btn {
background: var(--l3-background);
}
}
}
}
}
}

View File

@@ -141,58 +141,3 @@
}
}
}
.lightMode {
.overview-content {
.overview-settings {
border: 1px solid var(--l1-border);
.name-icon-input {
.dashboard-image-input {
.ant-select-selector {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name-input {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.dashboard-name {
color: var(--l1-foreground);
}
.description-text-area {
border: 1px solid var(--l1-border);
background: var(--l3-background);
}
}
.overview-settings-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.unsaved {
.unsaved-dot {
background: var(--primary-background);
}
.unsaved-changes {
color: var(--bg-robin-400);
}
}
.footer-action-btns {
.discard-btn {
color: var(--l1-foreground);
background-color: var(--l3-background);
}
.save-btn {
color: var(--l3-background);
}
}
}
}
}

View File

@@ -100,27 +100,6 @@
max-width: 500px;
}
.lightMode {
.variable-item {
.variable-name {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--bg-robin-300);
}
.variable-value {
border: 1px solid var(--l1-border);
background: var(--l1-background);
color: var(--l1-foreground);
&:hover,
&:focus-within {
outline: 1px solid var(--bg-robin-400);
}
}
}
}
.cycle-error-alert {
margin-bottom: 12px;
padding: 4px 12px;

View File

@@ -116,50 +116,3 @@
}
}
}
.lightMode {
.panel-type-selection-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.panel-selection {
.selected {
background: var(--l2-background);
}
.ant-card {
border: 1px solid var(--l1-border);
.ant-card-body {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-btn {
color: var(--l1-foreground);
background: var(--primary-background);
}
}
}
}
}

View File

@@ -61,11 +61,3 @@
width: 14px;
}
}
.lightMode {
.dashboard-breadcrumbs {
.dashboard-btn {
color: var(--l1-foreground);
}
}
}

View File

@@ -57,32 +57,3 @@
}
}
}
.lightMode {
.download-logs-popover {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: linear-gradient(
139deg,
color-mix(in srgb, var(--card) 80%, transparent) 0%,
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
.download-logs-content {
.action-btns {
color: var(--l1-foreground);
}
.action-btns:hover {
&.ant-btn-text {
background-color: var(--l3-background) !important;
}
}
.export-heading {
color: var(--l2-foreground);
}
}
}
}
}

View File

@@ -119,41 +119,6 @@
}
}
.lightMode {
.main-container {
.plot-tag {
background: var(--l3-background);
}
}
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-confirm-title {
color: var(--l1-foreground);
}
.ant-modal-confirm-content {
.ant-typography {
color: var(--l1-foreground);
}
}
.ant-modal-confirm-btns {
button:nth-of-type(1) {
background-color: var(--l3-background);
border: none;
color: var(--l1-foreground);
}
}
}
.info-help-btns {
.doc-redirection-btn {
color: var(--bg-aqua-600) !important;
border-color: var(--bg-aqua-600) !important;
}
}
}
.create-notification-btn {
box-shadow: none;
}

View File

@@ -174,23 +174,3 @@
}
}
}
.lightMode {
.dashboard-empty-state {
.dashboard-content {
.heading {
.icons {
color: var(--l1-foreground);
}
.welcome {
color: var(--l1-foreground);
}
.welcome-info {
color: var(--l1-foreground);
}
}
}
}
}

View File

@@ -333,100 +333,3 @@
}
}
}
.lightMode {
.fullscreen-grid-container {
.react-grid-layout {
.row-panel {
background: var(--l2-background);
.settings-icon {
color: var(--l1-foreground);
}
.row-icon {
color: var(--l1-foreground);
}
.section-title {
color: var(--l1-foreground);
}
}
}
}
.widget-full-view {
.ant-modal-content {
background-color: var(--l1-foreground);
.ant-modal-header {
background-color: var(--l1-foreground);
}
}
}
.row-settings {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.menu-content {
.section-1 {
.rename-btn {
color: var(--l1-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
.remove-section {
color: var(--bg-cherry-400);
}
}
}
}
}
.rename-section {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
border-bottom: 1px solid var(--l1-border);
.ant-modal-title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.typography {
color: var(--l2-foreground);
}
.ant-form-item {
.action-btns {
.cancel-btn {
color: var(--l1-foreground);
background: var(--l3-background);
}
}
}
}
}
}
.view-onclick-show-button {
background: var(--l2-background);
border-color: var(--l1-foreground);
color: var(--l1-foreground);
.menu-item {
&:hover {
background-color: var(--l2-foreground);
}
}
}
}

View File

@@ -55,14 +55,6 @@
padding-right: 0.25rem;
}
.lightMode {
.widget-header-container {
.ant-input-group-addon {
background-color: inherit;
}
}
}
.long-tooltip {
.ant-tooltip-content {
max-height: 500px;

View File

@@ -227,7 +227,9 @@
.ant-typography {
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on;
font-feature-settings:
'dlig' on,
'salt' on;
font-family: Inter;
font-size: 11px !important;
font-style: normal;
@@ -480,10 +482,10 @@
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
&.selected {
background: var(--l2-background);
background: var(--l3-background);
color: var(--primary);
}
}
}

View File

@@ -21,9 +21,9 @@ import { DataSource } from 'types/common/queryBuilder';
import { USER_ROLES } from 'types/roles';
import floppyDiscUrl from '@/assets/Icons/floppy-disc.svg';
import logsUrl from '@/assets/Icons/logs.svg';
import { getItemIcon } from '../constants';
import { ScrollText } from '@signozhq/icons';
export default function SavedViews({
onUpdateChecklistDoneItem,
@@ -54,17 +54,20 @@ export default function SavedViews({
isError: metricsViewsError,
} = useGetAllViews(DataSource.METRICS);
const logsViews = useMemo(() => [...(logsViewsData?.data.data || [])], [
logsViewsData,
]);
const logsViews = useMemo(
() => [...(logsViewsData?.data.data || [])],
[logsViewsData],
);
const tracesViews = useMemo(() => [...(tracesViewsData?.data.data || [])], [
tracesViewsData,
]);
const tracesViews = useMemo(
() => [...(tracesViewsData?.data.data || [])],
[tracesViewsData],
);
const metricsViews = useMemo(() => [...(metricsViewsData?.data.data || [])], [
metricsViewsData,
]);
const metricsViews = useMemo(
() => [...(metricsViewsData?.data.data || [])],
[metricsViewsData],
);
useEffect(() => {
if (selectedEntity === 'logs') {
@@ -348,7 +351,7 @@ export default function SavedViews({
className={selectedEntity === 'logs' ? 'selected tab' : 'tab'}
onClick={(): void => handleTabChange('logs')}
>
<img src={logsUrl} alt="logs-icon" className="logs-icon" />
<ScrollText size={14} />
Logs
</Button>
<Button

View File

@@ -132,7 +132,9 @@
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
> th:not(:last-child):not(.ant-table-selection-column):not(
.ant-table-row-expand-icon-cell
):not([colspan])::before {
background-color: transparent;
}
@@ -146,50 +148,3 @@
}
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l1-foreground);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}

View File

@@ -192,63 +192,6 @@
}
}
.lightMode {
.ant-drawer-header {
border-bottom: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.entity-detail-drawer {
.title {
color: var(--l2-foreground);
}
.entity-detail-drawer__entity {
.ant-typography {
color: var(--l2-foreground);
background: transparent;
}
}
.radio-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.views-tabs {
.tab {
background: var(--l1-foreground);
}
.selected_view {
background: var(--l3-background);
border: 1px solid var(--l1-border);
color: var(--l2-foreground);
}
.selected_view::before {
background: var(--l3-background);
border-left: 1px solid var(--l1-border);
}
}
.compass-button {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.tabs-and-search {
.action-btn {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
color: var(--l2-foreground);
}
}
}
}
.entity-metric-traces {
margin-top: 1rem;
@@ -382,7 +325,9 @@
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
> th:not(:last-child):not(.ant-table-selection-column):not(
.ant-table-row-expand-icon-cell
):not([colspan])::before {
background-color: transparent;
}
@@ -397,53 +342,6 @@
}
}
.lightMode {
.entity-metric-traces-header {
.filter-section {
border-top: 1px solid var(--l1-border);
border-bottom: 1px solid var(--l1-border);
.ant-select-selector {
border-color: var(--l1-border) !important;
color: var(--l2-foreground);
}
}
}
.entity-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--l1-border);
.ant-table-thead > tr > th {
background: var(--l1-foreground);
color: var(--l2-foreground);
}
.ant-table-thead > tr > th:has(.entityname-column-header) {
background: var(--l1-foreground);
}
.ant-table-cell {
background: var(--l1-foreground);
color: var(--l1-foreground);
}
.ant-table-cell:has(.entityname-column-value) {
background: var(--l1-foreground);
}
.entityname-column-value {
color: var(--l3-background);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}
.entity-metrics-logs-container {
margin-top: 1rem;

View File

@@ -202,7 +202,11 @@
letter-spacing: -0.07px;
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
}
}
@@ -251,7 +255,11 @@
> a {
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'case' on, 'cpsp' on;
font-feature-settings:
'dlig' on,
'salt' on,
'case' on,
'cpsp' on;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
@@ -765,206 +773,6 @@
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.lightMode {
.ingestion-key-container {
.ingestion-key-content {
.title {
color: var(--l1-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.column-render {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-collapse {
border: none;
.ant-collapse-header {
background: var(--l1-background);
}
.ant-collapse-content {
border-top: 1px solid var(--l1-border);
}
}
.title-with-action {
.ingestion-key-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.ingestion-key-value {
background: var(--l3-background);
.ant-typography {
color: var(--l2-foreground);
}
.copy-key-btn {
cursor: pointer;
}
}
.action-btn {
.ant-typography {
color: var(--l2-foreground);
}
}
}
.ingestion-key-details {
border-top: 1px solid var(--l1-border);
.ingestion-key-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.ingestion-key-created-by {
color: var(--l2-foreground);
}
.ingestion-key-last-used-at {
.ant-typography {
color: var(--l2-foreground);
}
}
}
}
}
}
}
.delete-ingestion-key-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.ant-modal-header {
background: var(--l1-background);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l2-foreground);
}
.ingestion-key-input {
.ant-input {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.ingestion-key-info-container {
.user-email {
background: var(--l3-background);
}
.limits-data {
border: 1px solid var(--l1-border);
}
}
.ingestion-key-modal {
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0;
.ant-modal-header {
background: none;
border-bottom: 1px solid var(--l1-border);
padding: 16px;
}
}
}
.ingestion-key-access-role {
.ant-radio-button-wrapper {
&.ant-radio-button-wrapper-checked {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&:hover {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
&::before {
background-color: var(--l3-background);
}
}
&:focus {
color: var(--l1-foreground);
background: var(--l3-background);
border-color: var(--l1-border);
}
}
}
.tab {
border: 1px solid var(--l1-border);
&::before {
background: var(--l3-background);
}
&.selected {
background: var(--l3-background);
}
}
}
.copyable-text {
background: var(--l3-background);
}
.ingestion-key-expires-at {
border: 1px solid var(--l1-border);
background: var(--l1-background);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.expires-at .ant-picker {
border-color: var(--l1-border) !important;
}
}
.mt-8 {
margin-top: 8px;
}
@@ -1077,26 +885,3 @@
color: var(--l2-foreground);
}
}
.lightMode {
.ingestion-setup-details-links {
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
color: var(--accent-primary);
.learn-more {
color: var(--accent-primary);
}
}
.ingestion-url-error-tooltip {
.ingestion-url-error-content {
.ingestion-url-error-code {
color: var(--bg-amber-500);
}
}
.ingestion-url-error-message {
color: var(--l1-foreground);
}
}
}

View File

@@ -9,6 +9,8 @@
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
text-align: center;
font-family: Inter;
font-size: 13px;
@@ -54,38 +56,3 @@
}
}
}
.lightMode {
.licenses-page {
.licenses-page-header {
border-bottom: 1px solid var(--l1-border);
background: var(--card);
backdrop-filter: blur(20px);
.licenses-page-header-title {
color: var(--l1-foreground);
background: var(--l1-background);
border-right: 1px solid var(--l1-border);
}
}
.licenses-page-content-container {
.licenses-page-content {
background: var(--l1-background);
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l3-background);
}
}
}
}
}

View File

@@ -176,15 +176,3 @@
cursor: pointer;
}
}
.lightMode {
.info-text {
color: var(--bg-robin-600) !important;
}
.info-link-container {
.anticon {
color: var(--bg-robin-400);
}
}
}

View File

@@ -224,7 +224,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -249,7 +253,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -273,7 +281,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -307,7 +319,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -328,7 +344,11 @@
> a {
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'case' on, 'cpsp' on;
font-feature-settings:
'dlig' on,
'salt' on,
'case' on,
'cpsp' on;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
@@ -932,7 +952,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12.805px;
font-style: normal;
@@ -966,7 +990,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12.805px;
font-style: normal;
@@ -988,7 +1016,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12.805px;
font-style: normal;
@@ -1022,7 +1054,11 @@
color: var(--l2-foreground);
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
font-feature-settings:
'dlig' on,
'salt' on,
'cpsp' on,
'case' on;
font-family: Inter;
font-size: 12.805px;
font-style: normal;
@@ -1078,305 +1114,6 @@
}
}
.lightMode {
.dashboards-list-container {
.dashboards-list-view-content {
.title {
color: var(--l1-foreground);
}
.subtitle {
color: var(--muted-foreground);
}
.ant-table-row {
.ant-table-cell {
background: var(--l1-background);
}
&:hover {
.ant-table-cell {
background: var(--l1-background) !important;
}
}
.dashboard-list-item {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-title {
color: var(--l2-foreground);
.title {
color: var(--l1-foreground);
}
}
.title-with-action {
.dashboard-title {
.ant-typography {
color: var(--l1-foreground);
}
}
.action-btn {
.ant-typography {
color: var(--l1-foreground);
}
}
}
.dashboard-details {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.created-by {
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.updated-by {
.text {
color: var(--l2-foreground);
}
.dashboard-tag {
background: var(--l3-background);
.tag-text {
color: var(--l1-foreground);
}
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
.dashboard-created-by {
color: var(--l1-foreground);
}
.dashboard-created-at {
.ant-typography {
color: var(--l1-foreground);
}
}
}
}
}
}
.no-search {
.text {
color: var(--muted-foreground);
}
}
.all-dashboards-header {
border: 1px solid var(--l1-border);
background: var(--l2-background);
.typography {
color: var(--l2-foreground);
}
.right-actions {
color: var(--l2-foreground);
}
}
.dashboard-empty-state {
.text {
.no-dashboard {
color: var(--l2-foreground);
}
.info {
color: var(--l2-foreground);
}
}
}
.dashboard-error-state {
.error-text {
color: var(--muted-foreground);
}
.action-btns {
.retry-btn {
background: var(--l3-background);
color: var(--l1-foreground);
}
}
}
}
.delete-view-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
.title {
color: var(--l1-foreground);
}
}
.ant-modal-body {
.ant-typography {
color: var(--l1-foreground);
}
.save-view-input {
.ant-input {
background: var(--l2-background);
color: var(--l1-foreground);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--l3-background);
color: var(--l2-foreground);
}
}
}
}
.dashboard-actions {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.dashboard-action-content {
.section-1 {
.action-btn {
color: var(--l2-foreground);
}
}
.section-2 {
border-top: 1px solid var(--l1-border);
}
}
}
}
.sort-dashboards {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.sort-content {
.sort-heading {
color: var(--l2-foreground);
}
.sort-btns {
color: var(--l2-foreground);
}
}
}
}
.configure-group {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--card);
.configure-content {
.configure-btn {
color: var(--l2-foreground);
}
}
}
}
.configure-metadata-root {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--card);
.ant-modal-header {
background: var(--card);
border-bottom: 1px solid var(--l1-border);
}
.ant-modal-body {
.configure-content {
.configure-preview {
border: 0.915px solid var(--l1-border);
background: var(--l2-background);
.header {
.title {
color: var(--l2-foreground);
}
}
.details {
.createdAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
.updatedAt {
.formatted-time {
color: var(--l2-foreground);
}
.user {
.user-tag {
color: var(--l2-foreground);
background-color: var(--l3-background);
}
.dashboard-created-by {
color: var(--l2-foreground);
}
}
}
}
}
.metadata-action {
.connection-line {
border: 1px dashed var(--l1-border);
}
}
}
}
.ant-modal-footer {
.save-changes {
border: 1px solid var(--l1-border);
background: var(--l2-background);
}
}
}
}
}
.title-toolip {
.ant-tooltip-content {
.ant-tooltip-inner {

View File

@@ -194,76 +194,3 @@
border-top: 1px solid var(--l1-border);
}
}
.lightMode {
.new-dashboard-templates-modal {
.ant-modal-content {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.new-dashboard-templates-content-header {
border-bottom: 1px solid var(--l1-border);
}
.new-dashboard-templates-content {
.new-dashboard-templates-list {
border-right: 1px solid var(--l1-border);
.templates-list {
.template-list-item {
.template-name {
color: var(--l3-background);
}
&:hover {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
&.active {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
}
}
}
}
.new-dashboard-template-preview {
.template-preview-header {
.template-preview-title {
.template-preview-icon {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
.template-info {
.template-name {
color: var(--l3-background);
}
.template-description {
color: var(--l2-foreground);
}
}
}
.create-dashboard-btn {
.ant-btn {
box-shadow: none;
}
}
}
.template-preview-image {
img {
border: 1px solid var(--l1-border);
background: var(--l1-foreground);
}
}
}
}
.ant-modal-footer {
border-top: 1px solid var(--l1-border);
}
}
}

View File

@@ -7,9 +7,3 @@
.delete-btn:hover {
background-color: color-mix(in srgb, var(--l1-foreground) 12%, transparent);
}
.lightMode {
.delete-btn:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
}
}

View File

@@ -58,14 +58,3 @@
}
}
}
.lightMode {
.table-view-actions-content {
.ant-popover-inner {
border: 1px solid var(--l1-border);
background: var(--l1-background) !important;
backdrop-filter: none;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
}
}

View File

@@ -160,55 +160,6 @@
}
}
.lightMode {
.login-form-card {
background: var(--l2-background);
}
.login-error-container {
.error-content {
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
border-color: color-mix(in srgb, var(--danger-background) 20%, transparent);
&__error-code {
color: var(--l2-foreground);
}
&__error-message {
color: var(--l1-foreground);
}
&__docs-button {
color: var(--l1-foreground);
border-color: var(--l1-border);
background: transparent;
&:hover {
color: var(--l2-foreground);
border-color: var(--l1-border);
background: transparent;
}
}
&__message-badge-label-text {
color: var(--l2-foreground);
}
&__message-item {
color: var(--l1-foreground);
&::before {
background: var(--l3-background);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
}
}
}
}
.password-label-container {
display: flex;
justify-content: space-between;

View File

@@ -10,10 +10,6 @@ export const Label = styled.label`
color: var(--l1-foreground);
margin-bottom: 12px;
display: block;
.lightMode & {
color: var(--text-ink-500);
}
`;
export const FormContainer = styled(Form)`
@@ -50,20 +46,10 @@ export const FormContainer = styled(Form)`
background: var(--l3-background) !important;
border-color: var(--l1-border) !important;
color: var(--l1-foreground) !important;
.lightMode & {
background: var(--bg-vanilla-200) !important;
border-color: var(--bg-vanilla-300) !important;
color: var(--text-ink-500) !important;
}
}
& .ant-input::placeholder {
color: var(--l3-foreground) !important;
.lightMode & {
color: var(--text-neutral-light-200) !important;
}
}
& .ant-input:focus,

View File

@@ -323,50 +323,3 @@
}
}
}
.lightMode {
.logs-list-view-container {
.logs-list-table-view-container {
table {
thead {
tr {
th {
color: var(--l1-foreground) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
background-color: var(--l1-background) !important;
tr {
&:hover {
background-color: var(--l3-background) !important;
}
}
}
tbody {
tr {
&:hover {
background-color: var(--l3-background) !important;
}
border-bottom: 1px solid var(--l1-border) !important;
}
}
}
.sticky-header-table-container {
&::-webkit-scrollbar-thumb {
background: var(--l3-background);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--l1-background);
}
}
}
}
}

View File

@@ -102,7 +102,9 @@
font-size: 12px;
line-height: 16px;
font-weight: 500;
transition: background-color 120ms ease, color 120ms ease;
transition:
background-color 120ms ease,
color 120ms ease;
&:hover {
background: var(--l2-background-hover);
@@ -143,10 +145,12 @@
left: 50%;
width: 4px;
transform: translateX(-50%);
background: var(--l2-border);
background: var(--l2-background);
opacity: 1;
pointer-events: none;
transition: background 120ms ease, width 120ms ease;
transition:
background 120ms ease,
width 120ms ease;
}
.tanstack-header-cell.is-resizing .tanstack-resize-handle-line {

View File

@@ -1,6 +1,6 @@
.logs-table-virtuoso-scroll {
scrollbar-width: thin;
scrollbar-color: var(--bg-slate-300) transparent;
scrollbar-color: var(--l3-background) transparent;
&::-webkit-scrollbar {
width: 4px;
@@ -16,23 +16,11 @@
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
background: var(--l3-background);
border-radius: 9999px;
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
}
.lightMode .logs-table-virtuoso-scroll {
scrollbar-color: var(--bg-vanilla-300) transparent;
&::-webkit-scrollbar-thumb {
background: var(--bg-vanilla-300);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-vanilla-100);
background: var(--l3-background);
}
}

View File

@@ -263,24 +263,6 @@
}
}
.lightMode {
.logs-explorer-views-container {
.views-tabs-container {
.views-tabs {
.selected_view {
background: white;
color: var(--text-robin-400);
border: 1px solid var(--bg-robin-400);
}
.selected_view::before {
background: var(--bg-robin-400);
}
}
}
}
}
.order-by-container {
display: flex;
align-items: center;

View File

@@ -179,16 +179,6 @@
}
}
.lightMode {
.metrics-explorer-explore-container {
.explore-tabs {
.selected-view {
background: var(--l3-background);
}
}
}
}
.dashboards-and-alerts-popover-container {
display: flex;
gap: 16px;

View File

@@ -382,81 +382,6 @@
}
}
.lightMode {
.metric-details-header {
.ant-btn {
background-color: var(--bg-white-400);
}
}
.metric-details-content {
.metrics-accordion {
.metrics-accordion-header {
.action-menu {
.action-button {
.ant-typography {
color: var(--l1-border);
}
}
}
}
.metrics-accordion-content {
.metric-metadata-key {
.field-renderer-container {
.label {
color: var(--l2-foreground);
}
}
.all-attributes-key {
.all-attributes-contribution {
color: var(--l1-border);
}
}
}
.metric-metadata-value {
background-color: var(--l3-background);
.all-attributes-value {
.ant-btn {
background-color: var(--l2-foreground);
.ant-typography {
color: var(--l1-border);
}
&:hover {
background-color: var(--l1-foreground);
}
}
}
.field-renderer-container {
.label {
color: var(--l1-border);
}
}
}
}
}
}
.top-attributes-content {
.top-attributes-item-progress {
.top-attributes-item-progress-bar {
background-color: var(--bg-robin-400);
}
.top-attributes-item-count {
background-color: var(--bg-robin-300);
}
.top-attributes-item-key,
.top-attributes-item-count {
color: var(--l1-border);
}
}
}
}
.metric-metadata-value {
.y-axis-unit-selector-component {
.ant-select {
@@ -585,24 +510,6 @@
color: var(--bg-robin-400) !important;
}
.lightMode {
.attribute-key-popover-overlay,
.all-values-popover-overlay {
.ant-popover-inner {
border: 1px solid var(--l2-foreground);
background: var(--l1-foreground) !important;
}
}
.all-values-popover {
.all-values-item {
&:hover {
background: rgba(0, 0, 0, 0.04);
}
}
}
}
@keyframes fade-in-out {
0% {
opacity: 0;

View File

@@ -177,7 +177,9 @@
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
> th:not(:last-child):not(.ant-table-selection-column):not(
.ant-table-row-expand-icon-cell
):not([colspan])::before {
background-color: transparent;
}
@@ -228,29 +230,6 @@
}
}
.lightMode {
.metrics-table-container {
.ant-table {
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
.ant-pagination {
.ant-pagination-item {
&-active {
background: var(--primary-background);
border-color: var(--bg-robin-500);
a {
color: var(--l1-foreground) !important;
}
}
}
}
}
}
.metric-type-renderer {
border-radius: 50px;
max-height: 24px;

View File

@@ -57,28 +57,3 @@
}
}
}
.lightMode {
.license-section {
.license-section-header {
.license-section-title {
color: var(--l1-foreground);
}
}
.license-section-content {
.license-section-content-item {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.license-section-content-item-title-action {
color: var(--l1-foreground);
}
.license-section-content-item-description {
color: var(--l1-foreground);
}
}
}
}
}

View File

@@ -153,62 +153,3 @@
border-radius: 3px;
}
.lightMode {
.user-info-section {
.user-info-section-header {
.user-info-section-title {
color: var(--l1-foreground);
}
.user-info-section-subtitle {
color: var(--l1-foreground);
}
}
}
.user-preference-section {
.user-preference-section-header {
.user-preference-section-title {
color: var(--l1-foreground);
}
.user-preference-section-subtitle {
color: var(--l1-foreground);
}
}
.user-preference-section-content {
.user-preference-section-content-item {
border: 1px solid var(--l1-border);
background: var(--l1-background);
.user-preference-section-content-item-title-action {
color: var(--l1-foreground);
}
.user-preference-section-content-item-description {
color: var(--l1-foreground);
}
.auto-theme-info {
background: var(--l2-background);
border: 1px solid var(--l1-border);
.auto-theme-status {
color: var(--l1-foreground);
strong {
color: var(--accent-primary);
}
}
}
}
}
}
.reset-password-card {
border: 1px solid var(--l1-border);
background: var(--l1-background);
}
}

View File

@@ -12,7 +12,7 @@
align-items: center;
justify-content: center;
gap: 4px;
color: #fff;
color: var(--l1-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;

View File

@@ -32,17 +32,3 @@
justify-content: end;
}
}
.lightMode {
.widget-graph {
background-color: var(--l1-background);
background-image: radial-gradient(var(--l2-foreground) 1px, transparent 0);
background-size: 20px 20px;
.header {
.plot-tag {
background: var(--l3-background);
}
}
}
}

View File

@@ -38,11 +38,3 @@
gap: 12px;
}
}
.lightMode {
.column-unit-selector {
.heading {
color: var(--l2-background);
}
}
}

View File

@@ -68,25 +68,3 @@
border-top: 1px solid var(--l1-border);
}
}
.lightMode {
.context-link-form-container {
.url-parameters-section {
.parameter-row {
.delete-parameter-btn {
color: var(--l1-border);
&:hover {
color: var(--danger-background) !important;
border-color: var(--danger-background) !important;
background-color: var(--bg-cherry-100);
}
}
}
}
.context-link-footer {
border-top-color: var(--l1-border);
}
}
}

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