Compare commits
11 Commits
base-path-
...
feat/field
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fe4078e955 | ||
|
|
7b45fe46f7 | ||
|
|
87a1c0412b | ||
|
|
9c3fa49eaa | ||
|
|
e1ba1d8d09 | ||
|
|
34de92339e | ||
|
|
46178decdf | ||
|
|
bbfba01aa9 | ||
|
|
34c6b593cb | ||
|
|
356676708b | ||
|
|
1c7b37d3e5 |
@@ -18,15 +18,11 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/gateway/noopgateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
@@ -104,9 +100,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
func(ps factory.ProviderSettings, q querier.Querier, a analytics.Analytics) querier.Handler {
|
||||
return querier.NewHandler(ps, q, a)
|
||||
},
|
||||
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
return implcloudintegration.NewModule(), nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))
|
||||
|
||||
@@ -17,8 +17,6 @@ import (
|
||||
"github.com/SigNoz/signoz/ee/gateway/httpgateway"
|
||||
enterpriselicensing "github.com/SigNoz/signoz/ee/licensing"
|
||||
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/ee/modules/cloudintegration/implcloudintegration/implcloudprovider"
|
||||
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
|
||||
eequerier "github.com/SigNoz/signoz/ee/querier"
|
||||
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
|
||||
@@ -33,14 +31,10 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/factory"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
pkgcloudintegration "github.com/SigNoz/signoz/pkg/modules/cloudintegration/implcloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/dashboard"
|
||||
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
|
||||
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/querier"
|
||||
"github.com/SigNoz/signoz/pkg/queryparser"
|
||||
"github.com/SigNoz/signoz/pkg/signoz"
|
||||
@@ -48,7 +42,6 @@ import (
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/version"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
@@ -134,6 +127,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
return nil, err
|
||||
}
|
||||
return openfgaauthz.NewProviderFactory(sqlstore, openfgaschema.NewSchema().Get(ctx), openfgaDataStore, licensing, 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)
|
||||
@@ -152,21 +146,8 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
|
||||
communityHandler := querier.NewHandler(ps, q, a)
|
||||
return eequerier.NewHandler(ps, q, communityHandler)
|
||||
},
|
||||
func(sqlStore sqlstore.SQLStore, global global.Global, zeus zeus.Zeus, gateway gateway.Gateway, licensing licensing.Licensing, serviceAccount serviceaccount.Module, config cloudintegration.Config) (cloudintegration.Module, error) {
|
||||
defStore := pkgcloudintegration.NewServiceDefinitionStore()
|
||||
awsCloudProviderModule, err := implcloudprovider.NewAWSCloudProvider(defStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
azureCloudProviderModule := implcloudprovider.NewAzureCloudProvider()
|
||||
cloudProvidersMap := map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule{
|
||||
cloudintegrationtypes.CloudProviderTypeAWS: awsCloudProviderModule,
|
||||
cloudintegrationtypes.CloudProviderTypeAzure: azureCloudProviderModule,
|
||||
}
|
||||
|
||||
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))
|
||||
return err
|
||||
|
||||
@@ -82,9 +82,6 @@ sqlstore:
|
||||
provider: sqlite
|
||||
# The maximum number of open connections to the database.
|
||||
max_open_conns: 100
|
||||
# The maximum amount of time a connection may be reused.
|
||||
# If max_conn_lifetime == 0, connections are not closed due to a connection's age.
|
||||
max_conn_lifetime: 0
|
||||
sqlite:
|
||||
# The path to the SQLite database file.
|
||||
path: /var/lib/signoz/signoz.db
|
||||
@@ -398,10 +395,3 @@ auditor:
|
||||
max_interval: 30s
|
||||
# The total maximum time spent retrying.
|
||||
max_elapsed_time: 60s
|
||||
|
||||
##################### Cloud Integration #####################
|
||||
cloudintegration:
|
||||
# cloud integration agent configuration
|
||||
agent:
|
||||
# The version of the cloud integration agent.
|
||||
version: v0.0.8
|
||||
|
||||
@@ -4465,6 +4465,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -4550,6 +4554,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -8312,6 +8320,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
@@ -8409,6 +8421,10 @@ paths:
|
||||
name: metricName
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: metricNamespace
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: searchText
|
||||
schema:
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
package implcloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
)
|
||||
|
||||
type awscloudprovider struct {
|
||||
serviceDefinitions cloudintegrationtypes.ServiceDefinitionStore
|
||||
}
|
||||
|
||||
func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore) (cloudintegration.CloudProviderModule, error) {
|
||||
return &awscloudprovider{serviceDefinitions: defStore}, nil
|
||||
}
|
||||
|
||||
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.Aws.DeploymentRegion)
|
||||
u, _ := url.Parse(baseURL)
|
||||
|
||||
q := u.Query()
|
||||
q.Set("region", req.Config.Aws.DeploymentRegion)
|
||||
u.Fragment = "/stacks/quickcreate"
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
q = u.Query()
|
||||
q.Set("stackName", cloudintegrationtypes.AgentCloudFormationBaseStackName.StringValue())
|
||||
q.Set("templateURL", fmt.Sprintf(cloudintegrationtypes.AgentCloudFormationTemplateS3Path.StringValue(), req.Config.AgentVersion))
|
||||
q.Set("param_SigNozIntegrationAgentVersion", req.Config.AgentVersion)
|
||||
q.Set("param_SigNozApiUrl", req.Credentials.SigNozAPIURL)
|
||||
q.Set("param_SigNozApiKey", req.Credentials.SigNozAPIKey)
|
||||
q.Set("param_SigNozAccountId", account.ID.StringValue())
|
||||
q.Set("param_IngestionUrl", req.Credentials.IngestionURL)
|
||||
q.Set("param_IngestionKey", req.Credentials.IngestionKey)
|
||||
|
||||
return &cloudintegrationtypes.ConnectionArtifact{
|
||||
Aws: &cloudintegrationtypes.AWSConnectionArtifact{
|
||||
ConnectionURL: u.String() + "?&" + q.Encode(), // this format is required by AWS
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (provider *awscloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
return provider.serviceDefinitions.List(ctx, cloudintegrationtypes.CloudProviderTypeAWS)
|
||||
}
|
||||
|
||||
func (provider *awscloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
serviceDef, err := provider.serviceDefinitions.Get(ctx, cloudintegrationtypes.CloudProviderTypeAWS, 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.CloudProviderTypeAWS, serviceID.StringValue(), dashboard.ID)
|
||||
}
|
||||
|
||||
return serviceDef, nil
|
||||
}
|
||||
|
||||
func (provider *awscloudprovider) BuildIntegrationConfig(
|
||||
ctx context.Context,
|
||||
account *cloudintegrationtypes.Account,
|
||||
services []*cloudintegrationtypes.StorableCloudIntegrationService,
|
||||
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
|
||||
// Sort services for deterministic output
|
||||
sort.Slice(services, func(i, j int) bool {
|
||||
return services[i].Type.StringValue() < services[j].Type.StringValue()
|
||||
})
|
||||
|
||||
compiledMetrics := new(cloudintegrationtypes.AWSMetricsCollectionStrategy)
|
||||
compiledLogs := new(cloudintegrationtypes.AWSLogsCollectionStrategy)
|
||||
var compiledS3Buckets map[string][]string
|
||||
|
||||
for _, storedSvc := range services {
|
||||
svcCfg, err := cloudintegrationtypes.NewServiceConfigFromJSON(cloudintegrationtypes.CloudProviderTypeAWS, storedSvc.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
svcDef, err := provider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
strategy := svcDef.TelemetryCollectionStrategy.AWS
|
||||
logsEnabled := svcCfg.IsLogsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
|
||||
|
||||
// S3Sync: logs come directly from configured S3 buckets, not CloudWatch subscriptions
|
||||
if storedSvc.Type == cloudintegrationtypes.AWSServiceS3Sync {
|
||||
if logsEnabled && svcCfg.AWS.Logs.S3Buckets != nil {
|
||||
compiledS3Buckets = svcCfg.AWS.Logs.S3Buckets
|
||||
}
|
||||
// no need to go ahead as the code block specifically checks for the S3Sync service
|
||||
continue
|
||||
}
|
||||
|
||||
if logsEnabled && strategy.Logs != nil {
|
||||
compiledLogs.Subscriptions = append(compiledLogs.Subscriptions, strategy.Logs.Subscriptions...)
|
||||
}
|
||||
|
||||
metricsEnabled := svcCfg.IsMetricsEnabled(cloudintegrationtypes.CloudProviderTypeAWS)
|
||||
|
||||
if metricsEnabled && strategy.Metrics != nil {
|
||||
compiledMetrics.StreamFilters = append(compiledMetrics.StreamFilters, strategy.Metrics.StreamFilters...)
|
||||
}
|
||||
}
|
||||
|
||||
collectionStrategy := new(cloudintegrationtypes.AWSTelemetryCollectionStrategy)
|
||||
|
||||
if len(compiledMetrics.StreamFilters) > 0 {
|
||||
collectionStrategy.Metrics = compiledMetrics
|
||||
}
|
||||
if len(compiledLogs.Subscriptions) > 0 {
|
||||
collectionStrategy.Logs = compiledLogs
|
||||
}
|
||||
if compiledS3Buckets != nil {
|
||||
collectionStrategy.S3Buckets = compiledS3Buckets
|
||||
}
|
||||
|
||||
return &cloudintegrationtypes.ProviderIntegrationConfig{
|
||||
AWS: &cloudintegrationtypes.AWSIntegrationConfig{
|
||||
EnabledRegions: account.Config.AWS.Regions,
|
||||
TelemetryCollectionStrategy: collectionStrategy,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package implcloudprovider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
)
|
||||
|
||||
type azurecloudprovider struct{}
|
||||
|
||||
func NewAzureCloudProvider() cloudintegration.CloudProviderModule {
|
||||
return &azurecloudprovider{}
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) ListServiceDefinitions(ctx context.Context) ([]*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) GetServiceDefinition(ctx context.Context, serviceID cloudintegrationtypes.ServiceID) (*cloudintegrationtypes.ServiceDefinition, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (provider *azurecloudprovider) BuildIntegrationConfig(
|
||||
ctx context.Context,
|
||||
account *cloudintegrationtypes.Account,
|
||||
services []*cloudintegrationtypes.StorableCloudIntegrationService,
|
||||
) (*cloudintegrationtypes.ProviderIntegrationConfig, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
@@ -1,540 +0,0 @@
|
||||
package implcloudintegration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/SigNoz/signoz/pkg/errors"
|
||||
"github.com/SigNoz/signoz/pkg/gateway"
|
||||
"github.com/SigNoz/signoz/pkg/global"
|
||||
"github.com/SigNoz/signoz/pkg/licensing"
|
||||
"github.com/SigNoz/signoz/pkg/modules/cloudintegration"
|
||||
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/dashboardtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/serviceaccounttypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/zeustypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
"github.com/SigNoz/signoz/pkg/zeus"
|
||||
)
|
||||
|
||||
type module struct {
|
||||
store cloudintegrationtypes.Store
|
||||
gateway gateway.Gateway
|
||||
zeus zeus.Zeus
|
||||
licensing licensing.Licensing
|
||||
global global.Global
|
||||
serviceAccount serviceaccount.Module
|
||||
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule
|
||||
config cloudintegration.Config
|
||||
}
|
||||
|
||||
func NewModule(
|
||||
store cloudintegrationtypes.Store,
|
||||
global global.Global,
|
||||
zeus zeus.Zeus,
|
||||
gateway gateway.Gateway,
|
||||
licensing licensing.Licensing,
|
||||
serviceAccount serviceaccount.Module,
|
||||
cloudProvidersMap map[cloudintegrationtypes.CloudProviderType]cloudintegration.CloudProviderModule,
|
||||
config cloudintegration.Config,
|
||||
) (cloudintegration.Module, error) {
|
||||
return &module{
|
||||
store: store,
|
||||
global: global,
|
||||
zeus: zeus,
|
||||
gateway: gateway,
|
||||
licensing: licensing,
|
||||
serviceAccount: serviceAccount,
|
||||
cloudProvidersMap: cloudProvidersMap,
|
||||
config: config,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetConnectionCredentials returns credentials required to generate connection artifact. eg. apiKey, ingestionKey etc.
|
||||
// It will return creds it can deduce and return empty value for others.
|
||||
func (module *module) GetConnectionCredentials(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Credentials, error) {
|
||||
// get license to get the deployment details
|
||||
license, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
// get deployment details from zeus, ignore error
|
||||
respBytes, _ := module.zeus.GetDeployment(ctx, license.Key)
|
||||
|
||||
var signozAPIURL string
|
||||
|
||||
if len(respBytes) > 0 {
|
||||
// parse deployment details, ignore error, if client is asking api url every time check for possible error
|
||||
deployment, _ := zeustypes.NewGettableDeployment(respBytes)
|
||||
if deployment != nil {
|
||||
signozAPIURL, _ = cloudintegrationtypes.GetSigNozAPIURLFromDeployment(deployment)
|
||||
}
|
||||
}
|
||||
|
||||
// ignore error
|
||||
apiKey, _ := module.getOrCreateAPIKey(ctx, orgID, provider)
|
||||
|
||||
// ignore error
|
||||
ingestionKey, _ := module.getOrCreateIngestionKey(ctx, orgID, provider)
|
||||
|
||||
return cloudintegrationtypes.NewCredentials(
|
||||
signozAPIURL,
|
||||
apiKey,
|
||||
module.global.GetConfig(ctx).IngestionURL,
|
||||
ingestionKey,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (module *module) CreateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
|
||||
_, err := module.licensing.GetActive(ctx, account.OrgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableCloudIntegration, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.CreateAccount(ctx, storableCloudIntegration)
|
||||
}
|
||||
|
||||
func (module *module) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
_, err := module.licensing.GetActive(ctx, account.OrgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
cloudProviderModule, err := module.getCloudProvider(account.Provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Config.SetAgentVersion(module.config.Agent.Version)
|
||||
return cloudProviderModule.GetConnectionArtifact(ctx, account, req)
|
||||
}
|
||||
|
||||
func (module *module) GetAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableAccount, err := module.store.GetAccountByID(ctx, orgID, accountID, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewAccountFromStorable(storableAccount)
|
||||
}
|
||||
|
||||
func (module *module) GetConnectedAccount(ctx context.Context, orgID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (*cloudintegrationtypes.Account, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableAccount, err := module.store.GetConnectedAccount(ctx, orgID, accountID, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewAccountFromStorable(storableAccount)
|
||||
}
|
||||
|
||||
// ListAccounts return only agent connected accounts.
|
||||
func (module *module) ListAccounts(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) ([]*cloudintegrationtypes.Account, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewAccountsFromStorables(storableAccounts)
|
||||
}
|
||||
|
||||
func (module *module) AgentCheckIn(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, req *cloudintegrationtypes.AgentCheckInRequest) (*cloudintegrationtypes.AgentCheckInResponse, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
connectedAccount, err := module.store.GetConnectedAccountByProviderAccountID(ctx, orgID, req.ProviderAccountID, provider)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If a different integration is already connected to this provider account ID, reject the check-in.
|
||||
// Allow re-check-in from the same integration (e.g. agent restarting).
|
||||
if connectedAccount != nil && connectedAccount.ID != req.CloudIntegrationID {
|
||||
errMessage := fmt.Sprintf("provider account id %s is already connected to cloud integration id %s", req.ProviderAccountID, connectedAccount.ID)
|
||||
return nil, errors.New(errors.TypeAlreadyExists, cloudintegrationtypes.ErrCodeCloudIntegrationAlreadyConnected, errMessage)
|
||||
}
|
||||
|
||||
account, err := module.store.GetAccountByID(ctx, orgID, req.CloudIntegrationID, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If account has been removed (disconnected), return a minimal response with empty integration config.
|
||||
// The agent uses this response to clean up resources
|
||||
if account.RemovedAt != nil {
|
||||
return cloudintegrationtypes.NewAgentCheckInResponse(
|
||||
req.ProviderAccountID,
|
||||
account.ID.StringValue(),
|
||||
new(cloudintegrationtypes.ProviderIntegrationConfig),
|
||||
account.RemovedAt,
|
||||
), nil
|
||||
}
|
||||
|
||||
// update account with cloud provider account id and agent report (heartbeat)
|
||||
account.Update(&req.ProviderAccountID, cloudintegrationtypes.NewAgentReport(req.Data))
|
||||
|
||||
err = module.store.UpdateAccount(ctx, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get account as domain object for config access (enabled regions, etc.)
|
||||
domainAccount, err := cloudintegrationtypes.NewAccountFromStorable(account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storedServices, err := module.store.ListServices(ctx, req.CloudIntegrationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delegate integration config building entirely to the provider module
|
||||
integrationConfig, err := cloudProvider.BuildIntegrationConfig(ctx, domainAccount, storedServices)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewAgentCheckInResponse(
|
||||
req.ProviderAccountID,
|
||||
account.ID.StringValue(),
|
||||
integrationConfig,
|
||||
account.RemovedAt,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (module *module) UpdateAccount(ctx context.Context, account *cloudintegrationtypes.Account) error {
|
||||
_, err := module.licensing.GetActive(ctx, account.OrgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
storableAccount, err := cloudintegrationtypes.NewStorableCloudIntegration(account)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.UpdateAccount(ctx, storableAccount)
|
||||
}
|
||||
|
||||
func (module *module) DisconnectAccount(ctx context.Context, orgID valuer.UUID, accountID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return module.store.RemoveAccount(ctx, orgID, accountID, provider)
|
||||
}
|
||||
|
||||
func (module *module) ListServicesMetadata(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType, integrationID valuer.UUID) ([]*cloudintegrationtypes.ServiceMetadata, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceDefinitions, err := cloudProvider.ListServiceDefinitions(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
enabledServiceIDs := map[string]bool{}
|
||||
if !integrationID.IsZero() {
|
||||
storedServices, err := module.store.ListServices(ctx, integrationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, svc := range storedServices {
|
||||
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, svc.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if serviceConfig.IsServiceEnabled(provider) {
|
||||
enabledServiceIDs[svc.Type.StringValue()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp := make([]*cloudintegrationtypes.ServiceMetadata, 0, len(serviceDefinitions))
|
||||
for _, serviceDefinition := range serviceDefinitions {
|
||||
resp = append(resp, cloudintegrationtypes.NewServiceMetadata(*serviceDefinition, enabledServiceIDs[serviceDefinition.ID]))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (module *module) GetService(ctx context.Context, orgID valuer.UUID, serviceID cloudintegrationtypes.ServiceID, provider cloudintegrationtypes.CloudProviderType, cloudIntegrationID valuer.UUID) (*cloudintegrationtypes.Service, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, serviceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var integrationService *cloudintegrationtypes.CloudIntegrationService
|
||||
|
||||
if !cloudIntegrationID.IsZero() {
|
||||
storedService, err := module.store.GetServiceByServiceID(ctx, cloudIntegrationID, serviceID)
|
||||
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
if storedService != nil {
|
||||
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedService.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
integrationService = cloudintegrationtypes.NewCloudIntegrationServiceFromStorable(storedService, serviceConfig)
|
||||
}
|
||||
}
|
||||
|
||||
return cloudintegrationtypes.NewService(*serviceDefinition, integrationService), nil
|
||||
}
|
||||
|
||||
func (module *module) CreateService(ctx context.Context, orgID valuer.UUID, service *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, service.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configJSON, err := service.Config.ToJSON(provider, service.Type, &serviceDefinition.SupportedSignals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return module.store.CreateService(ctx, cloudintegrationtypes.NewStorableCloudIntegrationService(service, string(configJSON)))
|
||||
}
|
||||
|
||||
func (module *module) UpdateService(ctx context.Context, orgID valuer.UUID, integrationService *cloudintegrationtypes.CloudIntegrationService, provider cloudintegrationtypes.CloudProviderType) error {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serviceDefinition, err := cloudProvider.GetServiceDefinition(ctx, integrationService.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configJSON, err := integrationService.Config.ToJSON(provider, integrationService.Type, &serviceDefinition.SupportedSignals)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
storableService := cloudintegrationtypes.NewStorableCloudIntegrationService(integrationService, string(configJSON))
|
||||
|
||||
return module.store.UpdateService(ctx, storableService)
|
||||
}
|
||||
|
||||
func (module *module) GetDashboardByID(ctx context.Context, orgID valuer.UUID, id string) (*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
_, _, _, err = cloudintegrationtypes.ParseCloudIntegrationDashboardID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allDashboards, err := module.listDashboards(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, d := range allDashboards {
|
||||
if d.ID == id {
|
||||
return d, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.New(errors.TypeNotFound, cloudintegrationtypes.ErrCodeCloudIntegrationNotFound, "cloud integration dashboard not found")
|
||||
}
|
||||
|
||||
func (module *module) ListDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
_, err := module.licensing.GetActive(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, errors.New(errors.TypeLicenseUnavailable, errors.CodeLicenseUnavailable, "a valid license is not available").WithAdditional("this feature requires a valid license").WithAdditional(err.Error())
|
||||
}
|
||||
|
||||
return module.listDashboards(ctx, orgID)
|
||||
}
|
||||
|
||||
func (module *module) Collect(ctx context.Context, orgID valuer.UUID) (map[string]any, error) {
|
||||
stats := make(map[string]any)
|
||||
|
||||
// get connected accounts for AWS
|
||||
awsAccountsCount, err := module.store.CountConnectedAccounts(ctx, orgID, cloudintegrationtypes.CloudProviderTypeAWS)
|
||||
if err == nil {
|
||||
stats["cloudintegration.aws.connectedaccounts.count"] = awsAccountsCount
|
||||
}
|
||||
|
||||
// NOTE: not adding stats for services for now.
|
||||
|
||||
// TODO: add more cloud providers when supported
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (module *module) getCloudProvider(provider cloudintegrationtypes.CloudProviderType) (cloudintegration.CloudProviderModule, error) {
|
||||
if cloudProviderModule, ok := module.cloudProvidersMap[provider]; ok {
|
||||
return cloudProviderModule, nil
|
||||
}
|
||||
|
||||
return nil, errors.NewInvalidInputf(cloudintegrationtypes.ErrCodeCloudProviderInvalidInput, "invalid cloud provider: %s", provider.StringValue())
|
||||
}
|
||||
|
||||
func (module *module) getOrCreateIngestionKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
|
||||
keyName := cloudintegrationtypes.NewIngestionKeyName(provider)
|
||||
|
||||
result, err := module.gateway.SearchIngestionKeysByName(ctx, orgID, keyName, 1, 10)
|
||||
if err != nil {
|
||||
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't search ingestion keys")
|
||||
}
|
||||
|
||||
// ideally there should be only one key per cloud integration provider
|
||||
if len(result.Keys) > 0 {
|
||||
return result.Keys[0].Value, nil
|
||||
}
|
||||
|
||||
createdIngestionKey, err := module.gateway.CreateIngestionKey(ctx, orgID, keyName, []string{"integration"}, time.Time{})
|
||||
if err != nil {
|
||||
return "", errors.WrapInternalf(err, errors.CodeInternal, "couldn't create ingestion key")
|
||||
}
|
||||
|
||||
return createdIngestionKey.Value, nil
|
||||
}
|
||||
|
||||
func (module *module) getOrCreateAPIKey(ctx context.Context, orgID valuer.UUID, provider cloudintegrationtypes.CloudProviderType) (string, error) {
|
||||
domain := module.serviceAccount.Config().Email.Domain
|
||||
serviceAccount := serviceaccounttypes.NewServiceAccount("integration", domain, serviceaccounttypes.ServiceAccountStatusActive, orgID)
|
||||
serviceAccount, err := module.serviceAccount.GetOrCreate(ctx, orgID, serviceAccount)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = module.serviceAccount.SetRoleByName(ctx, orgID, serviceAccount.ID, authtypes.SigNozViewerRoleName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
factorAPIKey, err := serviceAccount.NewFactorAPIKey(provider.StringValue(), 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
factorAPIKey, err = module.serviceAccount.GetOrCreateFactorAPIKey(ctx, factorAPIKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return factorAPIKey.Key, nil
|
||||
}
|
||||
|
||||
func (module *module) listDashboards(ctx context.Context, orgID valuer.UUID) ([]*dashboardtypes.Dashboard, error) {
|
||||
var allDashboards []*dashboardtypes.Dashboard
|
||||
|
||||
for provider := range module.cloudProvidersMap {
|
||||
cloudProvider, err := module.getCloudProvider(provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connectedAccounts, err := module.store.ListConnectedAccounts(ctx, orgID, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, storableAccount := range connectedAccounts {
|
||||
storedServices, err := module.store.ListServices(ctx, storableAccount.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, storedSvc := range storedServices {
|
||||
serviceConfig, err := cloudintegrationtypes.NewServiceConfigFromJSON(provider, storedSvc.Config)
|
||||
if err != nil || !serviceConfig.IsMetricsEnabled(provider) {
|
||||
continue
|
||||
}
|
||||
|
||||
svcDef, err := cloudProvider.GetServiceDefinition(ctx, storedSvc.Type)
|
||||
if err != nil || svcDef == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
dashboards := cloudintegrationtypes.GetDashboardsFromAssets(
|
||||
storedSvc.Type.StringValue(),
|
||||
orgID,
|
||||
provider,
|
||||
storableAccount.CreatedAt,
|
||||
svcDef.Assets,
|
||||
)
|
||||
allDashboards = append(allDashboards, dashboards...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(allDashboards, func(i, j int) bool {
|
||||
return allDashboards[i].ID < allDashboards[j].ID
|
||||
})
|
||||
|
||||
return allDashboards, nil
|
||||
}
|
||||
@@ -7,10 +7,6 @@ import (
|
||||
|
||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||
"github.com/SigNoz/signoz/pkg/flagger"
|
||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||
"github.com/SigNoz/signoz/pkg/valuer"
|
||||
)
|
||||
|
||||
type DayWiseBreakdown struct {
|
||||
@@ -49,17 +45,15 @@ type details struct {
|
||||
BillTotal float64 `json:"billTotal"`
|
||||
}
|
||||
|
||||
type billingData struct {
|
||||
BillingPeriodStart int64 `json:"billingPeriodStart"`
|
||||
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
|
||||
Details details `json:"details"`
|
||||
Discount float64 `json:"discount"`
|
||||
SubscriptionStatus string `json:"subscriptionStatus"`
|
||||
}
|
||||
|
||||
type billingDetails struct {
|
||||
Status string `json:"status"`
|
||||
Data billingData `json:"data"`
|
||||
Status string `json:"status"`
|
||||
Data struct {
|
||||
BillingPeriodStart int64 `json:"billingPeriodStart"`
|
||||
BillingPeriodEnd int64 `json:"billingPeriodEnd"`
|
||||
Details details `json:"details"`
|
||||
Discount float64 `json:"discount"`
|
||||
SubscriptionStatus string `json:"subscriptionStatus"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -70,33 +64,6 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
orgID := valuer.MustNewUUID(claims.OrgID)
|
||||
evalCtx := featuretypes.NewFlaggerEvaluationContext(orgID)
|
||||
useZeus := ah.Signoz.Flagger.BooleanOrEmpty(r.Context(), flagger.FeatureGetMetersFromZeus, evalCtx)
|
||||
|
||||
if useZeus {
|
||||
data, err := ah.Signoz.Zeus.GetMeters(r.Context(), licenseKey)
|
||||
if err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var billing billingData
|
||||
if err := json.Unmarshal(data, &billing); err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
ah.Respond(w, billing)
|
||||
return
|
||||
}
|
||||
|
||||
billingURL := fmt.Sprintf("%s/usage?licenseKey=%s", constants.LicenseSignozIo, licenseKey)
|
||||
|
||||
hClient := &http.Client{}
|
||||
@@ -112,11 +79,13 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// decode response body
|
||||
var billingResponse billingDetails
|
||||
if err := json.NewDecoder(billingResp.Body).Decode(&billingResponse); err != nil {
|
||||
RespondError(w, model.InternalError(err), nil)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(srikanthccv):Fetch the current day usage and add it to the response
|
||||
ah.Respond(w, billingResponse.Data)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ func New(ctx context.Context, providerSettings factory.ProviderSettings, config
|
||||
|
||||
// Set the maximum number of open connections
|
||||
pgConfig.MaxConns = int32(config.Connection.MaxOpenConns)
|
||||
pgConfig.MaxConnLifetime = config.Connection.MaxConnLifetime
|
||||
|
||||
// Use pgxpool to create a connection pool
|
||||
pool, err := pgxpool.NewWithConfig(ctx, pgConfig)
|
||||
|
||||
@@ -109,21 +109,6 @@ func (provider *Provider) GetDeployment(ctx context.Context, key string) ([]byte
|
||||
return []byte(gjson.GetBytes(response, "data").String()), nil
|
||||
}
|
||||
|
||||
func (provider *Provider) GetMeters(ctx context.Context, key string) ([]byte, error) {
|
||||
response, err := provider.do(
|
||||
ctx,
|
||||
provider.config.URL.JoinPath("/v1/meters"),
|
||||
http.MethodGet,
|
||||
key,
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []byte(gjson.GetBytes(response, "data").String()), nil
|
||||
}
|
||||
|
||||
func (provider *Provider) PutMeters(ctx context.Context, key string, data []byte) error {
|
||||
_, err := provider.do(
|
||||
ctx,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
node_modules
|
||||
build
|
||||
eslint-rules/
|
||||
stylelint-rules/
|
||||
*.typegen.ts
|
||||
i18-generate-hash.js
|
||||
src/parser/TraceOperatorParser/**
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const path = require('path');
|
||||
const rulesDirPlugin = require('eslint-plugin-rulesdir');
|
||||
rulesDirPlugin.RULES_DIR = path.join(__dirname, 'eslint-rules');
|
||||
|
||||
/**
|
||||
* ESLint Configuration for SigNoz Frontend
|
||||
*/
|
||||
@@ -37,7 +32,6 @@ module.exports = {
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: [
|
||||
'rulesdir', // Local custom rules
|
||||
'react', // React-specific rules
|
||||
'@typescript-eslint', // TypeScript linting
|
||||
'simple-import-sort', // Auto-sort imports
|
||||
@@ -62,9 +56,6 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// Asset migration — base-path safety
|
||||
'rulesdir/no-unsupported-asset-pattern': 'error',
|
||||
|
||||
// Code quality rules
|
||||
'prefer-const': 'error', // Enforces const for variables never reassigned
|
||||
'no-var': 'error', // Disallows var, enforces let/const
|
||||
@@ -211,23 +202,6 @@ module.exports = {
|
||||
message:
|
||||
'Avoid calling .getState() directly. Export a standalone action from the store instead.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"CallExpression[callee.object.name='window'][callee.property.name='open']",
|
||||
message:
|
||||
'Do not call window.open() directly. ' +
|
||||
"Use openInNewTab() from 'utils/navigation' for internal SigNoz paths. " +
|
||||
"For intentional external URLs, use openExternalLink() from 'utils/navigation'. " +
|
||||
'For unavoidable direct calls, add // eslint-disable-next-line with a reason.',
|
||||
},
|
||||
{
|
||||
selector:
|
||||
"AssignmentExpression[left.object.name='window'][left.property.name='href']",
|
||||
message:
|
||||
'Do not assign window.location.href for internal navigation. ' +
|
||||
"Use history.push() or history.replace() from 'lib/history'. " +
|
||||
'For external redirects (SSO, logout URLs), add // eslint-disable-next-line with a reason.',
|
||||
},
|
||||
],
|
||||
},
|
||||
overrides: [
|
||||
@@ -280,14 +254,5 @@ module.exports = {
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
// navigation.ts and useSafeNavigate.ts are the canonical implementations that call
|
||||
// window.open after computing a base-path-aware href. They are the only places
|
||||
// allowed to call window.open directly.
|
||||
files: ['src/utils/navigation.ts', 'src/hooks/useSafeNavigate.ts'],
|
||||
rules: {
|
||||
'no-restricted-syntax': 'off',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: [path.join(__dirname, 'stylelint-rules/no-unsupported-asset-url.js')],
|
||||
customSyntax: 'postcss-scss',
|
||||
rules: {
|
||||
'local/no-unsupported-asset-url': true,
|
||||
},
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export default 'test-file-stub';
|
||||
@@ -1,268 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
hasAssetExtension,
|
||||
containsAssetExtension,
|
||||
extractUrlPath,
|
||||
isAbsolutePath,
|
||||
isPublicRelative,
|
||||
isRelativePublicDir,
|
||||
isValidAssetImport,
|
||||
isExternalUrl,
|
||||
} = require('./shared/asset-patterns');
|
||||
|
||||
const PUBLIC_DIR_SEGMENTS = ['/Icons/', '/Images/', '/Logos/', '/svgs/'];
|
||||
|
||||
function collectBinaryStringParts(node) {
|
||||
if (node.type === 'Literal' && typeof node.value === 'string')
|
||||
return [node.value];
|
||||
if (node.type === 'BinaryExpression' && node.operator === '+') {
|
||||
return [
|
||||
...collectBinaryStringParts(node.left),
|
||||
...collectBinaryStringParts(node.right),
|
||||
];
|
||||
}
|
||||
if (node.type === 'TemplateLiteral') {
|
||||
return node.quasis.map((q) => q.value.raw);
|
||||
}
|
||||
return [null];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description:
|
||||
'Disallow Vite-unsafe asset reference patterns that break runtime base-path deployments',
|
||||
category: 'Asset Migration',
|
||||
recommended: true,
|
||||
},
|
||||
schema: [],
|
||||
messages: {
|
||||
absoluteString:
|
||||
'Absolute asset path "{{ value }}" is not base-path-safe. ' +
|
||||
"Use an ES import instead: import fooUrl from '@/assets/...' and reference the variable.",
|
||||
templateLiteral:
|
||||
'Dynamic asset path with absolute prefix is not base-path-safe. ' +
|
||||
"Use new URL('./asset.svg', import.meta.url).href for dynamic asset paths.",
|
||||
absoluteImport:
|
||||
'Asset imported via absolute path is not supported. ' +
|
||||
"Use import fooUrl from '@/assets/...' instead.",
|
||||
publicImport:
|
||||
"Assets in public/ bypass Vite's module pipeline — their URLs are not base-path-aware and will break when the app is served from a sub-path (e.g. /app/). " +
|
||||
"Use an ES import instead: import fooUrl from '@/assets/...' so Vite injects the correct base path.",
|
||||
relativePublicString:
|
||||
'Relative public-dir path "{{ value }}" is not base-path-safe. ' +
|
||||
"Use an ES import instead: import fooUrl from '@/assets/...' and reference the variable.",
|
||||
invalidAssetImport:
|
||||
"Asset '{{ src }}' must be imported from src/assets/ using either '@/assets/...' " +
|
||||
'or a relative path into src/assets/.',
|
||||
},
|
||||
},
|
||||
|
||||
create(context) {
|
||||
return {
|
||||
Literal(node) {
|
||||
if (node.parent && node.parent.type === 'ImportDeclaration') {
|
||||
return;
|
||||
}
|
||||
const value = node.value;
|
||||
if (typeof value !== 'string') return;
|
||||
if (isExternalUrl(value)) return;
|
||||
|
||||
if (isAbsolutePath(value) && hasAssetExtension(value)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'absoluteString',
|
||||
data: { value },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRelativePublicDir(value) && hasAssetExtension(value)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'relativePublicString',
|
||||
data: { value },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const urlPath = extractUrlPath(value);
|
||||
if (urlPath && isExternalUrl(urlPath)) return;
|
||||
if (urlPath && isAbsolutePath(urlPath) && hasAssetExtension(urlPath)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'absoluteString',
|
||||
data: { value: urlPath },
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (urlPath && isRelativePublicDir(urlPath) && hasAssetExtension(urlPath)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'relativePublicString',
|
||||
data: { value: urlPath },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
TemplateLiteral(node) {
|
||||
const quasis = node.quasis;
|
||||
if (!quasis || quasis.length === 0) return;
|
||||
|
||||
const firstQuasi = quasis[0].value.raw;
|
||||
if (isExternalUrl(firstQuasi)) return;
|
||||
|
||||
const hasAssetExt = quasis.some((q) => containsAssetExtension(q.value.raw));
|
||||
|
||||
if (isAbsolutePath(firstQuasi) && hasAssetExt) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'templateLiteral',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRelativePublicDir(firstQuasi) && hasAssetExt) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'relativePublicString',
|
||||
data: { value: firstQuasi },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Expression-first template with known public-dir segment: `${base}/Icons/foo.svg`
|
||||
const hasPublicSegment = quasis.some((q) =>
|
||||
PUBLIC_DIR_SEGMENTS.some((seg) => q.value.raw.includes(seg)),
|
||||
);
|
||||
if (hasPublicSegment && hasAssetExt) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'templateLiteral',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (quasis.length === 1) {
|
||||
const urlPath = extractUrlPath(firstQuasi);
|
||||
if (urlPath && isExternalUrl(urlPath)) return;
|
||||
if (urlPath && isAbsolutePath(urlPath) && hasAssetExtension(urlPath)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'templateLiteral',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (
|
||||
urlPath &&
|
||||
isRelativePublicDir(urlPath) &&
|
||||
hasAssetExtension(urlPath)
|
||||
) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'relativePublicString',
|
||||
data: { value: urlPath },
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstQuasi.includes('url(') && hasAssetExt) {
|
||||
const urlMatch = firstQuasi.match(/^url\(\s*['"]?\//);
|
||||
if (urlMatch) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'templateLiteral',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// String concatenation: "/Icons/" + name + ".svg" or "Icons/" + name + ".svg"
|
||||
BinaryExpression(node) {
|
||||
if (node.operator !== '+') return;
|
||||
|
||||
const parts = collectBinaryStringParts(node);
|
||||
const prefixParts = [];
|
||||
for (const part of parts) {
|
||||
if (part === null) break;
|
||||
prefixParts.push(part);
|
||||
}
|
||||
const staticPrefix = prefixParts.join('');
|
||||
|
||||
if (isExternalUrl(staticPrefix)) return;
|
||||
|
||||
const hasExt = parts.some(
|
||||
(part) => part !== null && containsAssetExtension(part),
|
||||
);
|
||||
|
||||
if (isAbsolutePath(staticPrefix) && hasExt) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'templateLiteral',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (isRelativePublicDir(staticPrefix) && hasExt) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'relativePublicString',
|
||||
data: { value: staticPrefix },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
ImportDeclaration(node) {
|
||||
const src = node.source.value;
|
||||
if (typeof src !== 'string') return;
|
||||
if (!hasAssetExtension(src)) return;
|
||||
|
||||
if (isAbsolutePath(src)) {
|
||||
context.report({ node, messageId: 'absoluteImport' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPublicRelative(src)) {
|
||||
context.report({ node, messageId: 'publicImport' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidAssetImport(src)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'invalidAssetImport',
|
||||
data: { src },
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
ImportExpression(node) {
|
||||
const src = node.source;
|
||||
if (!src || src.type !== 'Literal' || typeof src.value !== 'string') return;
|
||||
const value = src.value;
|
||||
if (!hasAssetExtension(value)) return;
|
||||
|
||||
if (isAbsolutePath(value)) {
|
||||
context.report({ node, messageId: 'absoluteImport' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPublicRelative(value)) {
|
||||
context.report({ node, messageId: 'publicImport' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isValidAssetImport(value)) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'invalidAssetImport',
|
||||
data: { src: value },
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"type": "commonjs"
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const ASSET_EXTENSIONS = ['.svg', '.png', '.webp', '.jpg', '.jpeg', '.gif'];
|
||||
|
||||
/**
|
||||
* Returns true if the string ends with an asset extension.
|
||||
* e.g. "/Icons/foo.svg" → true, "/Icons/foo.svg.bak" → false
|
||||
*/
|
||||
function hasAssetExtension(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return ASSET_EXTENSIONS.some((ext) => str.endsWith(ext));
|
||||
}
|
||||
|
||||
// Like hasAssetExtension but also matches mid-string with boundary check,
|
||||
// e.g. "/foo.svg?v=1" → true, "/icons.svg-dir/" → true (- is non-alphanumeric boundary)
|
||||
function containsAssetExtension(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return ASSET_EXTENSIONS.some((ext) => {
|
||||
const idx = str.indexOf(ext);
|
||||
if (idx === -1) return false;
|
||||
const afterIdx = idx + ext.length;
|
||||
// Broad boundary (any non-alphanumeric) is intentional — the real guard against
|
||||
// false positives is the upstream conditions (isAbsolutePath, isRelativePublicDir, etc.)
|
||||
// that must pass before this is reached. "/icons.svg-dir/" → true (- is a boundary).
|
||||
return afterIdx >= str.length || /[^a-zA-Z0-9]/.test(str[afterIdx]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the asset path from a CSS url() wrapper.
|
||||
* Handles single quotes, double quotes, unquoted, and whitespace variations.
|
||||
* e.g.
|
||||
* "url('/Icons/foo.svg')" → "/Icons/foo.svg"
|
||||
* "url( '../assets/bg.png' )" → "../assets/bg.png"
|
||||
* "url(/Icons/foo.svg)" → "/Icons/foo.svg"
|
||||
* Returns null if the string is not a url() wrapper.
|
||||
*/
|
||||
function extractUrlPath(str) {
|
||||
if (typeof str !== 'string') return null;
|
||||
// Match url( [whitespace] [quote?] path [quote?] [whitespace] )
|
||||
// Capture group: [^'")\s]+ matches path until quote, closing paren, or whitespace
|
||||
const match = str.match(/^url\(\s*['"]?([^'")\s]+)['"]?\s*\)$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string is an absolute path (starts with /).
|
||||
* Absolute paths in url() bypass <base href> and fail under any URL prefix.
|
||||
*/
|
||||
function isAbsolutePath(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return str.startsWith('/') && !str.startsWith('//');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the path imports from the public/ directory.
|
||||
* Relative imports into public/ cause asset duplication in dist/.
|
||||
*/
|
||||
function isPublicRelative(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return str.includes('/public/') || str.startsWith('public/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string is a relative reference into a known public-dir folder.
|
||||
* e.g. "Icons/foo.svg", `Logos/aws-dark.svg`, "Images/bg.png"
|
||||
* These bypass Vite's module pipeline even without a leading slash.
|
||||
*/
|
||||
const PUBLIC_DIR_SEGMENTS = ['Icons/', 'Images/', 'Logos/', 'svgs/'];
|
||||
|
||||
function isRelativePublicDir(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return PUBLIC_DIR_SEGMENTS.some((seg) => str.startsWith(seg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if an asset import path is valid (goes through Vite's module pipeline).
|
||||
* Valid: @/assets/..., any relative path containing /assets/, or node_modules packages.
|
||||
* Invalid: absolute paths, public/ dir, or relative paths outside src/assets/.
|
||||
*/
|
||||
function isValidAssetImport(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
if (str.startsWith('@/assets/')) return true;
|
||||
if (str.includes('/assets/')) return true;
|
||||
// Not starting with . or / means it's a node_modules package — always valid
|
||||
if (!str.startsWith('.') && !str.startsWith('/')) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the string is an external URL.
|
||||
* Used to avoid false positives on CDN/API URLs with asset extensions.
|
||||
*/
|
||||
function isExternalUrl(str) {
|
||||
if (typeof str !== 'string') return false;
|
||||
return (
|
||||
str.startsWith('http://') ||
|
||||
str.startsWith('https://') ||
|
||||
str.startsWith('//')
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
ASSET_EXTENSIONS,
|
||||
PUBLIC_DIR_SEGMENTS,
|
||||
hasAssetExtension,
|
||||
containsAssetExtension,
|
||||
extractUrlPath,
|
||||
isAbsolutePath,
|
||||
isPublicRelative,
|
||||
isRelativePublicDir,
|
||||
isValidAssetImport,
|
||||
isExternalUrl,
|
||||
};
|
||||
@@ -6,6 +6,3 @@ VITE_APPCUES_APP_ID="appcess-app-id"
|
||||
VITE_PYLON_IDENTITY_SECRET="pylon-identity-secret"
|
||||
|
||||
CI="1"
|
||||
|
||||
# Uncomment to test sub-path deployment locally (e.g. app served at /signoz/).
|
||||
# VITE_BASE_PATH="/signoz/"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="<%- BASE_PATH %>" />
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
http-equiv="Cache-Control"
|
||||
@@ -60,7 +59,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">
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
@@ -114,7 +113,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>
|
||||
|
||||
@@ -11,16 +11,12 @@ const config: Config.InitialOptions = {
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
|
||||
modulePathIgnorePatterns: ['dist'],
|
||||
moduleNameMapper: {
|
||||
'\\.(png|jpg|jpeg|gif|svg|webp|avif|ico|bmp|tiff)$':
|
||||
'<rootDir>/__mocks__/fileMock.ts',
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
|
||||
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
|
||||
'^@signozhq/resizable$': '<rootDir>/__mocks__/resizableMock.tsx',
|
||||
'^hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^src/hooks/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^hooks/useSafeNavigate\\.impl$': '<rootDir>/src/hooks/useSafeNavigate.ts',
|
||||
'^.*/useSafeNavigate$': USE_SAFE_NAVIGATE_MOCK_PATH,
|
||||
'^constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
'^src/constants/env$': '<rootDir>/__mocks__/env.ts',
|
||||
|
||||
@@ -10,10 +10,9 @@
|
||||
"preview": "vite preview",
|
||||
"prettify": "prettier --write .",
|
||||
"fmt": "prettier --check .",
|
||||
"lint": "eslint ./src && stylelint \"src/**/*.scss\"",
|
||||
"lint": "eslint ./src",
|
||||
"lint:generated": "eslint ./src/api/generated --fix",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"lint:styles": "stylelint \"src/**/*.scss\"",
|
||||
"jest": "jest",
|
||||
"jest:coverage": "jest --coverage",
|
||||
"jest:watch": "jest --watch",
|
||||
@@ -230,7 +229,6 @@
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-rulesdir": "0.2.2",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-sonarjs": "^0.12.0",
|
||||
"husky": "^7.0.4",
|
||||
@@ -246,7 +244,6 @@
|
||||
"orval": "7.18.0",
|
||||
"portfinder-sync": "^0.0.2",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-scss": "4.0.9",
|
||||
"prettier": "2.2.1",
|
||||
"prop-types": "15.8.1",
|
||||
"react-hooks-testing-library": "0.6.0",
|
||||
@@ -254,8 +251,6 @@
|
||||
"redux-mock-store": "1.5.4",
|
||||
"sass": "1.97.3",
|
||||
"sharp": "0.34.5",
|
||||
"stylelint": "17.7.0",
|
||||
"stylelint-scss": "7.0.0",
|
||||
"svgo": "4.0.0",
|
||||
"ts-api-utils": "2.4.0",
|
||||
"ts-jest": "29.4.6",
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 507 B |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 644 B After Width: | Height: | Size: 644 B |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 418 B After Width: | Height: | Size: 418 B |
|
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 604 B |
|
Before Width: | Height: | Size: 878 B After Width: | Height: | Size: 878 B |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 303 B After Width: | Height: | Size: 303 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 629 B After Width: | Height: | Size: 629 B |
|
Before Width: | Height: | Size: 439 B After Width: | Height: | Size: 439 B |
|
Before Width: | Height: | Size: 467 B After Width: | Height: | Size: 467 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 300 B After Width: | Height: | Size: 300 B |
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 348 B |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 910 B After Width: | Height: | Size: 910 B |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 408 KiB After Width: | Height: | Size: 408 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 695 B After Width: | Height: | Size: 695 B |
|
Before Width: | Height: | Size: 714 B After Width: | Height: | Size: 714 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 872 B |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 936 B After Width: | Height: | Size: 936 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |