mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-23 20:30:31 +01:00
Compare commits
53 Commits
test/body-
...
feat/cloud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7f656bf5b | ||
|
|
04552fa2e9 | ||
|
|
535ee9900c | ||
|
|
8533a85dcf | ||
|
|
07e7fcac4b | ||
|
|
bc49a19f15 | ||
|
|
bb16d92176 | ||
|
|
c595506a09 | ||
|
|
2efa776de2 | ||
|
|
21ce7a663a | ||
|
|
e6e2f95ec2 | ||
|
|
afe85c48f9 | ||
|
|
97d4f29d3a | ||
|
|
7aa720a6e1 | ||
|
|
1296ae627d | ||
|
|
4771391b9d | ||
|
|
79e6485379 | ||
|
|
69f0508b15 | ||
|
|
481e94e5d2 | ||
|
|
4eadc17fd1 | ||
|
|
2acee1a8eb | ||
|
|
f3c9129fa6 | ||
|
|
3ab42a9012 | ||
|
|
f870efbdac | ||
|
|
5068f412e6 | ||
|
|
7f5d1d8ddb | ||
|
|
a81501bd87 | ||
|
|
06cc2b71c6 | ||
|
|
2a23522510 | ||
|
|
f3dfff6de1 | ||
|
|
841c928b90 | ||
|
|
e3374d0bf3 | ||
|
|
a7cd98dd8f | ||
|
|
2714ded0b5 | ||
|
|
c54513c327 | ||
|
|
7050a9a841 | ||
|
|
aea5447f55 | ||
|
|
d65628d989 | ||
|
|
2d96ea84e5 | ||
|
|
ff38502517 | ||
|
|
e50d0684b3 | ||
|
|
6463029786 | ||
|
|
806108d7b6 | ||
|
|
617afeb64b | ||
|
|
3737905670 | ||
|
|
54c79642f5 | ||
|
|
0682c528da | ||
|
|
3377bc8a2b | ||
|
|
3b0d5dcf0e | ||
|
|
aa8c4471dc | ||
|
|
04b8ef4d86 | ||
|
|
9aee83607f | ||
|
|
d8abbce47e |
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -167,7 +167,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
|
||||
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider(defStore)
|
||||
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
|
||||
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
|
||||
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,40 @@ 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) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
|
||||
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
batchResults, err := provider.openfgaServer.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]*authtypes.TransactionWithAuthorization, len(transactions))
|
||||
for i, txn := range transactions {
|
||||
result := batchResults[txn.ID.StringValue()]
|
||||
results[i] = &authtypes.TransactionWithAuthorization{
|
||||
Transaction: txn,
|
||||
Authorized: result.Authorized,
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
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 +180,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 +197,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 +209,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 +234,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 +262,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 +295,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 +390,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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -2,27 +2,47 @@ package implcloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
)
|
||||
|
||||
type azurecloudprovider struct{}
|
||||
type azurecloudprovider struct {
|
||||
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
|
||||
}
|
||||
|
||||
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
|
||||
return &azurecloudprovider{}
|
||||
func NewAzureCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) cloudintegration.CloudProviderModule {
|
||||
return &azurecloudprovider{
|
||||
serviceDefinitions: defStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
panic("implement me")
|
||||
cliCommand := cloudintegrationtypes.NewAzureConnectionCLICommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
|
||||
psCommand := cloudintegrationtypes.NewAzureConnectionPowerShellCommand(account.ID, req.Config.AgentVersion, req.Credentials, req.Config.Azure)
|
||||
|
||||
return &cloudintegrationtypes.ConnectionArtifact{
|
||||
Azure: cloudintegrationtypes.NewAzureConnectionArtifact(cliCommand, psCommand),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAzure, serviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// override cloud integration dashboard id.
|
||||
for index, dashboard := range serviceDef.Assets.Dashboards {
|
||||
serviceDef.Assets.Dashboards[index].ID = cloudintegrationtypes.GetCloudIntegrationDashboardID(cloudintegrationtypes.CloudProviderTypeAzure, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) BuildIntegrationConfig(
|
||||
@@ -30,5 +50,56 @@ func (provider *azurecloudprovider) BuildIntegrationConfig(
|
||||
account *cloudintegrationtypes.Account,
|
||||
services []*cloudintegrationtypes.StorableCloudIntegrationService,
|
||||
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
|
||||
panic("implement me")
|
||||
sort.Slice(services, func(i, j int) bool {
|
||||
return services[i].Type.StringValue() < services[j].Type.StringValue()
|
||||
})
|
||||
|
||||
var strategies []*cloudintegrationtypes.AzureTelemetryCollectionStrategy
|
||||
|
||||
for _, storedSvc := range services {
|
||||
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAzure, storedSvc.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
strategy := svcDef.TelemetryCollectionStrategy.Azure
|
||||
if strategy == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
|
||||
if !logsEnabled && !metricsEnabled {
|
||||
continue
|
||||
}
|
||||
|
||||
entry := &cloudintegrationtypes.AzureTelemetryCollectionStrategy{
|
||||
ResourceProvider: strategy.ResourceProvider,
|
||||
ResourceType: strategy.ResourceType,
|
||||
}
|
||||
|
||||
if metricsEnabled && strategy.Metrics != nil {
|
||||
entry.Metrics = strategy.Metrics
|
||||
}
|
||||
|
||||
if logsEnabled && strategy.Logs != nil {
|
||||
entry.Logs = strategy.Logs
|
||||
}
|
||||
|
||||
strategies = append(strategies, entry)
|
||||
}
|
||||
|
||||
return &cloudintegrationtypes.ProviderIntegrationConfig{
|
||||
Azure: cloudintegrationtypes.NewAzureIntegrationConfig(
|
||||
account.Config.Azure.DeploymentRegion,
|
||||
account.Config.Azure.ResourceGroups,
|
||||
strategies,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -429,9 +429,13 @@ func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[strin
|
||||
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
|
||||
}
|
||||
|
||||
// NOTE: not adding stats for services for now.
|
||||
// get connected accounts for Azure
|
||||
azureAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAzure)
|
||||
if err == nil {
|
||||
stats["cloudintegration.azure.connectedaccounts.count"] = azureAccountsCount
|
||||
}
|
||||
|
||||
// TODO: add more cloud providers when supported
|
||||
// NOTE: not adding stats for services for now.
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
@@ -71,15 +71,6 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||
Route: "",
|
||||
})
|
||||
|
||||
bodyJSONQuery := ah.Signoz.Flagger.BooleanOrEmpty(ctx, flagger.FeatureBodyJSONQuery, evalCtx)
|
||||
featureSet = append(featureSet, &licensetypes.Feature{
|
||||
Name: valuer.NewString(flagger.FeatureBodyJSONQuery.String()),
|
||||
Active: bodyJSONQuery,
|
||||
Usage: 0,
|
||||
UsageLimit: -1,
|
||||
Route: "",
|
||||
})
|
||||
|
||||
if constants.IsDotMetricsEnabled {
|
||||
for idx, feature := range featureSet {
|
||||
if feature.Name == licensetypes.DotMetricsEnabled {
|
||||
|
||||
@@ -42,8 +42,8 @@ import (
|
||||
|
||||
// Server runs HTTP, Mux and a grpc server
|
||||
type Server struct {
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
config signoz.Config
|
||||
signoz *signoz.SigNoz
|
||||
|
||||
// public http router
|
||||
httpConn net.Listener
|
||||
@@ -105,7 +105,6 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
signoz.SQLStore,
|
||||
integrationsController.GetPipelinesForInstalledIntegrations,
|
||||
reader,
|
||||
signoz.Flagger,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -148,7 +147,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
|
||||
|
||||
s := &Server{
|
||||
config: config,
|
||||
signoz: signoz,
|
||||
signoz: signoz,
|
||||
httpHostPort: baseconst.HTTPHostPort,
|
||||
unavailableChannel: make(chan healthcheck.Status),
|
||||
usageManager: usageManager,
|
||||
@@ -317,3 +316,4 @@ func (s *Server) Stop(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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": "error",
|
||||
// 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"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable-next-line typescript/no-require-imports
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -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"
|
||||
@@ -39,12 +40,12 @@
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
property="og:image"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
name="twitter:image"
|
||||
content="/images/signoz-hero-image.webp"
|
||||
content="[[.BaseHref]]images/signoz-hero-image.webp"
|
||||
/>
|
||||
<meta
|
||||
data-react-helmet="true"
|
||||
@@ -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>
|
||||
|
||||
152
frontend/plugins/rules/no-raw-absolute-path.mjs
Normal file
152
frontend/plugins/rules/no-raw-absolute-path.mjs
Normal 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' });
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { CreditCard, MessageSquareText, X } from 'lucide-react';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
export default function ChatSupportGateway(): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
@@ -54,7 +55,7 @@ export default function ChatSupportGateway(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import { useTimezone } from 'providers/Timezone';
|
||||
import APIError from 'types/api/error';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { toAPIError } from 'utils/errorUtils';
|
||||
|
||||
import DeleteMemberDialog from './DeleteMemberDialog';
|
||||
@@ -381,7 +382,7 @@ function EditMemberDrawer({
|
||||
pathParams: { id: member.id },
|
||||
});
|
||||
if (response?.data?.token) {
|
||||
const link = `${window.location.origin}/password-reset?token=${response.data.token}`;
|
||||
const link = getAbsoluteUrl(`/password-reset?token=${response.data.token}`);
|
||||
setResetLink(link);
|
||||
setResetLinkExpiresAt(
|
||||
response.data.expiresAt
|
||||
|
||||
@@ -13,6 +13,7 @@ import GetMinMax from 'lib/getMinMax';
|
||||
import { Check, Info, Link2 } from 'lucide-react';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
const routesToBeSharedWithTime = [
|
||||
ROUTES.LOGS_EXPLORER,
|
||||
@@ -80,17 +81,13 @@ function ShareURLModal(): JSX.Element {
|
||||
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
} else {
|
||||
urlQuery.delete(QueryParams.startTime);
|
||||
urlQuery.delete(QueryParams.endTime);
|
||||
|
||||
urlQuery.set(QueryParams.relativeTime, selectedTime);
|
||||
currentUrl = `${window.location.origin}${
|
||||
location.pathname
|
||||
}?${urlQuery.toString()}`;
|
||||
currentUrl = getAbsoluteUrl(`${location.pathname}?${urlQuery.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { ROLES } from 'types/roles';
|
||||
import { EMAIL_REGEX } from 'utils/app';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
@@ -191,7 +192,7 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role as ROLES,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
});
|
||||
} else {
|
||||
await inviteUsers({
|
||||
@@ -199,7 +200,7 @@ function InviteMembersModal({
|
||||
email: row.email.trim(),
|
||||
name: '',
|
||||
role: row.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import './LaunchChatSupport.styles.scss';
|
||||
|
||||
@@ -154,7 +155,7 @@ function LaunchChatSupport({
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from 'antd';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './LearnMore.styles.scss';
|
||||
|
||||
@@ -14,7 +15,7 @@ function LearnMore({ text, url, onClick }: LearnMoreProps): JSX.Element {
|
||||
const handleClick = (): void => {
|
||||
onClick?.();
|
||||
if (url) {
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
KAFKA_SETUP_DOC_LINK,
|
||||
MessagingQueueHealthCheckService,
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import './MessagingQueueHealthCheck.styles.scss';
|
||||
@@ -76,7 +77,7 @@ function ErrorTitleAndKey({
|
||||
if (isCloudUserVal && !!link) {
|
||||
history.push(link);
|
||||
} else {
|
||||
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
|
||||
openInNewTab(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
};
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from 'container/ApiMonitoring/utils';
|
||||
import { UnfoldVertical } from 'lucide-react';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import emptyStateUrl from '@/assets/Icons/emptyState.svg';
|
||||
|
||||
@@ -94,20 +95,14 @@ function DependentServices({
|
||||
}}
|
||||
onRow={(record): { onClick: () => void; className: string } => ({
|
||||
onClick: (): void => {
|
||||
const url = new URL(
|
||||
`/services/${
|
||||
record.serviceData.serviceName &&
|
||||
record.serviceData.serviceName !== '-'
|
||||
? record.serviceData.serviceName
|
||||
: ''
|
||||
}`,
|
||||
window.location.origin,
|
||||
);
|
||||
const serviceName =
|
||||
record.serviceData.serviceName && record.serviceData.serviceName !== '-'
|
||||
? record.serviceData.serviceName
|
||||
: '';
|
||||
const urlQuery = new URLSearchParams();
|
||||
urlQuery.set(QueryParams.startTime, timeRange.startTime.toString());
|
||||
urlQuery.set(QueryParams.endTime, timeRange.endTime.toString());
|
||||
url.search = urlQuery.toString();
|
||||
window.open(url.toString(), '_blank');
|
||||
openInNewTab(`/services/${serviceName}?${urlQuery.toString()}`);
|
||||
},
|
||||
className: 'clickable-row',
|
||||
})}
|
||||
|
||||
@@ -73,6 +73,7 @@ import {
|
||||
import { UserPreference } from 'types/api/preferences/preference';
|
||||
import AppReducer from 'types/reducer/app';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { showErrorNotification } from 'utils/error';
|
||||
import { eventEmitter } from 'utils/getEventEmitter';
|
||||
import {
|
||||
@@ -461,7 +462,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
||||
|
||||
const handleFailedPayment = useCallback((): void => {
|
||||
manageCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}, [manageCreditCard]);
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ import { isEmpty, pick } from 'lodash-es';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
||||
|
||||
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
|
||||
@@ -324,7 +325,7 @@ export default function BillingContainer(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
} else {
|
||||
logEvent('Billing : Manage Billing', {
|
||||
@@ -333,7 +334,7 @@ export default function BillingContainer(): JSX.Element {
|
||||
});
|
||||
|
||||
manageCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -7,6 +7,7 @@ import { FeatureKeys } from 'constants/features';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { AlertTypes } from 'types/api/alerts/alertTypes';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { getOptionList } from './config';
|
||||
import { AlertTypeCard, SelectTypeContainer } from './styles';
|
||||
@@ -55,7 +56,7 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
|
||||
page: 'New alert data source selection page',
|
||||
});
|
||||
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
}
|
||||
const renderOptions = useMemo(
|
||||
() => (
|
||||
|
||||
@@ -14,6 +14,7 @@ import { IUser } from 'providers/App/types';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { EQueryType } from 'types/common/dashboard';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ROUTING_POLICIES_ROUTE } from './constants';
|
||||
import { RoutingPolicyBannerProps } from './types';
|
||||
@@ -387,7 +388,7 @@ export function NotificationChannelsNotFoundContent({
|
||||
style={{ padding: '0 4px' }}
|
||||
type="link"
|
||||
onClick={(): void => {
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
}}
|
||||
>
|
||||
here.
|
||||
|
||||
@@ -46,6 +46,7 @@ function DomainUpdateToast({
|
||||
className="custom-domain-toast-visit-btn"
|
||||
suffix={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}}
|
||||
>
|
||||
@@ -74,10 +75,8 @@ export default function CustomDomainSettings(): JSX.Element {
|
||||
const [isPollingEnabled, setIsPollingEnabled] = useState(false);
|
||||
const [hosts, setHosts] = useState<ZeustypesHostDTO[] | null>(null);
|
||||
|
||||
const [
|
||||
updateDomainError,
|
||||
setUpdateDomainError,
|
||||
] = useState<AxiosError<RenderErrorResponseDTO> | null>(null);
|
||||
const [updateDomainError, setUpdateDomainError] =
|
||||
useState<AxiosError<RenderErrorResponseDTO> | null>(null);
|
||||
|
||||
const [customDomainSubdomain, setCustomDomainSubdomain] = useState<
|
||||
string | undefined
|
||||
@@ -90,10 +89,8 @@ export default function CustomDomainSettings(): JSX.Element {
|
||||
refetch: refetchHosts,
|
||||
} = useGetHosts();
|
||||
|
||||
const {
|
||||
mutate: updateSubDomain,
|
||||
isLoading: isLoadingUpdateCustomDomain,
|
||||
} = usePutHost<AxiosError<RenderErrorResponseDTO>>();
|
||||
const { mutate: updateSubDomain, isLoading: isLoadingUpdateCustomDomain } =
|
||||
usePutHost<AxiosError<RenderErrorResponseDTO>>();
|
||||
|
||||
const stripProtocol = (url: string): string => url?.split('://')[1] ?? url;
|
||||
|
||||
@@ -137,7 +134,7 @@ export default function CustomDomainSettings(): JSX.Element {
|
||||
{
|
||||
onSuccess: () => {
|
||||
setIsPollingEnabled(true);
|
||||
refetchHosts();
|
||||
void refetchHosts();
|
||||
setIsEditModalOpen(false);
|
||||
setCustomDomainSubdomain(subdomain);
|
||||
const newUrl = `https://${subdomain}.${dnsSuffix}`;
|
||||
|
||||
@@ -15,6 +15,8 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { PublicDashboardMetaProps } from 'types/api/dashboard/public/getMeta';
|
||||
import APIError from 'types/api/error';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './PublicDashboard.styles.scss';
|
||||
|
||||
@@ -212,7 +214,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
|
||||
try {
|
||||
setCopyPublicDashboardURL(
|
||||
`${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
|
||||
getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
|
||||
);
|
||||
toast.success('Copied Public Dashboard URL successfully');
|
||||
} catch (error) {
|
||||
@@ -221,7 +223,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
};
|
||||
|
||||
const publicDashboardURL = useMemo(
|
||||
() => `${window.location.origin}${publicDashboardResponse?.data?.publicPath}`,
|
||||
() => getAbsoluteUrl(publicDashboardResponse?.data?.publicPath ?? ''),
|
||||
[publicDashboardResponse],
|
||||
);
|
||||
|
||||
@@ -294,7 +296,7 @@ function PublicDashboardSetting(): JSX.Element {
|
||||
icon={<ExternalLink size={12} />}
|
||||
onClick={(): void => {
|
||||
if (publicDashboardURL) {
|
||||
window.open(publicDashboardURL, '_blank');
|
||||
openInNewTab(publicDashboardURL);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -9,6 +9,7 @@ import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import APIError from 'types/api/error';
|
||||
import { OrgSessionContext } from 'types/api/v2/sessions/context/get';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import tvUrl from '@/assets/svgs/tv.svg';
|
||||
|
||||
@@ -104,7 +105,7 @@ function ForgotPassword({
|
||||
data: {
|
||||
email: values.email,
|
||||
orgId: currentOrgId,
|
||||
frontendBaseURL: window.location.origin,
|
||||
frontendBaseURL: getBaseUrl(),
|
||||
},
|
||||
});
|
||||
}, [form, forgotPasswordMutate, initialOrgId, hasMultipleOrgs]);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { AlertDef, Labels } from 'types/api/alerts/def';
|
||||
import { Channels } from 'types/api/channels/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import ChannelSelect from './ChannelSelect';
|
||||
@@ -87,7 +88,7 @@ function BasicInfo({
|
||||
dataSource: ALERTS_DATA_SOURCE_MAP[alertDef?.alertType as AlertTypes],
|
||||
ruleId: isNewRule ? 0 : alertDef?.id,
|
||||
});
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
const hasLoggedEvent = useRef(false);
|
||||
|
||||
@@ -55,6 +55,7 @@ import { DataSource } from 'types/common/queryBuilder';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { compositeQueryToQueryEnvelope } from 'utils/compositeQueryToQueryEnvelope';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import BasicInfo from './BasicInfo';
|
||||
import ChartPreview from './ChartPreview';
|
||||
@@ -777,7 +778,7 @@ function FormAlertRules({
|
||||
queryType: currentQuery.queryType,
|
||||
link: url,
|
||||
});
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { Dashboard } from 'types/api/dashboard/getAll';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import dialsUrl from '@/assets/Icons/dials.svg';
|
||||
|
||||
@@ -114,7 +115,7 @@ export default function Dashboards({
|
||||
dashboardName: dashboard.data.title,
|
||||
});
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
} else {
|
||||
safeNavigate(getLink());
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Link2 } from 'lucide-react';
|
||||
import Card from 'periscope/components/Card/Card';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import containerPlusUrl from '@/assets/Icons/container-plus.svg';
|
||||
import helloWaveUrl from '@/assets/Icons/hello-wave.svg';
|
||||
@@ -51,7 +52,7 @@ function DataSourceInfo({
|
||||
if (activeLicense && activeLicense.platform === LicensePlatform.CLOUD) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(DOCS_LINKS.ADD_DATA_SOURCE, '_blank', 'noopener noreferrer');
|
||||
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ArrowRight, ArrowRightToLine, BookOpenText } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './HomeChecklist.styles.scss';
|
||||
|
||||
@@ -99,11 +100,7 @@ function HomeChecklist({
|
||||
) {
|
||||
history.push(item.toRoute || '');
|
||||
} else {
|
||||
window?.open(
|
||||
item.docsLink || '',
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openInNewTab(item.docsLink || '');
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -119,7 +116,7 @@ function HomeChecklist({
|
||||
step: item.id,
|
||||
});
|
||||
|
||||
window?.open(item.docsLink, '_blank', 'noopener noreferrer');
|
||||
openInNewTab(item.docsLink ?? '');
|
||||
}}
|
||||
>
|
||||
<BookOpenText size={16} />
|
||||
|
||||
@@ -31,6 +31,7 @@ import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { Tags } from 'types/reducer/trace';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
@@ -79,11 +80,7 @@ const EmptyState = memo(
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ServicesList } from 'types/api/metrics/getService';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import triangleRulerUrl from '@/assets/Icons/triangle-ruler.svg';
|
||||
|
||||
@@ -133,11 +134,7 @@ export default function ServiceTraces({
|
||||
) {
|
||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||
} else {
|
||||
window?.open(
|
||||
DOCS_LINKS.ADD_DATA_SOURCE,
|
||||
'_blank',
|
||||
'noopener noreferrer',
|
||||
);
|
||||
openInNewTab(DOCS_LINKS.ADD_DATA_SOURCE);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -51,6 +51,7 @@ import {
|
||||
LogsAggregatorOperator,
|
||||
TracesAggregatorOperator,
|
||||
} from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { filterDuplicateFilters } from '../commonUtils';
|
||||
@@ -569,10 +570,7 @@ function K8sBaseDetails<T>({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
|
||||
} else if (selectedView === VIEW_TYPES.TRACES) {
|
||||
const compositeQuery = {
|
||||
...initialQueryState,
|
||||
@@ -591,10 +589,7 @@ function K8sBaseDetails<T>({
|
||||
|
||||
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
|
||||
'_blank',
|
||||
);
|
||||
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
CloudintegrationtypesServiceDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import './ServiceDashboards.styles.scss';
|
||||
|
||||
@@ -44,7 +45,11 @@ function ServiceDashboards({
|
||||
return;
|
||||
}
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
return;
|
||||
}
|
||||
safeNavigate(dashboardUrl);
|
||||
@@ -54,7 +59,11 @@ function ServiceDashboards({
|
||||
return;
|
||||
}
|
||||
if (event.button === 1) {
|
||||
window.open(dashboardUrl, '_blank', 'noopener,noreferrer');
|
||||
window.open(
|
||||
withBasePath(dashboardUrl),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event): void => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined } from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
interface AlertInfoCardProps {
|
||||
header: string;
|
||||
@@ -19,7 +20,7 @@ function AlertInfoCard({
|
||||
className="alert-info-card"
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openInNewTab(link);
|
||||
}}
|
||||
>
|
||||
<div className="alert-card-text">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
|
||||
import { Flex, Typography } from 'antd';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
interface InfoLinkTextProps {
|
||||
infoText: string;
|
||||
@@ -20,7 +21,7 @@ function InfoLinkText({
|
||||
<Flex
|
||||
onClick={(): void => {
|
||||
onClick();
|
||||
window.open(link, '_blank');
|
||||
openInNewTab(link);
|
||||
}}
|
||||
className="info-link-container"
|
||||
>
|
||||
|
||||
@@ -83,6 +83,8 @@ import {
|
||||
} from 'types/api/dashboard/getAll';
|
||||
import APIError from 'types/api/error';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import awwSnapUrl from '@/assets/Icons/awwSnap.svg';
|
||||
import dashboardsUrl from '@/assets/Icons/dashboards.svg';
|
||||
@@ -457,7 +459,7 @@ function DashboardsList(): JSX.Element {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
}}
|
||||
>
|
||||
Open in New Tab
|
||||
@@ -469,7 +471,7 @@ function DashboardsList(): JSX.Element {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setCopy(`${window.location.origin}${getLink()}`);
|
||||
setCopy(getAbsoluteUrl(getLink()));
|
||||
}}
|
||||
>
|
||||
Copy Link
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { LockFilled } from '@ant-design/icons';
|
||||
import ROUTES from 'constants/routes';
|
||||
import history from 'lib/history';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { Data } from '../DashboardsList';
|
||||
import { TableLinkText } from './styles';
|
||||
@@ -12,7 +13,7 @@ function Name(name: Data['name'], data: Data): JSX.Element {
|
||||
|
||||
const onClickHandler = (event: React.MouseEvent<HTMLElement>): void => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(), '_blank');
|
||||
openInNewTab(getLink());
|
||||
} else {
|
||||
history.push(getLink());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource, StringOperators } from 'types/common/queryBuilder';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import { useContextLogData } from './useContextLogData';
|
||||
|
||||
@@ -116,7 +117,7 @@ function ContextLogRenderer({
|
||||
);
|
||||
|
||||
const link = `${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`;
|
||||
window.open(link, '_blank', 'noopener,noreferrer');
|
||||
window.open(withBasePath(link), '_blank', 'noopener,noreferrer');
|
||||
},
|
||||
[query, urlQuery],
|
||||
);
|
||||
|
||||
@@ -34,6 +34,7 @@ import { SET_DETAILED_LOG_DATA } from 'types/actions/logs';
|
||||
import { IField } from 'types/api/logs/fields';
|
||||
import { ILog } from 'types/api/logs/log';
|
||||
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ActionItemProps } from './ActionItem';
|
||||
import FieldRenderer from './FieldRenderer';
|
||||
@@ -191,7 +192,7 @@ function TableView({
|
||||
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
// open the trace in new tab
|
||||
window.open(route, '_blank');
|
||||
openInNewTab(route);
|
||||
} else {
|
||||
history.push(route);
|
||||
}
|
||||
|
||||
@@ -60,10 +60,8 @@ function Login(): JSX.Element {
|
||||
const [sessionsContext, setSessionsContext] = useState<SessionsContext>();
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
const [sessionsOrgId, setSessionsOrgId] = useState<string>('');
|
||||
const [
|
||||
sessionsContextLoading,
|
||||
setIsLoadingSessionsContext,
|
||||
] = useState<boolean>(false);
|
||||
const [sessionsContextLoading, setIsLoadingSessionsContext] =
|
||||
useState<boolean>(false);
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const [errorMessage, setErrorMessage] = useState<APIError>();
|
||||
|
||||
@@ -213,6 +211,7 @@ function Login(): JSX.Element {
|
||||
if (isCallbackAuthN) {
|
||||
const url = form.getFieldValue('url');
|
||||
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path
|
||||
window.location.href = url;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -328,7 +327,6 @@ function Login(): JSX.Element {
|
||||
data-testid="email"
|
||||
required
|
||||
placeholder="e.g. john@signoz.io"
|
||||
autoFocus
|
||||
disabled={versionLoading}
|
||||
className="login-form-input"
|
||||
onPressEnter={onNextHandler}
|
||||
|
||||
@@ -34,6 +34,7 @@ import ROUTES from 'constants/routes';
|
||||
import { useActiveLog } from 'hooks/logs/useActiveLog';
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
import useDragColumns from 'hooks/useDragColumns';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import { infinityDefaultStyles } from '../InfinityTableView/config';
|
||||
import { TanStackTableStyled } from '../InfinityTableView/styles';
|
||||
@@ -239,7 +240,7 @@ const TanStackTableView = forwardRef<TableVirtuosoHandle, InfinityTableProps>(
|
||||
urlQuery.delete(QueryParams.activeLogId);
|
||||
urlQuery.delete(QueryParams.relativeTime);
|
||||
urlQuery.set(QueryParams.activeLogId, `"${logId}"`);
|
||||
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
|
||||
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
|
||||
|
||||
setCopy(link);
|
||||
toast.success('Copied to clipboard', { position: 'top-right' });
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import { TopOperationList } from './TopOperationsTable';
|
||||
import { NavigateToTraceProps } from './types';
|
||||
@@ -37,7 +38,7 @@ export const navigateToTrace = ({
|
||||
}=${JSONCompositeQuery}`;
|
||||
|
||||
if (openInNewTab) {
|
||||
window.open(newTraceExplorerPath, '_blank');
|
||||
window.open(withBasePath(newTraceExplorerPath), '_blank');
|
||||
} else {
|
||||
safeNavigate(newTraceExplorerPath);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { QueryParams } from 'constants/query';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { Bell, Grid } from 'lucide-react';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { pluralize } from 'utils/pluralize';
|
||||
|
||||
import { DashboardsAndAlertsPopoverProps } from './types';
|
||||
@@ -67,9 +68,8 @@ function DashboardsAndAlertsPopover({
|
||||
<Typography.Link
|
||||
key={alert.alertId}
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
openInNewTab(
|
||||
`${ROUTES.ALERT_OVERVIEW}?${QueryParams.ruleId}=${alert.alertId}`,
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
className="dashboards-popover-content-item"
|
||||
@@ -90,11 +90,10 @@ function DashboardsAndAlertsPopover({
|
||||
<Typography.Link
|
||||
key={dashboard.dashboardId}
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
openInNewTab(
|
||||
generatePath(ROUTES.DASHBOARD, {
|
||||
dashboardId: dashboard.dashboardId,
|
||||
}),
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
className="dashboards-popover-content-item"
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import useContextVariables from 'hooks/dashboard/useContextVariables';
|
||||
import { Plus, Trash2 } from 'lucide-react';
|
||||
import { ContextLinkProps, Widgets } from 'types/api/dashboard/getAll';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import VariablesDropdown from './VariablesDropdown';
|
||||
|
||||
@@ -84,7 +85,7 @@ function UpdateContextLinks({
|
||||
);
|
||||
|
||||
// Function to get current domain
|
||||
const getCurrentDomain = (): string => window.location.origin;
|
||||
const getCurrentDomain = (): string => getBaseUrl();
|
||||
|
||||
// Function to handle variable selection from dropdown
|
||||
const handleVariableSelect = (
|
||||
|
||||
@@ -6,6 +6,7 @@ import history from 'lib/history';
|
||||
import { ArrowUpRight } from 'lucide-react';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import DOCLINKS from 'utils/docLinks';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import eyesEmojiUrl from '@/assets/Images/eyesEmoji.svg';
|
||||
|
||||
@@ -42,11 +43,11 @@ export default function NoLogs({
|
||||
}
|
||||
history.push(link);
|
||||
} else if (dataSource === 'traces') {
|
||||
window.open(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE, '_blank');
|
||||
openInNewTab(DOCLINKS.TRACES_EXPLORER_EMPTY_STATE);
|
||||
} else if (dataSource === DataSource.METRICS) {
|
||||
window.open(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE, '_blank');
|
||||
openInNewTab(DOCLINKS.METRICS_EXPLORER_EMPTY_STATE);
|
||||
} else {
|
||||
window.open(`${DOCLINKS.USER_GUIDE}${dataSource}/`, '_blank');
|
||||
openInNewTab(`${DOCLINKS.USER_GUIDE}${dataSource}/`);
|
||||
}
|
||||
};
|
||||
return (
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Trash2,
|
||||
} from 'lucide-react';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { OnboardingQuestionHeader } from '../OnboardingQuestionHeader';
|
||||
@@ -58,7 +59,7 @@ function InviteTeamMembers({
|
||||
email: '',
|
||||
role: '',
|
||||
name: '',
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
id: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { DOCS_BASE_URL } from 'constants/app';
|
||||
import { useGetGlobalConfig } from 'hooks/globalConfig/useGetGlobalConfig';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { ArrowUpRight, Copy, Info, Key, TriangleAlert } from 'lucide-react';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import './IngestionDetails.styles.scss';
|
||||
|
||||
@@ -215,7 +216,7 @@ export default function OnboardingIngestionDetails(): JSX.Element {
|
||||
</a>
|
||||
. To create a new one,{' '}
|
||||
<a
|
||||
href="/settings/ingestion-settings"
|
||||
href={withBasePath('/settings/ingestion-settings')}
|
||||
target="_blank"
|
||||
className="learn-more"
|
||||
rel="noreferrer"
|
||||
|
||||
@@ -8,6 +8,7 @@ import { useNotifications } from 'hooks/useNotifications';
|
||||
import { cloneDeep, debounce, isEmpty } from 'lodash-es';
|
||||
import { ArrowRight, CheckCircle, Plus, TriangleAlert, X } from 'lucide-react';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import './InviteTeamMembers.styles.scss';
|
||||
@@ -56,7 +57,7 @@ function InviteTeamMembers({
|
||||
email: '',
|
||||
role: 'EDITOR',
|
||||
name: '',
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
id: '',
|
||||
};
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import CopyToClipboard from 'periscope/components/CopyToClipboard';
|
||||
import { useErrorModal } from 'providers/ErrorModalProvider';
|
||||
import APIError from 'types/api/error';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import CreateEdit from './CreateEdit/CreateEdit';
|
||||
import SSOEnforcementToggle from './SSOEnforcementToggle';
|
||||
@@ -144,7 +145,7 @@ function AuthDomain(): JSX.Element {
|
||||
return <span className="auth-domain-list-na">N/A</span>;
|
||||
}
|
||||
|
||||
const href = `${window.location.origin}/${relayPath}`;
|
||||
const href = getAbsoluteUrl(`/${relayPath}`);
|
||||
return <CopyToClipboard textToCopy={href} />;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Button, Form, FormInstance, Modal } from 'antd';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
|
||||
import InviteTeamMembers from '../InviteTeamMembers';
|
||||
import { InviteMemberFormValues } from '../utils';
|
||||
@@ -40,7 +41,7 @@ function InviteUserModal(props: InviteUserModalProps): JSX.Element {
|
||||
email: member.email,
|
||||
name: member?.name,
|
||||
role: member.role,
|
||||
frontendBaseUrl: window.location.origin,
|
||||
frontendBaseUrl: getBaseUrl(),
|
||||
});
|
||||
|
||||
notifications.success({
|
||||
|
||||
@@ -14,6 +14,7 @@ import ContextMenu from 'periscope/components/ContextMenu';
|
||||
import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { ContextLinksData } from 'types/api/dashboard/getAll';
|
||||
import { Query } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { ContextMenuItem } from './contextConfig';
|
||||
import { getDataLinks } from './dataLinksUtils';
|
||||
@@ -115,7 +116,7 @@ const useBaseAggregateOptions = ({
|
||||
key={id}
|
||||
icon={<LinkOutlined />}
|
||||
onClick={(): void => {
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
onClose?.();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ModalTitle } from 'container/PipelinePage/PipelineListsView/styles';
|
||||
import { Check, Loader, X } from 'lucide-react';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import { USER_ROLES } from 'types/roles';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import { INITIAL_ROUTING_POLICY_DETAILS_FORM_STATE } from './constants';
|
||||
import {
|
||||
@@ -76,7 +77,7 @@ function RoutingPolicyDetails({
|
||||
style={{ padding: '0 4px' }}
|
||||
type="link"
|
||||
onClick={(): void => {
|
||||
window.open(ROUTES.CHANNELS_NEW, '_blank');
|
||||
openInNewTab(ROUTES.CHANNELS_NEW);
|
||||
}}
|
||||
>
|
||||
here.
|
||||
|
||||
@@ -818,7 +818,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
|
||||
);
|
||||
|
||||
if (item && !('type' in item) && item.isExternal && item.url) {
|
||||
window.open(item.url, '_blank');
|
||||
openInNewTab(item.url);
|
||||
}
|
||||
|
||||
const event = (info as SidebarItem & { domEvent?: MouseEvent }).domEvent;
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
} from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
|
||||
import { DataSource } from 'types/common/queryBuilder';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import noDataUrl from '@/assets/Icons/no-data.svg';
|
||||
@@ -143,7 +144,7 @@ function SpanLogs({
|
||||
|
||||
const url = `${ROUTES.LOGS_EXPLORER}?${createQueryParams(queryParams)}`;
|
||||
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
},
|
||||
[
|
||||
isLogSpanRelated,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { BarChart2, Compass, X } from 'lucide-react';
|
||||
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { DataSource, LogsAggregatorOperator } from 'types/common/queryBuilder';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import { RelatedSignalsViews } from '../constants';
|
||||
import SpanLogs from '../SpanLogs/SpanLogs';
|
||||
@@ -158,9 +159,7 @@ function SpanRelatedSignals({
|
||||
searchParams.set(QueryParams.endTime, endTimeMs.toString());
|
||||
|
||||
window.open(
|
||||
`${window.location.origin}${
|
||||
ROUTES.LOGS_EXPLORER
|
||||
}?${searchParams.toString()}`,
|
||||
getAbsoluteUrl(`${ROUTES.LOGS_EXPLORER}?${searchParams.toString()}`),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
);
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
UPDATE_SPANS_AGGREGATE_PAGE_SIZE,
|
||||
} from 'types/actions/trace';
|
||||
import { TraceReducer } from 'types/reducer/trace';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
dayjs.extend(duration);
|
||||
@@ -214,7 +215,7 @@ function TraceTable(): JSX.Element {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getLink(record), '_blank');
|
||||
openInNewTab(getLink(record));
|
||||
} else {
|
||||
history.push(getLink(record));
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import { useTimezone } from 'providers/Timezone';
|
||||
import { SuccessResponse } from 'types/api';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './TracesTableComponent.styles.scss';
|
||||
|
||||
@@ -86,7 +87,7 @@ function TracesTableComponent({
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
window.open(getTraceLink(record), '_blank');
|
||||
openInNewTab(getTraceLink(record));
|
||||
} else {
|
||||
history.push(getTraceLink(record));
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export function useIntegrationModal({
|
||||
ModalStateEnum.FORM,
|
||||
);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [accountId, setAccountId] = useState<string | undefined>(undefined);
|
||||
const [accountId, setAccountId] = useState<string | undefined>();
|
||||
const [activeView, setActiveView] = useState<ActiveViewEnum>(
|
||||
ActiveViewEnum.FORM,
|
||||
);
|
||||
@@ -80,7 +80,7 @@ export function useIntegrationModal({
|
||||
const [includeAllRegions, setIncludeAllRegions] = useState(false);
|
||||
const [selectedDeploymentRegion, setSelectedDeploymentRegion] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
>();
|
||||
const allRegions = useMemo(
|
||||
() => regions.flatMap((r) => r.subRegions.map((sr) => sr.name)),
|
||||
[],
|
||||
@@ -108,7 +108,7 @@ export function useIntegrationModal({
|
||||
|
||||
const handleConnectionSuccess = useCallback(
|
||||
(payload: { cloudAccountId: string; status?: unknown }): void => {
|
||||
logEvent('AWS Integration: Account connected', {
|
||||
void logEvent('AWS Integration: Account connected', {
|
||||
cloudAccountId: payload.cloudAccountId,
|
||||
status: payload.status,
|
||||
});
|
||||
@@ -126,7 +126,7 @@ export function useIntegrationModal({
|
||||
const handleConnectionTimeout = useCallback(
|
||||
(payload: { id?: string }): void => {
|
||||
setModalState(ModalStateEnum.ERROR);
|
||||
logEvent('AWS Integration: Account connection attempt timed out', {
|
||||
void logEvent('AWS Integration: Account connection attempt timed out', {
|
||||
id: payload.id,
|
||||
});
|
||||
},
|
||||
@@ -140,19 +140,17 @@ export function useIntegrationModal({
|
||||
const { mutate: generateUrl, isLoading: isGeneratingUrl } = useCreateAccount();
|
||||
|
||||
const handleError = useAxiosError();
|
||||
const {
|
||||
data: connectionParams,
|
||||
isLoading: isConnectionParamsLoading,
|
||||
} = useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
|
||||
{
|
||||
cloudProvider: INTEGRATION_TYPES.AWS,
|
||||
},
|
||||
{
|
||||
query: {
|
||||
onError: handleError,
|
||||
const { data: connectionParams, isLoading: isConnectionParamsLoading } =
|
||||
useGetConnectionCredentials<GetConnectionCredentialsQueryResult>(
|
||||
{
|
||||
cloudProvider: INTEGRATION_TYPES.AWS,
|
||||
},
|
||||
},
|
||||
);
|
||||
{
|
||||
query: {
|
||||
onError: handleError,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const handleGenerateUrl = useCallback(
|
||||
(payload: CloudintegrationtypesPostableAccountDTO): void => {
|
||||
@@ -164,14 +162,16 @@ export function useIntegrationModal({
|
||||
{
|
||||
onSuccess: (response: CreateAccountMutationResult) => {
|
||||
const accountId = response.data.id;
|
||||
const connectionUrl = response.data.connectionArtifact.aws.connectionUrl;
|
||||
const connectionUrl =
|
||||
response.data.connectionArtifact.aws?.connectionUrl ?? '';
|
||||
|
||||
logEvent(
|
||||
void logEvent(
|
||||
'AWS Integration: Account connection attempt redirected to AWS',
|
||||
{
|
||||
id: accountId,
|
||||
},
|
||||
);
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path -- connectionUrl is an external AWS console URL, not an internal path
|
||||
window.open(connectionUrl, '_blank');
|
||||
setModalState(ModalStateEnum.WAITING);
|
||||
setAccountId(accountId);
|
||||
|
||||
@@ -17,6 +17,7 @@ import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import useUrlQueryData from 'hooks/useUrlQueryData';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
import { HIGHLIGHTED_DELAY } from './configs';
|
||||
import { UseCopyLogLink } from './types';
|
||||
@@ -60,7 +61,7 @@ export const useCopyLogLink = (logId?: string): UseCopyLogLink => {
|
||||
urlQuery.set(QueryParams.startTime, minTime?.toString() || '');
|
||||
urlQuery.set(QueryParams.endTime, maxTime?.toString() || '');
|
||||
|
||||
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
|
||||
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
|
||||
|
||||
setCopy(link);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useDashboardStore } from 'providers/Dashboard/store/useDashboardStore';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Widgets } from 'types/api/dashboard/getAll';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
import { getGraphType } from 'utils/getGraphType';
|
||||
|
||||
const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
@@ -95,7 +96,7 @@ const useCreateAlerts = (widget?: Widgets, caller?: string): VoidFunction => {
|
||||
|
||||
const url = `${ROUTES.ALERTS_NEW}?${params.toString()}`;
|
||||
|
||||
window.open(url, '_blank', 'noreferrer');
|
||||
window.open(withBasePath(url), '_blank', 'noreferrer');
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useCopyToClipboard } from 'react-use';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { Span } from 'types/api/trace/getTraceV2';
|
||||
import { getAbsoluteUrl } from 'utils/basePath';
|
||||
|
||||
export const useCopySpanLink = (
|
||||
span?: Span,
|
||||
@@ -28,7 +29,7 @@ export const useCopySpanLink = (
|
||||
urlQuery.set('spanId', span?.spanId);
|
||||
}
|
||||
|
||||
const link = `${window.location.origin}${pathname}?${urlQuery.toString()}`;
|
||||
const link = getAbsoluteUrl(`${pathname}?${urlQuery.toString()}`);
|
||||
|
||||
setCopy(link);
|
||||
notifications.success({
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom-v5-compat';
|
||||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
interface NavigateOptions {
|
||||
replace?: boolean;
|
||||
@@ -25,12 +26,9 @@ const areUrlsEffectivelySame = (url1: URL, url2: URL): boolean => {
|
||||
const params1 = new URLSearchParams(url1.search);
|
||||
const params2 = new URLSearchParams(url2.search);
|
||||
|
||||
const allParams = new Set([
|
||||
...Array.from(params1.keys()),
|
||||
...Array.from(params2.keys()),
|
||||
]);
|
||||
const allParams = new Set([...params1.keys(), ...params2.keys()]);
|
||||
|
||||
return Array.from(allParams).every((param) => {
|
||||
return [...allParams].every((param) => {
|
||||
if (param === 'compositeQuery') {
|
||||
try {
|
||||
const query1 = params1.get('compositeQuery');
|
||||
@@ -83,11 +81,11 @@ const isDefaultNavigation = (currentUrl: URL, targetUrl: URL): boolean => {
|
||||
}
|
||||
|
||||
// Case 2: Check for new params that didn't exist before
|
||||
const currentKeys = new Set(Array.from(currentParams.keys()));
|
||||
const targetKeys = new Set(Array.from(targetParams.keys()));
|
||||
const currentKeys = new Set(currentParams.keys());
|
||||
const targetKeys = new Set(targetParams.keys());
|
||||
|
||||
// Find keys that exist in target but not in current
|
||||
const newKeys = Array.from(targetKeys).filter((key) => !currentKeys.has(key));
|
||||
const newKeys = [...targetKeys].filter((key) => !currentKeys.has(key));
|
||||
|
||||
return newKeys.length > 0;
|
||||
};
|
||||
@@ -107,19 +105,18 @@ export const useSafeNavigate = (
|
||||
const safeNavigate = useCallback(
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
(to: string | SafeNavigateParams, options?: NavigateOptions) => {
|
||||
const currentUrl = new URL(
|
||||
`${location.pathname}${location.search}`,
|
||||
window.location.origin,
|
||||
);
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path
|
||||
const base = window.location.origin;
|
||||
const currentUrl = new URL(`${location.pathname}${location.search}`, base);
|
||||
|
||||
let targetUrl: URL;
|
||||
|
||||
if (typeof to === 'string') {
|
||||
targetUrl = new URL(to, window.location.origin);
|
||||
targetUrl = new URL(to, base);
|
||||
} else {
|
||||
targetUrl = new URL(
|
||||
`${to.pathname || location.pathname}${to.search || ''}`,
|
||||
window.location.origin,
|
||||
base,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +127,7 @@ export const useSafeNavigate = (
|
||||
typeof to === 'string'
|
||||
? to
|
||||
: `${to.pathname || location.pathname}${to.search || ''}`;
|
||||
window.open(targetPath, '_blank');
|
||||
window.open(withBasePath(targetPath), '_blank');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createBrowserHistory } from 'history';
|
||||
import { getBasePath } from 'utils/basePath';
|
||||
|
||||
export default createBrowserHistory();
|
||||
export default createBrowserHistory({ basename: getBasePath() });
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
.headerContainer {
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-4);
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: var(--spacing-4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.headerRow {
|
||||
padding: var(--spacing-8) var(--spacing-8) 0 var(--spacing-8);
|
||||
font-size: var(--font-size-sm);
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--spacing-2);
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.pinnedItem {
|
||||
padding: var(--spacing-4) var(--spacing-4) 0 var(--spacing-4);
|
||||
}
|
||||
|
||||
.status {
|
||||
|
||||
@@ -72,13 +72,15 @@ export default function TooltipHeader({
|
||||
)}
|
||||
|
||||
{activeItem && (
|
||||
<TooltipItem
|
||||
item={activeItem}
|
||||
isItemActive={true}
|
||||
containerTestId="uplot-tooltip-pinned"
|
||||
markerTestId="uplot-tooltip-pinned-marker"
|
||||
contentTestId="uplot-tooltip-pinned-content"
|
||||
/>
|
||||
<div className={Styles.pinnedItem} data-testid="uplot-tooltip-pinned-item">
|
||||
<TooltipItem
|
||||
item={activeItem}
|
||||
isItemActive={true}
|
||||
containerTestId="uplot-tooltip-pinned"
|
||||
markerTestId="uplot-tooltip-pinned-marker"
|
||||
contentTestId="uplot-tooltip-pinned-content"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,36 +1,51 @@
|
||||
.uplot-tooltip-item {
|
||||
.uplotTooltipItem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
gap: var(--spacing-2);
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
font-weight: 400;
|
||||
|
||||
.uplot-tooltip-item-marker {
|
||||
border-radius: 50%;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
flex-shrink: 0;
|
||||
&[data-is-active='true'] {
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.uplot-tooltip-item-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
justify-content: space-between;
|
||||
|
||||
.uplot-tooltip-item-label {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
&-separator {
|
||||
flex: 1;
|
||||
border-width: 0.5px;
|
||||
border-style: dashed;
|
||||
min-width: 24px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1 !important;
|
||||
background-color: var(--l2-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.uplotTooltipItemMarker {
|
||||
border-radius: 50%;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-sizing: border-box;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.uplotTooltipItemContent {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--spacing-2);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.uplotTooltipItemLabel {
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.uplotTooltipItemContentSeparator {
|
||||
flex: 1;
|
||||
border-width: 0.5px;
|
||||
border-style: dashed;
|
||||
min-width: 24px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,7 @@ export default function TooltipItem({
|
||||
return (
|
||||
<div
|
||||
className={Styles.uplotTooltipItem}
|
||||
style={{
|
||||
opacity: isItemActive ? 1 : 0.7,
|
||||
fontWeight: isItemActive ? 700 : 400,
|
||||
}}
|
||||
data-is-active={isItemActive}
|
||||
data-testid={containerTestId}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
:global(div[data-viewport-type='element']) {
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 12px 4px 16px;
|
||||
padding: 0px var(--spacing-2) 0 var(--spacing-4);
|
||||
|
||||
[data-test-id='virtuoso-item-list'] > * + * {
|
||||
margin-top: var(--spacing-2);
|
||||
}
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
|
||||
@@ -4,6 +4,7 @@ import ROUTES from 'constants/routes';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { Home, LifeBuoy } from 'lucide-react';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
import cloudUrl from '@/assets/Images/cloud.svg';
|
||||
|
||||
@@ -11,8 +12,8 @@ import './ErrorBoundaryFallback.styles.scss';
|
||||
|
||||
function ErrorBoundaryFallback(): JSX.Element {
|
||||
const handleReload = (): void => {
|
||||
// Go to home page
|
||||
window.location.href = ROUTES.HOME;
|
||||
// Hard reload resets Sentry.ErrorBoundary state; withBasePath preserves any /signoz/ prefix.
|
||||
window.location.href = withBasePath(ROUTES.HOME);
|
||||
};
|
||||
|
||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from 'pages/MessagingQueues/MessagingQueuesUtils';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import {
|
||||
convertToMilliseconds,
|
||||
@@ -93,7 +94,7 @@ export function getColumns(
|
||||
key={item}
|
||||
className="traceid-text"
|
||||
onClick={(): void => {
|
||||
window.open(`${ROUTES.TRACE}/${item}`, '_blank');
|
||||
openInNewTab(`${ROUTES.TRACE}/${item}`);
|
||||
logEvent(`MQ Kafka: Drop Rate - traceid navigation`, {
|
||||
item,
|
||||
});
|
||||
@@ -123,7 +124,7 @@ export function getColumns(
|
||||
onClick={(e): void => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
window.open(`/services/${encodeURIComponent(text)}`, '_blank');
|
||||
openInNewTab(`/services/${encodeURIComponent(text)}`);
|
||||
}}
|
||||
>
|
||||
{text}
|
||||
|
||||
@@ -59,7 +59,7 @@ function MessagingQueues(): JSX.Element {
|
||||
history.push(link);
|
||||
}
|
||||
} else {
|
||||
window.open(KAFKA_SETUP_DOC_LINK, '_blank');
|
||||
openInNewTab(KAFKA_SETUP_DOC_LINK);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ import { useAppContext } from 'providers/App/App';
|
||||
import { SuccessResponseV2 } from 'types/api';
|
||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||
import APIError from 'types/api/error';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { openInNewTab } from 'utils/navigation';
|
||||
|
||||
import './Support.styles.scss';
|
||||
|
||||
@@ -92,7 +94,7 @@ export default function Support(): JSX.Element {
|
||||
|
||||
const { pathname } = useLocation();
|
||||
const handleChannelWithRedirects = (url: string): void => {
|
||||
window.open(url, '_blank');
|
||||
openInNewTab(url);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -150,7 +152,7 @@ export default function Support(): JSX.Element {
|
||||
});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ import { useAppContext } from 'providers/App/App';
|
||||
import APIError from 'types/api/error';
|
||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||
import { isModifierKeyPressed } from 'utils/app';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { getFormattedDate } from 'utils/timeUtils';
|
||||
|
||||
import CustomerStoryCard from './CustomerStoryCard';
|
||||
@@ -115,7 +116,7 @@ export default function WorkspaceBlocked(): JSX.Element {
|
||||
logEvent('Workspace Blocked: User Clicked Update Credit Card', {});
|
||||
|
||||
updateCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}, [updateCreditCard]);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import history from 'lib/history';
|
||||
import { useAppContext } from 'providers/App/App';
|
||||
import APIError from 'types/api/error';
|
||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||
import { getBaseUrl } from 'utils/basePath';
|
||||
import { getFormattedDateWithMinutes } from 'utils/timeUtils';
|
||||
|
||||
import featureGraphicCorrelationUrl from '@/assets/Images/feature-graphic-correlation.svg';
|
||||
@@ -57,7 +58,7 @@ function WorkspaceSuspended(): JSX.Element {
|
||||
|
||||
const handleUpdateCreditCard = useCallback(async () => {
|
||||
manageCreditCard({
|
||||
url: window.location.origin,
|
||||
url: getBaseUrl(),
|
||||
});
|
||||
}, [manageCreditCard]);
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { LOCALSTORAGE } from 'constants/localStorage';
|
||||
import { EventListener, EventSourcePolyfill } from 'event-source-polyfill';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import APIError from 'types/api/error';
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
interface IEventSourceContext {
|
||||
eventSourceInstance: EventSourcePolyfill | null;
|
||||
@@ -129,9 +130,12 @@ export function EventSourceProvider({
|
||||
|
||||
const handleStartOpenConnection = useCallback(
|
||||
(filterExpression?: string): void => {
|
||||
const eventSourceUrl = `${
|
||||
ENVIRONMENT.baseURL
|
||||
}${apiV3}logs/livetail?filter=${encodeURIComponent(filterExpression || '')}`;
|
||||
const apiPath = `${apiV3}logs/livetail?filter=${encodeURIComponent(
|
||||
filterExpression || '',
|
||||
)}`;
|
||||
const eventSourceUrl = ENVIRONMENT.baseURL
|
||||
? `${ENVIRONMENT.baseURL}${apiPath}`
|
||||
: withBasePath(apiPath);
|
||||
|
||||
eventSourceRef.current = new EventSourcePolyfill(eventSourceUrl, {
|
||||
headers: {
|
||||
|
||||
118
frontend/src/utils/__tests__/base-path.test.ts
Normal file
118
frontend/src/utils/__tests__/base-path.test.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* basePath is memoized at module init, so each describe block isolates the
|
||||
* module with a fresh DOM state using jest.isolateModules + require.
|
||||
*/
|
||||
|
||||
type BasePath = typeof import('../basePath');
|
||||
|
||||
function loadModule(href?: string): BasePath {
|
||||
if (href !== undefined) {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
}
|
||||
|
||||
let mod!: BasePath;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../basePath');
|
||||
});
|
||||
return mod;
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const el of document.head.querySelectorAll('base')) { el.remove(); }
|
||||
});
|
||||
|
||||
describe('at basePath="/"', () => {
|
||||
let m: BasePath;
|
||||
beforeEach(() => {
|
||||
m = loadModule('/');
|
||||
});
|
||||
|
||||
it('getBasePath returns "/"', () => {
|
||||
expect(m.getBasePath()).toBe('/');
|
||||
});
|
||||
|
||||
it('withBasePath is a no-op for any internal path', () => {
|
||||
expect(m.withBasePath('/logs')).toBe('/logs');
|
||||
expect(m.withBasePath('/logs/explorer')).toBe('/logs/explorer');
|
||||
});
|
||||
|
||||
it('withBasePath passes through external URLs', () => {
|
||||
expect(m.withBasePath('https://example.com/foo')).toBe(
|
||||
'https://example.com/foo',
|
||||
);
|
||||
});
|
||||
|
||||
it('getAbsoluteUrl returns origin + path', () => {
|
||||
expect(m.getAbsoluteUrl('/logs')).toBe(`${window.location.origin}/logs`);
|
||||
});
|
||||
|
||||
it('getBaseUrl returns bare origin', () => {
|
||||
expect(m.getBaseUrl()).toBe(window.location.origin);
|
||||
});
|
||||
});
|
||||
|
||||
describe('at basePath="/signoz/"', () => {
|
||||
let m: BasePath;
|
||||
beforeEach(() => {
|
||||
m = loadModule('/signoz/');
|
||||
});
|
||||
|
||||
it('getBasePath returns "/signoz/"', () => {
|
||||
expect(m.getBasePath()).toBe('/signoz/');
|
||||
});
|
||||
|
||||
it('withBasePath prepends the prefix', () => {
|
||||
expect(m.withBasePath('/logs')).toBe('/signoz/logs');
|
||||
expect(m.withBasePath('/logs/explorer')).toBe('/signoz/logs/explorer');
|
||||
});
|
||||
|
||||
it('withBasePath is idempotent — safe to call twice', () => {
|
||||
expect(m.withBasePath('/signoz/logs')).toBe('/signoz/logs');
|
||||
});
|
||||
|
||||
it('withBasePath is idempotent when path equals the prefix without trailing slash', () => {
|
||||
expect(m.withBasePath('/signoz')).toBe('/signoz');
|
||||
});
|
||||
|
||||
it('withBasePath passes through external URLs', () => {
|
||||
expect(m.withBasePath('https://example.com/foo')).toBe(
|
||||
'https://example.com/foo',
|
||||
);
|
||||
});
|
||||
|
||||
it('getAbsoluteUrl returns origin + prefixed path', () => {
|
||||
expect(m.getAbsoluteUrl('/logs')).toBe(
|
||||
`${window.location.origin}/signoz/logs`,
|
||||
);
|
||||
});
|
||||
|
||||
it('getBaseUrl returns origin + prefix without trailing slash', () => {
|
||||
expect(m.getBaseUrl()).toBe(`${window.location.origin}/signoz`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('no <base> tag', () => {
|
||||
it('getBasePath falls back to "/"', () => {
|
||||
const m = loadModule();
|
||||
expect(m.getBasePath()).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('href without trailing slash', () => {
|
||||
it('normalises to trailing slash', () => {
|
||||
const m = loadModule('/signoz');
|
||||
expect(m.getBasePath()).toBe('/signoz/');
|
||||
expect(m.withBasePath('/logs')).toBe('/signoz/logs');
|
||||
});
|
||||
});
|
||||
|
||||
describe('nested prefix "/a/b/prefix/"', () => {
|
||||
it('withBasePath handles arbitrary depth', () => {
|
||||
const m = loadModule('/a/b/prefix/');
|
||||
expect(m.withBasePath('/logs')).toBe('/a/b/prefix/logs');
|
||||
expect(m.withBasePath('/a/b/prefix/logs')).toBe('/a/b/prefix/logs');
|
||||
});
|
||||
});
|
||||
@@ -1,25 +1,38 @@
|
||||
import { isModifierKeyPressed } from '../app';
|
||||
import { openInNewTab } from '../navigation';
|
||||
|
||||
type NavigationModule = typeof import('../navigation');
|
||||
|
||||
function loadNavigationModule(href?: string): NavigationModule {
|
||||
if (href !== undefined) {
|
||||
const base = document.createElement('base');
|
||||
base.setAttribute('href', href);
|
||||
document.head.append(base);
|
||||
}
|
||||
let mod!: NavigationModule;
|
||||
jest.isolateModules(() => {
|
||||
// oxlint-disable-next-line typescript-eslint/no-require-imports, typescript-eslint/no-var-requires
|
||||
mod = require('../navigation');
|
||||
});
|
||||
return mod;
|
||||
}
|
||||
|
||||
const createMouseEvent = (overrides: Partial<MouseEvent> = {}): MouseEvent =>
|
||||
({
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
button: 0,
|
||||
...overrides,
|
||||
}) as MouseEvent;
|
||||
|
||||
describe('navigation utilities', () => {
|
||||
const originalWindowOpen = window.open;
|
||||
|
||||
beforeEach(() => {
|
||||
window.open = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
window.open = originalWindowOpen;
|
||||
for (const el of document.head.querySelectorAll('base')) { el.remove(); }
|
||||
});
|
||||
|
||||
describe('isModifierKeyPressed', () => {
|
||||
const createMouseEvent = (overrides: Partial<MouseEvent> = {}): MouseEvent =>
|
||||
({
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
button: 0,
|
||||
...overrides,
|
||||
} as MouseEvent);
|
||||
|
||||
it('returns true when metaKey is pressed (Cmd on Mac)', () => {
|
||||
const event = createMouseEvent({ metaKey: true });
|
||||
@@ -56,25 +69,59 @@ describe('navigation utilities', () => {
|
||||
});
|
||||
|
||||
describe('openInNewTab', () => {
|
||||
it('calls window.open with the given path and _blank target', () => {
|
||||
openInNewTab('/dashboard');
|
||||
expect(window.open).toHaveBeenCalledWith('/dashboard', '_blank');
|
||||
describe('at basePath="/"', () => {
|
||||
let m: NavigationModule;
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'open').mockImplementation();
|
||||
m = loadNavigationModule('/');
|
||||
});
|
||||
|
||||
it('passes internal path through unchanged', () => {
|
||||
m.openInNewTab('/dashboard');
|
||||
expect(window.open).toHaveBeenCalledWith('/dashboard', '_blank');
|
||||
});
|
||||
|
||||
it('passes through external URLs unchanged', () => {
|
||||
m.openInNewTab('https://example.com/page');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://example.com/page',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('handles paths with query strings', () => {
|
||||
m.openInNewTab('/alerts?tab=AlertRules&relativeTime=30m');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'/alerts?tab=AlertRules&relativeTime=30m',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('handles full URLs', () => {
|
||||
openInNewTab('https://example.com/page');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://example.com/page',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
describe('at basePath="/signoz/"', () => {
|
||||
let m: NavigationModule;
|
||||
beforeEach(() => {
|
||||
jest.spyOn(window, 'open').mockImplementation();
|
||||
m = loadNavigationModule('/signoz/');
|
||||
});
|
||||
|
||||
it('handles paths with query strings', () => {
|
||||
openInNewTab('/alerts?tab=AlertRules&relativeTime=30m');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'/alerts?tab=AlertRules&relativeTime=30m',
|
||||
'_blank',
|
||||
);
|
||||
it('prepends base path to internal paths', () => {
|
||||
m.openInNewTab('/dashboard');
|
||||
expect(window.open).toHaveBeenCalledWith('/signoz/dashboard', '_blank');
|
||||
});
|
||||
|
||||
it('passes through external URLs unchanged', () => {
|
||||
m.openInNewTab('https://example.com/page');
|
||||
expect(window.open).toHaveBeenCalledWith(
|
||||
'https://example.com/page',
|
||||
'_blank',
|
||||
);
|
||||
});
|
||||
|
||||
it('is idempotent — does not double-prefix an already-prefixed path', () => {
|
||||
m.openInNewTab('/signoz/dashboard');
|
||||
expect(window.open).toHaveBeenCalledWith('/signoz/dashboard', '_blank');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
51
frontend/src/utils/basePath.ts
Normal file
51
frontend/src/utils/basePath.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
// Read once at module init — avoids a DOM query on every axios request.
|
||||
const _basePath: string = ((): string => {
|
||||
const href = document.querySelector('base')?.getAttribute('href') ?? '/';
|
||||
return href.endsWith('/') ? href : `${href}/`;
|
||||
})();
|
||||
|
||||
/** Returns the runtime base path — always trailing-slashed. e.g. "/" or "/signoz/" */
|
||||
export function getBasePath(): string {
|
||||
return _basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends the base path to an internal absolute path.
|
||||
* Idempotent and safe to call on any value.
|
||||
*
|
||||
* withBasePath('/logs') → '/signoz/logs'
|
||||
* withBasePath('/signoz/logs') → '/signoz/logs' (already prefixed)
|
||||
* withBasePath('https://x.com') → 'https://x.com' (external, passthrough)
|
||||
*/
|
||||
export function withBasePath(path: string): string {
|
||||
if (!path.startsWith('/')) {
|
||||
return path;
|
||||
}
|
||||
if (_basePath === '/') {
|
||||
return path;
|
||||
}
|
||||
if (path.startsWith(_basePath) || path === _basePath.slice(0, -1)) {
|
||||
return path;
|
||||
}
|
||||
return _basePath + path.slice(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Full absolute URL — for copy-to-clipboard and window.open calls.
|
||||
* getAbsoluteUrl(ROUTES.LOGS_EXPLORER) → 'https://host/signoz/logs/logs-explorer'
|
||||
*/
|
||||
export function getAbsoluteUrl(path: string): string {
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path
|
||||
return window.location.origin + withBasePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Origin + base path without trailing slash — for sending to the backend
|
||||
* as frontendBaseUrl in invite / password-reset email flows.
|
||||
* getBaseUrl() → 'https://host/signoz'
|
||||
*/
|
||||
export function getBaseUrl(): string {
|
||||
// oxlint-disable-next-line signoz/no-raw-absolute-path
|
||||
const origin = window.location.origin;
|
||||
return origin + (_basePath === '/' ? '' : _basePath.slice(0, -1));
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
/**
|
||||
* Opens the given path in a new browser tab.
|
||||
*/
|
||||
import { withBasePath } from 'utils/basePath';
|
||||
|
||||
export const openInNewTab = (path: string): void => {
|
||||
window.open(path, '_blank');
|
||||
window.open(withBasePath(path), '_blank');
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ const ruleName = 'local/no-unsupported-asset-url';
|
||||
* e.g. "url('/a.svg') url('/b.png') repeat" → ['/a.svg', '/b.png']
|
||||
*/
|
||||
function extractUrlPaths(value) {
|
||||
if (typeof value !== 'string') return [];
|
||||
if (typeof value !== 'string') {return [];}
|
||||
const paths = [];
|
||||
const urlPattern = /url\(\s*['"]?([^'")\s]+)['"]?\s*\)/g;
|
||||
let match = urlPattern.exec(value);
|
||||
@@ -71,7 +71,7 @@ const messages = stylelint.utils.ruleMessages(ruleName, {
|
||||
*/
|
||||
const rule = (primaryOption) => {
|
||||
return (root, result) => {
|
||||
if (!primaryOption) return;
|
||||
if (!primaryOption) {return;}
|
||||
|
||||
root.walkDecls((decl) => {
|
||||
const urlPaths = extractUrlPaths(decl.value);
|
||||
|
||||
@@ -10,6 +10,19 @@ import { createHtmlPlugin } from 'vite-plugin-html';
|
||||
import { ViteImageOptimizer } from 'vite-plugin-image-optimizer';
|
||||
import tsconfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
// In dev the Go backend is not involved, so replace the [[.BaseHref]] placeholder
|
||||
// with the configured base path so relative assets resolve correctly from the Vite dev server.
|
||||
// Set VITE_BASE_PATH env var to test with a custom base path (e.g., /signoz/).
|
||||
function devBasePathPlugin(basePath: string): Plugin {
|
||||
return {
|
||||
name: 'dev-base-path',
|
||||
apply: 'serve',
|
||||
transformIndexHtml(html): string {
|
||||
return html.replaceAll('[[.BaseHref]]', basePath);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function rawMarkdownPlugin(): Plugin {
|
||||
return {
|
||||
name: 'raw-markdown',
|
||||
@@ -28,10 +41,13 @@ function rawMarkdownPlugin(): Plugin {
|
||||
export default defineConfig(
|
||||
({ mode }): UserConfig => {
|
||||
const env = loadEnv(mode, process.cwd(), '');
|
||||
// Base path for serving the app (e.g., '/signoz/'). Defaults to '/'.
|
||||
const basePath = env.VITE_BASE_PATH || '/';
|
||||
|
||||
const plugins = [
|
||||
tsconfigPaths(),
|
||||
rawMarkdownPlugin(),
|
||||
devBasePathPlugin(basePath),
|
||||
react(),
|
||||
createHtmlPlugin({
|
||||
inject: {
|
||||
@@ -124,6 +140,9 @@ export default defineConfig(
|
||||
'process.env.TUNNEL_DOMAIN': JSON.stringify(env.VITE_TUNNEL_DOMAIN),
|
||||
'process.env.DOCS_BASE_URL': JSON.stringify(env.VITE_DOCS_BASE_URL),
|
||||
},
|
||||
// In production, use relative paths so assets work with any base path injected by the backend.
|
||||
// In dev, use the configured base path for proper HMR and routing.
|
||||
base: mode === 'production' ? './' : basePath,
|
||||
build: {
|
||||
sourcemap: true,
|
||||
outDir: 'build',
|
||||
|
||||
@@ -22,11 +22,15 @@ type AuthZ interface {
|
||||
// BatchCheck accepts a map of ID → tuple and returns a map of ID → authorization result.
|
||||
BatchCheck(context.Context, map[string]*openfgav1.TupleKey) (map[string]*authtypes.TupleKeyAuthorization, error)
|
||||
|
||||
// CheckTransactions checks whether the given subject is authorized for the given transactions.
|
||||
// Returns results in the same order as the input transactions.
|
||||
CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error)
|
||||
|
||||
// Write accepts the insertion tuples and the deletion tuples.
|
||||
Write(context.Context, []*openfgav1.TupleKey, []*openfgav1.TupleKey) error
|
||||
|
||||
// Lists the selectors for objects assigned to subject (s) with relation (r) on resource (s)
|
||||
ListObjects(context.Context, string, authtypes.Relation, authtypes.Typeable) ([]*authtypes.Object, error)
|
||||
ListObjects(context.Context, string, authtypes.Relation, authtypes.Type) ([]*authtypes.Object, error)
|
||||
|
||||
// Creates the role.
|
||||
Create(context.Context, valuer.UUID, *authtypes.Role) error
|
||||
@@ -78,8 +82,14 @@ type AuthZ interface {
|
||||
|
||||
// Bootstrap managed roles transactions and user assignments
|
||||
CreateManagedUserRoleTransactions(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
// ReadTuples reads tuples from the authorization server matching the given tuple key filter.
|
||||
ReadTuples(context.Context, *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error)
|
||||
}
|
||||
|
||||
// OnBeforeRoleDelete is a callback invoked before a role is deleted.
|
||||
type OnBeforeRoleDelete func(context.Context, valuer.UUID, valuer.UUID) error
|
||||
|
||||
type RegisterTypeable interface {
|
||||
MustGetTypeables() []authtypes.Typeable
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewSqlAuthzStore(sqlstore sqlstore.SQLStore) authtypes.RoleStore {
|
||||
return &store{sqlstore: sqlstore}
|
||||
}
|
||||
|
||||
func (store *store) Create(ctx context.Context, role *authtypes.StorableRole) error {
|
||||
func (store *store) Create(ctx context.Context, role *authtypes.Role) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -32,8 +32,8 @@ func (store *store) Create(ctx context.Context, role *authtypes.StorableRole) er
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
role := new(authtypes.Role)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -49,8 +49,8 @@ func (store *store) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID)
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.StorableRole, error) {
|
||||
role := new(authtypes.StorableRole)
|
||||
func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
role := new(authtypes.Role)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -66,8 +66,8 @@ func (store *store) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, na
|
||||
return role, nil
|
||||
}
|
||||
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -82,8 +82,8 @@ func (store *store) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.S
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -103,8 +103,8 @@ func (store *store) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID,
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.StorableRole, error) {
|
||||
roles := make([]*authtypes.StorableRole, 0)
|
||||
func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
roles := make([]*authtypes.Role, 0)
|
||||
err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -124,7 +124,7 @@ func (store *store) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, id
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *authtypes.StorableRole) error {
|
||||
func (store *store) Update(ctx context.Context, orgID valuer.UUID, role *authtypes.Role) error {
|
||||
_, err := store.
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
@@ -145,7 +145,7 @@ func (store *store) Delete(ctx context.Context, orgID valuer.UUID, id valuer.UUI
|
||||
sqlstore.
|
||||
BunDBCtx(ctx).
|
||||
NewDelete().
|
||||
Model(new(authtypes.StorableRole)).
|
||||
Model(new(authtypes.Role)).
|
||||
Where("org_id = ?", orgID).
|
||||
Where("id = ?", id).
|
||||
Exec(ctx)
|
||||
|
||||
@@ -4,14 +4,30 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
)
|
||||
|
||||
type Config struct{}
|
||||
type Config struct {
|
||||
// Provider is the name of the authorization provider to use.
|
||||
Provider string `mapstructure:"provider"`
|
||||
|
||||
// OpenFGA is the configuration specific to the OpenFGA authorization provider.
|
||||
OpenFGA OpenFGAConfig `mapstructure:"openfga"`
|
||||
}
|
||||
|
||||
type OpenFGAConfig struct {
|
||||
// MaxTuplesPerWrite is the maximum number of tuples to include in a single write call.
|
||||
MaxTuplesPerWrite int `mapstructure:"max_tuples_per_write"`
|
||||
}
|
||||
|
||||
func NewConfigFactory() factory.ConfigFactory {
|
||||
return factory.NewConfigFactory(factory.MustNewName("authz"), newConfig)
|
||||
}
|
||||
|
||||
func newConfig() factory.Config {
|
||||
return Config{}
|
||||
return &Config{
|
||||
Provider: "openfga",
|
||||
OpenFGA: OpenFGAConfig{
|
||||
MaxTuplesPerWrite: 100,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) Validate() error {
|
||||
|
||||
@@ -18,25 +18,31 @@ import (
|
||||
)
|
||||
|
||||
type provider struct {
|
||||
server *openfgaserver.Server
|
||||
store authtypes.RoleStore
|
||||
server *openfgaserver.Server
|
||||
store authtypes.RoleStore
|
||||
registry []authz.RegisterTypeable
|
||||
managedRolesByTransaction map[string][]string
|
||||
}
|
||||
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore) factory.ProviderFactory[authz.AuthZ, authz.Config] {
|
||||
func NewProviderFactory(sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, 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)
|
||||
return newOpenfgaProvider(ctx, ps, config, sqlstore, openfgaSchema, openfgaDataStore, registry)
|
||||
})
|
||||
}
|
||||
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore) (authz.AuthZ, error) {
|
||||
func newOpenfgaProvider(ctx context.Context, settings factory.ProviderSettings, config authz.Config, sqlstore sqlstore.SQLStore, openfgaSchema []openfgapkgtransformer.ModuleFile, openfgaDataStore storage.OpenFGADatastore, registry []authz.RegisterTypeable) (authz.AuthZ, error) {
|
||||
server, err := openfgaserver.NewOpenfgaServer(ctx, settings, config, sqlstore, openfgaSchema, openfgaDataStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
managedRolesByTransaction := buildManagedRolesByTransaction(registry)
|
||||
|
||||
return &provider{
|
||||
server: server,
|
||||
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
|
||||
server: server,
|
||||
store: sqlauthzstore.NewSqlAuthzStore(sqlstore),
|
||||
registry: registry,
|
||||
managedRolesByTransaction: managedRolesByTransaction,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -68,68 +74,32 @@ func (provider *provider) Write(ctx context.Context, additions []*openfgav1.Tupl
|
||||
return provider.server.Write(ctx, additions, deletions)
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
return provider.server.ListObjects(ctx, subject, relation, typeable)
|
||||
func (provider *provider) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
|
||||
return provider.server.ReadTuples(ctx, tupleKey)
|
||||
}
|
||||
|
||||
func (provider *provider) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
return provider.server.ListObjects(ctx, subject, relation, objectType)
|
||||
}
|
||||
|
||||
func (provider *provider) Get(ctx context.Context, orgID valuer.UUID, id valuer.UUID) (*authtypes.Role, error) {
|
||||
storableRole, err := provider.store.Get(ctx, orgID, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRoleFromStorableRole(storableRole), nil
|
||||
return provider.store.Get(ctx, orgID, id)
|
||||
}
|
||||
|
||||
func (provider *provider) GetByOrgIDAndName(ctx context.Context, orgID valuer.UUID, name string) (*authtypes.Role, error) {
|
||||
storableRole, err := provider.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewRoleFromStorableRole(storableRole), nil
|
||||
return provider.store.GetByOrgIDAndName(ctx, orgID, name)
|
||||
}
|
||||
|
||||
func (provider *provider) List(ctx context.Context, orgID valuer.UUID) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.List(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storableRole := range storableRoles {
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storableRole)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
return provider.store.List(ctx, orgID)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndNames(ctx context.Context, orgID valuer.UUID, names []string) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storable := range storableRoles {
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
return provider.store.ListByOrgIDAndNames(ctx, orgID, names)
|
||||
}
|
||||
|
||||
func (provider *provider) ListByOrgIDAndIDs(ctx context.Context, orgID valuer.UUID, ids []valuer.UUID) ([]*authtypes.Role, error) {
|
||||
storableRoles, err := provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]*authtypes.Role, len(storableRoles))
|
||||
for idx, storable := range storableRoles {
|
||||
roles[idx] = authtypes.NewRoleFromStorableRole(storable)
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
return provider.store.ListByOrgIDAndIDs(ctx, orgID, ids)
|
||||
}
|
||||
|
||||
func (provider *provider) Grant(ctx context.Context, orgID valuer.UUID, names []string, subject string) error {
|
||||
@@ -197,7 +167,7 @@ func (provider *provider) Revoke(ctx context.Context, orgID valuer.UUID, names [
|
||||
func (provider *provider) CreateManagedRoles(ctx context.Context, _ valuer.UUID, managedRoles []*authtypes.Role) error {
|
||||
err := provider.store.RunInTx(ctx, func(ctx context.Context) error {
|
||||
for _, role := range managedRoles {
|
||||
err := provider.store.Create(ctx, authtypes.NewStorableRoleFromRole(role))
|
||||
err := provider.store.Create(ctx, role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -245,6 +215,42 @@ func (provider *provider) Delete(_ context.Context, _ valuer.UUID, _ valuer.UUID
|
||||
return errors.Newf(errors.TypeUnsupported, authtypes.ErrCodeRoleUnsupported, "not implemented")
|
||||
}
|
||||
|
||||
func (provider *provider) CheckTransactions(ctx context.Context, subject string, orgID valuer.UUID, transactions []*authtypes.Transaction) ([]*authtypes.TransactionWithAuthorization, error) {
|
||||
if len(transactions) == 0 {
|
||||
return make([]*authtypes.TransactionWithAuthorization, 0), nil
|
||||
}
|
||||
|
||||
tuples, preResolved, roleCorrelations, err := authtypes.NewTuplesFromTransactionsWithManagedRoles(transactions, subject, orgID, provider.managedRolesByTransaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(tuples) == 0 {
|
||||
return authtypes.NewTransactionWithAuthorizationFromBatchResults(transactions, nil, preResolved, roleCorrelations), nil
|
||||
}
|
||||
|
||||
batchResults, err := provider.server.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return authtypes.NewTransactionWithAuthorizationFromBatchResults(transactions, batchResults, preResolved, roleCorrelations), nil
|
||||
}
|
||||
|
||||
func buildManagedRolesByTransaction(registry []authz.RegisterTypeable) map[string][]string {
|
||||
managedRolesByTransaction := make(map[string][]string)
|
||||
for _, register := range registry {
|
||||
for roleName, transactions := range register.MustGetManagedRoleTransactions() {
|
||||
for _, txn := range transactions {
|
||||
key := txn.TransactionKey()
|
||||
managedRolesByTransaction[key] = append(managedRolesByTransaction[key], roleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return managedRolesByTransaction
|
||||
}
|
||||
|
||||
func (provider *provider) MustGetTypeables() []authtypes.Typeable {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -265,17 +265,45 @@ func (server *Server) Write(ctx context.Context, additions []*openfgav1.TupleKey
|
||||
return nil
|
||||
}
|
||||
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, typeable authtypes.Typeable) ([]*authtypes.Object, error) {
|
||||
func (server *Server) ReadTuples(ctx context.Context, tupleKey *openfgav1.ReadRequestTupleKey) ([]*openfgav1.TupleKey, error) {
|
||||
storeID, _ := server.getStoreIDandModelID()
|
||||
var tuples []*openfgav1.TupleKey
|
||||
continuationToken := ""
|
||||
|
||||
for {
|
||||
response, err := server.openfgaServer.Read(ctx, &openfgav1.ReadRequest{
|
||||
StoreId: storeID,
|
||||
TupleKey: tupleKey,
|
||||
ContinuationToken: continuationToken,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "failed to read tuples from authorization server")
|
||||
}
|
||||
|
||||
for _, tuple := range response.Tuples {
|
||||
tuples = append(tuples, tuple.Key)
|
||||
}
|
||||
|
||||
if response.ContinuationToken == "" {
|
||||
break
|
||||
}
|
||||
continuationToken = response.ContinuationToken
|
||||
}
|
||||
|
||||
return tuples, nil
|
||||
}
|
||||
|
||||
func (server *Server) ListObjects(ctx context.Context, subject string, relation authtypes.Relation, objectType authtypes.Type) ([]*authtypes.Object, error) {
|
||||
storeID, modelID := server.getStoreIDandModelID()
|
||||
response, err := server.openfgaServer.ListObjects(ctx, &openfgav1.ListObjectsRequest{
|
||||
StoreId: storeID,
|
||||
AuthorizationModelId: modelID,
|
||||
User: subject,
|
||||
Relation: relation.StringValue(),
|
||||
Type: typeable.Type().StringValue(),
|
||||
Type: objectType.StringValue(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), typeable.Type().StringValue())
|
||||
return nil, errors.Wrapf(err, errors.TypeInternal, authtypes.ErrCodeAuthZUnavailable, "cannot list objects for subject %s with relation %s for type %s", subject, relation.StringValue(), objectType.StringValue())
|
||||
}
|
||||
|
||||
return authtypes.MustNewObjectsFromStringSlice(response.Objects), nil
|
||||
|
||||
@@ -272,17 +272,11 @@ func (handler *handler) Check(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
tuples, err := authtypes.NewTuplesFromTransactions(transactions, subject, orgID)
|
||||
results, err := handler.authz.CheckTransactions(ctx, subject, orgID, transactions)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
results, err := handler.authz.BatchCheck(ctx, tuples)
|
||||
if err != nil {
|
||||
render.Error(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
render.Success(rw, http.StatusOK, authtypes.NewGettableTransaction(transactions, results))
|
||||
render.Success(rw, http.StatusOK, authtypes.NewGettableTransaction(results))
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// Package flaggertest provides helpers for creating Flagger instances in tests.
|
||||
package flaggertest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/flagger/configflagger"
|
||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||
)
|
||||
|
||||
// New returns a Flagger with all flags at their registry defaults (all disabled).
|
||||
// Use this in tests that do not need any feature flag enabled.
|
||||
func New(t *testing.T) flagger.Flagger {
|
||||
t.Helper()
|
||||
registry := flagger.MustNewRegistry()
|
||||
fl, err := flagger.New(
|
||||
context.Background(),
|
||||
instrumentationtest.New().ToProviderSettings(),
|
||||
flagger.Config{},
|
||||
registry,
|
||||
configflagger.NewFactory(registry),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("flaggertest.New: %v", err)
|
||||
}
|
||||
return fl
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
package flagger
|
||||
|
||||
import (
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
)
|
||||
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
|
||||
var (
|
||||
FeatureUseSpanMetrics = featuretypes.MustNewName("use_span_metrics")
|
||||
@@ -10,7 +8,6 @@ var (
|
||||
FeatureHideRootUser = featuretypes.MustNewName("hide_root_user")
|
||||
FeatureGetMetersFromZeus = featuretypes.MustNewName("get_meters_from_zeus")
|
||||
FeaturePutMetersInZeus = featuretypes.MustNewName("put_meters_in_zeus")
|
||||
FeatureBodyJSONQuery = featuretypes.MustNewName("body_json_enabled") // Note: JSON Enabled is not restrictive of orgID, it is tenant level featureflag
|
||||
)
|
||||
|
||||
func MustNewRegistry() featuretypes.Registry {
|
||||
@@ -55,14 +52,6 @@ func MustNewRegistry() featuretypes.Registry {
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
&featuretypes.Feature{
|
||||
Name: FeatureBodyJSONQuery,
|
||||
Kind: featuretypes.KindBoolean,
|
||||
Stage: featuretypes.StageExperimental,
|
||||
Description: "Controls whether body JSON querying is enabled",
|
||||
DefaultVariant: featuretypes.MustNewName("disabled"),
|
||||
Variants: featuretypes.NewBooleanVariants(),
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user