mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 04:45:57 +08:00
feat(license): build license service (#7969)
* feat(license): base setup for license service * feat(license): delete old manager and import to new * feat(license): deal with features * feat(license): complete the license service in ee * feat(license): add sqlmigration for licenses * feat(license): remove feature flags * feat(license): refactor into provider pattern * feat(license): remove the ff lookup interface * feat(license): add logging to the validator functions * feat(license): implement features for OSS build * feat(license): fix the OSS build * feat(license): lets blast frontend * feat(license): fix the EE OSS build without license * feat(license): remove the hardcoded testing configs * feat(license): upgrade migration to 34 * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): better naming and structure * feat(license): integration tests * feat(license): integration tests * feat(license): refactor frontend * feat(license): make frontend api structure changes * feat(license): fix integration tests * feat(license): revert hardcoded configs * feat(license): fix integration tests * feat(license): address review comments * feat(license): address review comments * feat(license): address review comments * feat(license): address review comments * feat(license): update migration * feat(license): update migration * feat(license): update migration * feat(license): fixed logging * feat(license): use the unmarshaller for postable subscription * feat(license): correct the error message * feat(license): fix license test * feat(license): fix lint issues * feat(user): do not kill the service if upstream is down
This commit is contained in:
parent
7feb94e5eb
commit
b1c78c2f12
26
ee/licensing/config.go
Normal file
26
ee/licensing/config.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package licensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
config licensing.Config
|
||||||
|
once sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
// initializes the licensing configuration
|
||||||
|
func Config(pollInterval time.Duration, failureThreshold int) licensing.Config {
|
||||||
|
once.Do(func() {
|
||||||
|
config = licensing.Config{PollInterval: pollInterval, FailureThreshold: failureThreshold}
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
panic(fmt.Errorf("invalid licensing config: %w", err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return config
|
||||||
|
}
|
168
ee/licensing/httplicensing/api.go
Normal file
168
ee/licensing/httplicensing/api.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package httplicensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type licensingAPI struct {
|
||||||
|
licensing licensing.Licensing
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLicensingAPI(licensing licensing.Licensing) licensing.API {
|
||||||
|
return &licensingAPI{licensing: licensing}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *licensingAPI) Activate(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(licensetypes.PostableLicense)
|
||||||
|
err = json.NewDecoder(r.Body).Decode(&req)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.licensing.Activate(r.Context(), orgID, req.Key)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusAccepted, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *licensingAPI) GetActive(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
license, err := api.licensing.GetActive(r.Context(), orgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableLicense := licensetypes.NewGettableLicense(license.Data, license.Key)
|
||||||
|
render.Success(rw, http.StatusOK, gettableLicense)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *licensingAPI) Refresh(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.licensing.Refresh(r.Context(), orgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusNoContent, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *licensingAPI) Checkout(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(licensetypes.PostableSubscription)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableSubscription, err := api.licensing.Checkout(ctx, orgID, req)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusCreated, gettableSubscription)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *licensingAPI) Portal(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req := new(licensetypes.PostableSubscription)
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(req); err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableSubscription, err := api.licensing.Portal(ctx, orgID, req)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
render.Success(rw, http.StatusCreated, gettableSubscription)
|
||||||
|
}
|
285
ee/licensing/httplicensing/provider.go
Normal file
285
ee/licensing/httplicensing/provider.go
Normal file
@ -0,0 +1,285 @@
|
|||||||
|
package httplicensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/ee/licensing/licensingstore/sqllicensingstore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/SigNoz/signoz/pkg/zeus"
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provider struct {
|
||||||
|
store licensetypes.Store
|
||||||
|
zeus zeus.Zeus
|
||||||
|
config licensing.Config
|
||||||
|
settings factory.ScopedProviderSettings
|
||||||
|
stopChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProviderFactory(store sqlstore.SQLStore, zeus zeus.Zeus) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("http"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||||
|
return New(ctx, providerSettings, config, store, zeus)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ctx context.Context, ps factory.ProviderSettings, config licensing.Config, sqlstore sqlstore.SQLStore, zeus zeus.Zeus) (licensing.Licensing, error) {
|
||||||
|
settings := factory.NewScopedProviderSettings(ps, "github.com/SigNoz/signoz/ee/licensing/httplicensing")
|
||||||
|
licensestore := sqllicensingstore.New(sqlstore)
|
||||||
|
return &provider{store: licensestore, zeus: zeus, config: config, settings: settings, stopChan: make(chan struct{})}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Start(ctx context.Context) error {
|
||||||
|
tick := time.NewTicker(provider.config.PollInterval)
|
||||||
|
defer tick.Stop()
|
||||||
|
|
||||||
|
err := provider.Validate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "failed to validate license from upstream server", "error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-provider.stopChan:
|
||||||
|
return nil
|
||||||
|
case <-tick.C:
|
||||||
|
err := provider.Validate(ctx)
|
||||||
|
if err != nil {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "failed to validate license from upstream server", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Stop(ctx context.Context) error {
|
||||||
|
provider.settings.Logger().DebugContext(ctx, "license validation stopped")
|
||||||
|
close(provider.stopChan)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Validate(ctx context.Context) error {
|
||||||
|
organizations, err := provider.store.ListOrganizations(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, organizationID := range organizations {
|
||||||
|
err := provider.Refresh(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(organizations) == 0 {
|
||||||
|
provider.settings.Logger().DebugContext(ctx, "no organizations found, defaulting to basic plan")
|
||||||
|
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Activate(ctx context.Context, organizationID valuer.UUID, key string) error {
|
||||||
|
data, err := provider.zeus.GetLicense(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to fetch license data with upstream server")
|
||||||
|
}
|
||||||
|
|
||||||
|
license, err := licensetypes.NewLicense(data, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create license entity")
|
||||||
|
}
|
||||||
|
|
||||||
|
storableLicense := licensetypes.NewStorableLicenseFromLicense(license)
|
||||||
|
err = provider.store.Create(ctx, storableLicense)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = provider.InitFeatures(ctx, license.Features)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) GetActive(ctx context.Context, organizationID valuer.UUID) (*licensetypes.License, error) {
|
||||||
|
storableLicenses, err := provider.store.GetAll(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
activeLicense, err := licensetypes.GetActiveLicenseFromStorableLicenses(storableLicenses, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeLicense, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Refresh(ctx context.Context, organizationID valuer.UUID) error {
|
||||||
|
provider.settings.Logger().DebugContext(ctx, "license validation started for organizationID", "organizationID", organizationID.StringValue())
|
||||||
|
activeLicense, err := provider.GetActive(ctx, organizationID)
|
||||||
|
if err != nil && !errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "license validation failed", "organizationID", organizationID.StringValue())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && errors.Ast(err, errors.TypeNotFound) {
|
||||||
|
provider.settings.Logger().DebugContext(ctx, "no active license found, defaulting to basic plan", "organizationID", organizationID.StringValue())
|
||||||
|
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := provider.zeus.GetLicense(ctx, activeLicense.Key)
|
||||||
|
if err != nil {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "failed to validate the license with upstream server", "licenseID", activeLicense.Key, "organizationID", organizationID.StringValue())
|
||||||
|
|
||||||
|
if time.Since(activeLicense.LastValidatedAt) > time.Duration(provider.config.FailureThreshold)*provider.config.PollInterval {
|
||||||
|
provider.settings.Logger().ErrorContext(ctx, "license validation failed for consecutive poll intervals. defaulting to basic plan", "failureThreshold", provider.config.FailureThreshold, "licenseID", activeLicense.ID.StringValue(), "organizationID", organizationID.StringValue())
|
||||||
|
err = provider.InitFeatures(ctx, licensetypes.BasicPlan)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = activeLicense.Update(data)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to create license entity from license data")
|
||||||
|
}
|
||||||
|
|
||||||
|
provider.settings.Logger().DebugContext(ctx, "license validation completed successfully", "licenseID", activeLicense.ID, "organizationID", organizationID.StringValue())
|
||||||
|
updatedStorableLicense := licensetypes.NewStorableLicenseFromLicense(activeLicense)
|
||||||
|
err = provider.store.Update(ctx, organizationID, updatedStorableLicense)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Checkout(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
|
||||||
|
activeLicense, err := provider.GetActive(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(postableSubscription)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to marshal checkout payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := provider.zeus.GetCheckoutURL(ctx, activeLicense.Key, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to generate checkout session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &licensetypes.GettableSubscription{RedirectURL: gjson.GetBytes(response, "url").String()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
|
||||||
|
activeLicense, err := provider.GetActive(ctx, organizationID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(postableSubscription)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to marshal portal payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := provider.zeus.GetPortalURL(ctx, activeLicense.Key, body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to generate portal session")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &licensetypes.GettableSubscription{RedirectURL: gjson.GetBytes(response, "url").String()}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// feature surrogate
|
||||||
|
func (provider *provider) CheckFeature(ctx context.Context, key string) error {
|
||||||
|
feature, err := provider.store.GetFeature(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if feature.Active {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Newf(errors.TypeUnsupported, licensing.ErrCodeFeatureUnavailable, "feature unavailable: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error) {
|
||||||
|
featureStatus, err := provider.store.GetFeature(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &featuretypes.GettableFeature{
|
||||||
|
Name: featureStatus.Name,
|
||||||
|
Active: featureStatus.Active,
|
||||||
|
Usage: int64(featureStatus.Usage),
|
||||||
|
UsageLimit: int64(featureStatus.UsageLimit),
|
||||||
|
Route: featureStatus.Route,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
|
||||||
|
storableFeatures, err := provider.store.GetAllFeatures(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gettableFeatures := make([]*featuretypes.GettableFeature, len(storableFeatures))
|
||||||
|
for idx, gettableFeature := range storableFeatures {
|
||||||
|
gettableFeatures[idx] = &featuretypes.GettableFeature{
|
||||||
|
Name: gettableFeature.Name,
|
||||||
|
Active: gettableFeature.Active,
|
||||||
|
Usage: int64(gettableFeature.Usage),
|
||||||
|
UsageLimit: int64(gettableFeature.UsageLimit),
|
||||||
|
Route: gettableFeature.Route,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gettableFeatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error {
|
||||||
|
featureStatus := make([]*featuretypes.StorableFeature, len(features))
|
||||||
|
for i, f := range features {
|
||||||
|
featureStatus[i] = &featuretypes.StorableFeature{
|
||||||
|
Name: f.Name,
|
||||||
|
Active: f.Active,
|
||||||
|
Usage: int(f.Usage),
|
||||||
|
UsageLimit: int(f.UsageLimit),
|
||||||
|
Route: f.Route,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return provider.store.InitFeatures(ctx, featureStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *provider) UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error {
|
||||||
|
return provider.store.UpdateFeature(ctx, &featuretypes.StorableFeature{
|
||||||
|
Name: feature.Name,
|
||||||
|
Active: feature.Active,
|
||||||
|
Usage: int(feature.Usage),
|
||||||
|
UsageLimit: int(feature.UsageLimit),
|
||||||
|
Route: feature.Route,
|
||||||
|
})
|
||||||
|
}
|
186
ee/licensing/licensingstore/sqllicensingstore/store.go
Normal file
186
ee/licensing/licensingstore/sqllicensingstore/store.go
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
package sqllicensingstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type store struct {
|
||||||
|
sqlstore sqlstore.SQLStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(sqlstore sqlstore.SQLStore) licensetypes.Store {
|
||||||
|
return &store{sqlstore}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) Create(ctx context.Context, storableLicense *licensetypes.StorableLicense) error {
|
||||||
|
_, err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewInsert().
|
||||||
|
Model(storableLicense).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "license with ID: %s already exists", storableLicense.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) Get(ctx context.Context, organizationID valuer.UUID, licenseID valuer.UUID) (*licensetypes.StorableLicense, error) {
|
||||||
|
storableLicense := new(licensetypes.StorableLicense)
|
||||||
|
err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model(storableLicense).
|
||||||
|
Where("org_id = ?", organizationID).
|
||||||
|
Where("id = ?", licenseID).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "license with ID: %s does not exist", licenseID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storableLicense, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) GetAll(ctx context.Context, organizationID valuer.UUID) ([]*licensetypes.StorableLicense, error) {
|
||||||
|
storableLicenses := make([]*licensetypes.StorableLicense, 0)
|
||||||
|
err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model(&storableLicenses).
|
||||||
|
Where("org_id = ?", organizationID).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "licenses for organizationID: %s does not exists", organizationID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storableLicenses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) Update(ctx context.Context, organizationID valuer.UUID, storableLicense *licensetypes.StorableLicense) error {
|
||||||
|
_, err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewUpdate().
|
||||||
|
Model(storableLicense).
|
||||||
|
WherePK().
|
||||||
|
Where("org_id = ?", organizationID).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to update license with ID: %s", storableLicense.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) ListOrganizations(ctx context.Context) ([]valuer.UUID, error) {
|
||||||
|
orgIDStrs := make([]string, 0)
|
||||||
|
err := store.sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model(new(types.Organization)).
|
||||||
|
Column("id").
|
||||||
|
Scan(ctx, &orgIDStrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
orgIDs := make([]valuer.UUID, len(orgIDStrs))
|
||||||
|
for idx, orgIDStr := range orgIDStrs {
|
||||||
|
orgID, err := valuer.NewUUID(orgIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
orgIDs[idx] = orgID
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgIDs, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) CreateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
||||||
|
_, err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewInsert().
|
||||||
|
Model(storableFeature).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return store.sqlstore.WrapAlreadyExistsErrf(err, errors.CodeAlreadyExists, "feature with name:%s already exists", storableFeature.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) GetFeature(ctx context.Context, key string) (*featuretypes.StorableFeature, error) {
|
||||||
|
storableFeature := new(featuretypes.StorableFeature)
|
||||||
|
err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model(storableFeature).
|
||||||
|
Where("name = ?", key).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "feature with name:%s does not exist", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return storableFeature, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) GetAllFeatures(ctx context.Context) ([]*featuretypes.StorableFeature, error) {
|
||||||
|
storableFeatures := make([]*featuretypes.StorableFeature, 0)
|
||||||
|
err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model(&storableFeatures).
|
||||||
|
Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, store.sqlstore.WrapNotFoundErrf(err, errors.CodeNotFound, "features do not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
return storableFeatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) InitFeatures(ctx context.Context, storableFeatures []*featuretypes.StorableFeature) error {
|
||||||
|
_, err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewInsert().
|
||||||
|
Model(&storableFeatures).
|
||||||
|
On("CONFLICT (name) DO UPDATE").
|
||||||
|
Set("active = EXCLUDED.active").
|
||||||
|
Set("usage = EXCLUDED.usage").
|
||||||
|
Set("usage_limit = EXCLUDED.usage_limit").
|
||||||
|
Set("route = EXCLUDED.route").
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to initialise features")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (store *store) UpdateFeature(ctx context.Context, storableFeature *featuretypes.StorableFeature) error {
|
||||||
|
_, err := store.
|
||||||
|
sqlstore.
|
||||||
|
BunDB().
|
||||||
|
NewUpdate().
|
||||||
|
Model(storableFeature).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "unable to update feature with key: %s", storableFeature.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -6,29 +6,29 @@ import (
|
|||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/interfaces"
|
"github.com/SigNoz/signoz/ee/query-service/interfaces"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/license"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||||
"github.com/SigNoz/signoz/pkg/apis/fields"
|
"github.com/SigNoz/signoz/pkg/apis/fields"
|
||||||
"github.com/SigNoz/signoz/pkg/errors"
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||||
quickfilterscore "github.com/SigNoz/signoz/pkg/modules/quickfilter/core"
|
quickfilterscore "github.com/SigNoz/signoz/pkg/modules/quickfilter/core"
|
||||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
|
||||||
baseint "github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||||
"github.com/SigNoz/signoz/pkg/signoz"
|
"github.com/SigNoz/signoz/pkg/signoz"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
"github.com/SigNoz/signoz/pkg/version"
|
"github.com/SigNoz/signoz/pkg/version"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@ -40,8 +40,6 @@ type APIHandlerOptions struct {
|
|||||||
AppDao dao.ModelDao
|
AppDao dao.ModelDao
|
||||||
RulesManager *rules.Manager
|
RulesManager *rules.Manager
|
||||||
UsageManager *usage.Manager
|
UsageManager *usage.Manager
|
||||||
FeatureFlags baseint.FeatureLookup
|
|
||||||
LicenseManager *license.Manager
|
|
||||||
IntegrationsController *integrations.Controller
|
IntegrationsController *integrations.Controller
|
||||||
CloudIntegrationsController *cloudintegrations.Controller
|
CloudIntegrationsController *cloudintegrations.Controller
|
||||||
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
|
||||||
@ -67,12 +65,12 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
|||||||
Reader: opts.DataConnector,
|
Reader: opts.DataConnector,
|
||||||
PreferSpanMetrics: opts.PreferSpanMetrics,
|
PreferSpanMetrics: opts.PreferSpanMetrics,
|
||||||
RuleManager: opts.RulesManager,
|
RuleManager: opts.RulesManager,
|
||||||
FeatureFlags: opts.FeatureFlags,
|
|
||||||
IntegrationsController: opts.IntegrationsController,
|
IntegrationsController: opts.IntegrationsController,
|
||||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
CloudIntegrationsController: opts.CloudIntegrationsController,
|
||||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||||
FluxInterval: opts.FluxInterval,
|
FluxInterval: opts.FluxInterval,
|
||||||
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
AlertmanagerAPI: alertmanager.NewAPI(signoz.Alertmanager),
|
||||||
|
LicensingAPI: httplicensing.NewLicensingAPI(signoz.Licensing),
|
||||||
FieldsAPI: fields.NewAPI(signoz.TelemetryStore),
|
FieldsAPI: fields.NewAPI(signoz.TelemetryStore),
|
||||||
Signoz: signoz,
|
Signoz: signoz,
|
||||||
QuickFilters: quickFilter,
|
QuickFilters: quickFilter,
|
||||||
@ -90,18 +88,10 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz) (*APIHandler,
|
|||||||
return ah, nil
|
return ah, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) FF() baseint.FeatureLookup {
|
|
||||||
return ah.opts.FeatureFlags
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) RM() *rules.Manager {
|
func (ah *APIHandler) RM() *rules.Manager {
|
||||||
return ah.opts.RulesManager
|
return ah.opts.RulesManager
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) LM() *license.Manager {
|
|
||||||
return ah.opts.LicenseManager
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) UM() *usage.Manager {
|
func (ah *APIHandler) UM() *usage.Manager {
|
||||||
return ah.opts.UsageManager
|
return ah.opts.UsageManager
|
||||||
}
|
}
|
||||||
@ -114,8 +104,8 @@ func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
|
|||||||
return ah.opts.Gateway
|
return ah.opts.Gateway
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) CheckFeature(f string) bool {
|
func (ah *APIHandler) CheckFeature(ctx context.Context, key string) bool {
|
||||||
err := ah.FF().CheckFeature(f)
|
err := ah.Signoz.Licensing.CheckFeature(ctx, key)
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,18 +141,17 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.UpdateAPIKey)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.UpdateAPIKey)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.RevokeAPIKey)).Methods(http.MethodDelete)
|
router.HandleFunc("/api/v1/pats/{id}", am.AdminAccess(ah.Signoz.Handlers.User.RevokeAPIKey)).Methods(http.MethodDelete)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.checkout)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/checkout", am.AdminAccess(ah.LicensingAPI.Checkout)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v1/billing", am.AdminAccess(ah.getBilling)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.portalSession)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v1/portal", am.AdminAccess(ah.LicensingAPI.Portal)).Methods(http.MethodPost)
|
||||||
|
|
||||||
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v1/dashboards/{uuid}/lock", am.EditAccess(ah.lockDashboard)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v1/dashboards/{uuid}/unlock", am.EditAccess(ah.unlockDashboard)).Methods(http.MethodPut)
|
||||||
|
|
||||||
// v3
|
// v3
|
||||||
router.HandleFunc("/api/v3/licenses", am.ViewAccess(ah.listLicensesV3)).Methods(http.MethodGet)
|
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Activate)).Methods(http.MethodPost)
|
||||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.applyLicenseV3)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.LicensingAPI.Refresh)).Methods(http.MethodPut)
|
||||||
router.HandleFunc("/api/v3/licenses", am.AdminAccess(ah.refreshLicensesV3)).Methods(http.MethodPut)
|
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(ah.LicensingAPI.GetActive)).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(ah.getActiveLicenseV3)).Methods(http.MethodGet)
|
|
||||||
|
|
||||||
// v4
|
// v4
|
||||||
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
router.HandleFunc("/api/v4/query_range", am.ViewAccess(ah.queryRangeV4)).Methods(http.MethodPost)
|
||||||
@ -175,19 +164,15 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO(nitya): remove this once we know how to get the FF's
|
// TODO(nitya): remove this once we know how to get the FF's
|
||||||
func (ah *APIHandler) updateRequestContext(w http.ResponseWriter, r *http.Request) (*http.Request, error) {
|
func (ah *APIHandler) updateRequestContext(_ http.ResponseWriter, r *http.Request) (*http.Request, error) {
|
||||||
ssoAvailable := true
|
ssoAvailable := true
|
||||||
err := ah.FF().CheckFeature(model.SSO)
|
err := ah.Signoz.Licensing.CheckFeature(r.Context(), licensetypes.SSO)
|
||||||
if err != nil {
|
if err != nil && errors.Asc(err, licensing.ErrCodeFeatureUnavailable) {
|
||||||
switch err.(type) {
|
|
||||||
case basemodel.ErrFeatureUnavailable:
|
|
||||||
// do nothing, just skip sso
|
|
||||||
ssoAvailable = false
|
ssoAvailable = false
|
||||||
default:
|
} else if err != nil {
|
||||||
zap.L().Error("feature check failed", zap.String("featureKey", model.SSO), zap.Error(err))
|
zap.L().Error("feature check failed", zap.String("featureKey", licensetypes.SSO), zap.Error(err))
|
||||||
return r, errors.New(errors.TypeInternal, errors.CodeInternal, "error checking SSO feature")
|
return r, errors.New(errors.TypeInternal, errors.CodeInternal, "error checking SSO feature")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
ctx := context.WithValue(r.Context(), types.SSOAvailable, ssoAvailable)
|
ctx := context.WithValue(r.Context(), types.SSOAvailable, ssoAvailable)
|
||||||
return r.WithContext(ctx), nil
|
return r.WithContext(ctx), nil
|
||||||
}
|
}
|
||||||
@ -199,7 +184,6 @@ func (ah *APIHandler) loginPrecheck(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ah.Signoz.Handlers.User.LoginPrecheck(w, r)
|
ah.Signoz.Handlers.User.LoginPrecheck(w, r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) acceptInvite(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) acceptInvite(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -209,7 +193,6 @@ func (ah *APIHandler) acceptInvite(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ah.Signoz.Handlers.User.AcceptInvite(w, r)
|
ah.Signoz.Handlers.User.AcceptInvite(w, r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
||||||
@ -219,7 +202,7 @@ func (ah *APIHandler) getInvite(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ah.Signoz.Handlers.User.GetInvite(w, r)
|
ah.Signoz.Handlers.User.GetInvite(w, r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
func (ah *APIHandler) RegisterCloudIntegrationsRoutes(router *mux.Router, am *middleware.AuthZ) {
|
||||||
|
@ -12,8 +12,8 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseRequest(r *http.Request, req interface{}) error {
|
func parseRequest(r *http.Request, req interface{}) error {
|
||||||
@ -35,7 +35,6 @@ func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
ah.Signoz.Handlers.User.Login(w, r)
|
ah.Signoz.Handlers.User.Login(w, r)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string) {
|
func handleSsoError(w http.ResponseWriter, r *http.Request, redirectURL string) {
|
||||||
@ -52,7 +51,7 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
|
|||||||
redirectUri := constants.GetDefaultSiteURL()
|
redirectUri := constants.GetDefaultSiteURL()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if !ah.CheckFeature(model.SSO) {
|
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
||||||
zap.L().Error("[receiveGoogleAuth] sso requested but feature unavailable in org domain")
|
zap.L().Error("[receiveGoogleAuth] sso requested but feature unavailable in org domain")
|
||||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
@ -118,7 +117,7 @@ func (ah *APIHandler) receiveSAML(w http.ResponseWriter, r *http.Request) {
|
|||||||
redirectUri := constants.GetDefaultSiteURL()
|
redirectUri := constants.GetDefaultSiteURL()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
if !ah.CheckFeature(model.SSO) {
|
if !ah.CheckFeature(r.Context(), licensetypes.SSO) {
|
||||||
zap.L().Error("[receiveSAML] sso requested but feature unavailable in org domain")
|
zap.L().Error("[receiveSAML] sso requested but feature unavailable in org domain")
|
||||||
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
http.Redirect(w, r, fmt.Sprintf("%s?ssoerror=%s", redirectUri, "feature unavailable, please upgrade your billing plan to access this feature"), http.StatusMovedPermanently)
|
||||||
return
|
return
|
||||||
|
@ -36,6 +36,12 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cloudProvider := mux.Vars(r)["cloudProvider"]
|
cloudProvider := mux.Vars(r)["cloudProvider"]
|
||||||
if cloudProvider != "aws" {
|
if cloudProvider != "aws" {
|
||||||
RespondError(w, basemodel.BadRequest(fmt.Errorf(
|
RespondError(w, basemodel.BadRequest(fmt.Errorf(
|
||||||
@ -56,11 +62,9 @@ func (ah *APIHandler) CloudIntegrationsGenerateConnectionParams(w http.ResponseW
|
|||||||
SigNozAPIKey: apiKey,
|
SigNozAPIKey: apiKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
license, apiErr := ah.LM().GetRepo().GetActiveLicense(r.Context())
|
license, err := ah.Signoz.Licensing.GetActive(r.Context(), orgID)
|
||||||
if apiErr != nil {
|
if err != nil {
|
||||||
RespondError(w, basemodel.WrapApiError(
|
render.Error(w, err)
|
||||||
apiErr, "couldn't look for active license",
|
|
||||||
), nil)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,13 +9,29 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
pkgError "github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
featureSet, err := ah.FF().GetFeatureFlags()
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(w, pkgError.Newf(pkgError.TypeInvalidInput, pkgError.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
featureSet, err := ah.Signoz.Licensing.GetFeatureFlags(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ah.HandleError(w, err, http.StatusInternalServerError)
|
ah.HandleError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -23,7 +39,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if constants.FetchFeatures == "true" {
|
if constants.FetchFeatures == "true" {
|
||||||
zap.L().Debug("fetching license")
|
zap.L().Debug("fetching license")
|
||||||
license, err := ah.LM().GetRepo().GetActiveLicense(ctx)
|
license, err := ah.Signoz.Licensing.GetActive(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("failed to fetch license", zap.Error(err))
|
zap.L().Error("failed to fetch license", zap.Error(err))
|
||||||
} else if license == nil {
|
} else if license == nil {
|
||||||
@ -44,9 +60,8 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ah.opts.PreferSpanMetrics {
|
if ah.opts.PreferSpanMetrics {
|
||||||
for idx := range featureSet {
|
for idx, feature := range featureSet {
|
||||||
feature := &featureSet[idx]
|
if feature.Name == featuretypes.UseSpanMetrics {
|
||||||
if feature.Name == basemodel.UseSpanMetrics {
|
|
||||||
featureSet[idx].Active = true
|
featureSet[idx].Active = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,7 +72,7 @@ func (ah *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
// fetchZeusFeatures makes an HTTP GET request to the /zeusFeatures endpoint
|
||||||
// and returns the FeatureSet.
|
// and returns the FeatureSet.
|
||||||
func fetchZeusFeatures(url, licenseKey string) (basemodel.FeatureSet, error) {
|
func fetchZeusFeatures(url, licenseKey string) ([]*featuretypes.GettableFeature, error) {
|
||||||
// Check if the URL is empty
|
// Check if the URL is empty
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return nil, fmt.Errorf("url is empty")
|
return nil, fmt.Errorf("url is empty")
|
||||||
@ -117,13 +132,13 @@ func fetchZeusFeatures(url, licenseKey string) (basemodel.FeatureSet, error) {
|
|||||||
|
|
||||||
type ZeusFeaturesResponse struct {
|
type ZeusFeaturesResponse struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data basemodel.FeatureSet `json:"data"`
|
Data []*featuretypes.GettableFeature `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
// MergeFeatureSets merges two FeatureSet arrays with precedence to zeusFeatures.
|
||||||
func MergeFeatureSets(zeusFeatures, internalFeatures basemodel.FeatureSet) basemodel.FeatureSet {
|
func MergeFeatureSets(zeusFeatures, internalFeatures []*featuretypes.GettableFeature) []*featuretypes.GettableFeature {
|
||||||
// Create a map to store the merged features
|
// Create a map to store the merged features
|
||||||
featureMap := make(map[string]basemodel.Feature)
|
featureMap := make(map[string]*featuretypes.GettableFeature)
|
||||||
|
|
||||||
// Add all features from the otherFeatures set to the map
|
// Add all features from the otherFeatures set to the map
|
||||||
for _, feature := range internalFeatures {
|
for _, feature := range internalFeatures {
|
||||||
@ -137,7 +152,7 @@ func MergeFeatureSets(zeusFeatures, internalFeatures basemodel.FeatureSet) basem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the map back to a FeatureSet slice
|
// Convert the map back to a FeatureSet slice
|
||||||
var mergedFeatures basemodel.FeatureSet
|
var mergedFeatures []*featuretypes.GettableFeature
|
||||||
for _, feature := range featureMap {
|
for _, feature := range featureMap {
|
||||||
mergedFeatures = append(mergedFeatures, feature)
|
mergedFeatures = append(mergedFeatures, feature)
|
||||||
}
|
}
|
||||||
|
@ -3,58 +3,58 @@ package api
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMergeFeatureSets(t *testing.T) {
|
func TestMergeFeatureSets(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
zeusFeatures basemodel.FeatureSet
|
zeusFeatures []*featuretypes.GettableFeature
|
||||||
internalFeatures basemodel.FeatureSet
|
internalFeatures []*featuretypes.GettableFeature
|
||||||
expected basemodel.FeatureSet
|
expected []*featuretypes.GettableFeature
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "empty zeusFeatures and internalFeatures",
|
name: "empty zeusFeatures and internalFeatures",
|
||||||
zeusFeatures: basemodel.FeatureSet{},
|
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||||
internalFeatures: basemodel.FeatureSet{},
|
internalFeatures: []*featuretypes.GettableFeature{},
|
||||||
expected: basemodel.FeatureSet{},
|
expected: []*featuretypes.GettableFeature{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and empty internalFeatures",
|
name: "non-empty zeusFeatures and empty internalFeatures",
|
||||||
zeusFeatures: basemodel.FeatureSet{
|
zeusFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: basemodel.FeatureSet{},
|
internalFeatures: []*featuretypes.GettableFeature{},
|
||||||
expected: basemodel.FeatureSet{
|
expected: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "empty zeusFeatures and non-empty internalFeatures",
|
name: "empty zeusFeatures and non-empty internalFeatures",
|
||||||
zeusFeatures: basemodel.FeatureSet{},
|
zeusFeatures: []*featuretypes.GettableFeature{},
|
||||||
internalFeatures: basemodel.FeatureSet{
|
internalFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
},
|
},
|
||||||
expected: basemodel.FeatureSet{
|
expected: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
name: "non-empty zeusFeatures and non-empty internalFeatures with no conflicts",
|
||||||
zeusFeatures: basemodel.FeatureSet{
|
zeusFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature3", Active: false},
|
{Name: "Feature3", Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: basemodel.FeatureSet{
|
internalFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature2", Active: true},
|
{Name: "Feature2", Active: true},
|
||||||
{Name: "Feature4", Active: false},
|
{Name: "Feature4", Active: false},
|
||||||
},
|
},
|
||||||
expected: basemodel.FeatureSet{
|
expected: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: true},
|
{Name: "Feature2", Active: true},
|
||||||
{Name: "Feature3", Active: false},
|
{Name: "Feature3", Active: false},
|
||||||
@ -63,15 +63,15 @@ func TestMergeFeatureSets(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
name: "non-empty zeusFeatures and non-empty internalFeatures with conflicts",
|
||||||
zeusFeatures: basemodel.FeatureSet{
|
zeusFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
},
|
},
|
||||||
internalFeatures: basemodel.FeatureSet{
|
internalFeatures: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: false},
|
{Name: "Feature1", Active: false},
|
||||||
{Name: "Feature3", Active: true},
|
{Name: "Feature3", Active: true},
|
||||||
},
|
},
|
||||||
expected: basemodel.FeatureSet{
|
expected: []*featuretypes.GettableFeature{
|
||||||
{Name: "Feature1", Active: true},
|
{Name: "Feature1", Active: true},
|
||||||
{Name: "Feature2", Active: false},
|
{Name: "Feature2", Active: false},
|
||||||
{Name: "Feature3", Active: true},
|
{Name: "Feature3", Active: true},
|
||||||
|
@ -5,10 +5,26 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
orgID, err := valuer.NewUUID(claims.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
render.Error(rw, errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "orgId is invalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
validPath := false
|
validPath := false
|
||||||
for _, allowedPrefix := range gateway.AllowedPrefix {
|
for _, allowedPrefix := range gateway.AllowedPrefix {
|
||||||
if strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+allowedPrefix) {
|
if strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+allowedPrefix) {
|
||||||
@ -22,9 +38,9 @@ func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
license, err := ah.LM().GetRepo().GetActiveLicense(ctx)
|
license, err := ah.Signoz.Licensing.GetActive(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
RespondError(rw, err, nil)
|
render.Error(rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/constants"
|
"github.com/SigNoz/signoz/ee/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DayWiseBreakdown struct {
|
type DayWiseBreakdown struct {
|
||||||
@ -49,10 +45,6 @@ type details struct {
|
|||||||
BillTotal float64 `json:"billTotal"`
|
BillTotal float64 `json:"billTotal"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Redirect struct {
|
|
||||||
RedirectURL string `json:"redirectURL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type billingDetails struct {
|
type billingDetails struct {
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Data struct {
|
Data struct {
|
||||||
@ -64,97 +56,6 @@ type billingDetails struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ApplyLicenseRequest struct {
|
|
||||||
LicenseKey string `json:"key"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) listLicensesV3(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ah.listLicensesV2(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) getActiveLicenseV3(w http.ResponseWriter, r *http.Request) {
|
|
||||||
activeLicense, err := ah.LM().GetRepo().GetActiveLicenseV3(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorInternal, Err: err}, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// return 404 not found if there is no active license
|
|
||||||
if activeLicense == nil {
|
|
||||||
RespondError(w, &model.ApiError{Typ: model.ErrorNotFound, Err: fmt.Errorf("no active license found")}, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO deprecate this when we move away from key for stripe
|
|
||||||
activeLicense.Data["key"] = activeLicense.Key
|
|
||||||
render.Success(w, http.StatusOK, activeLicense.Data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function is called by zeus when inserting licenses in the query-service
|
|
||||||
func (ah *APIHandler) applyLicenseV3(w http.ResponseWriter, r *http.Request) {
|
|
||||||
claims, err := authtypes.ClaimsFromContext(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
render.Error(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var licenseKey ApplyLicenseRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&licenseKey); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if licenseKey.LicenseKey == "" {
|
|
||||||
RespondError(w, model.BadRequest(fmt.Errorf("license key is required")), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ah.LM().ActivateV3(r.Context(), licenseKey.LicenseKey)
|
|
||||||
if err != nil {
|
|
||||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_ACT_FAILED, map[string]interface{}{"err": err.Error()}, claims.Email, true, false)
|
|
||||||
render.Error(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Success(w, http.StatusAccepted, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) refreshLicensesV3(w http.ResponseWriter, r *http.Request) {
|
|
||||||
err := ah.LM().RefreshLicense(r.Context())
|
|
||||||
if err != nil {
|
|
||||||
render.Error(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
render.Success(w, http.StatusNoContent, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCheckoutPortalResponse(redirectURL string) *Redirect {
|
|
||||||
return &Redirect{RedirectURL: redirectURL}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) checkout(w http.ResponseWriter, r *http.Request) {
|
|
||||||
checkoutRequest := &model.CheckoutRequest{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(checkoutRequest); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
license := ah.LM().GetActiveLicense()
|
|
||||||
if license == nil {
|
|
||||||
RespondError(w, model.BadRequestStr("cannot proceed with checkout without license key"), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl, err := signozio.CheckoutSession(r.Context(), checkoutRequest, license.Key, ah.Signoz.Zeus)
|
|
||||||
if err != nil {
|
|
||||||
render.Error(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ah.Respond(w, getCheckoutPortalResponse(redirectUrl))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
||||||
licenseKey := r.URL.Query().Get("licenseKey")
|
licenseKey := r.URL.Query().Get("licenseKey")
|
||||||
|
|
||||||
@ -188,71 +89,3 @@ func (ah *APIHandler) getBilling(w http.ResponseWriter, r *http.Request) {
|
|||||||
// TODO(srikanthccv):Fetch the current day usage and add it to the response
|
// TODO(srikanthccv):Fetch the current day usage and add it to the response
|
||||||
ah.Respond(w, billingResponse.Data)
|
ah.Respond(w, billingResponse.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertLicenseV3ToLicenseV2(licenses []*model.LicenseV3) []model.License {
|
|
||||||
licensesV2 := []model.License{}
|
|
||||||
for _, l := range licenses {
|
|
||||||
planKeyFromPlanName, ok := model.MapOldPlanKeyToNewPlanName[l.PlanName]
|
|
||||||
if !ok {
|
|
||||||
planKeyFromPlanName = model.Basic
|
|
||||||
}
|
|
||||||
licenseV2 := model.License{
|
|
||||||
Key: l.Key,
|
|
||||||
ActivationId: "",
|
|
||||||
PlanDetails: "",
|
|
||||||
FeatureSet: l.Features,
|
|
||||||
ValidationMessage: "",
|
|
||||||
IsCurrent: l.IsCurrent,
|
|
||||||
LicensePlan: model.LicensePlan{
|
|
||||||
PlanKey: planKeyFromPlanName,
|
|
||||||
ValidFrom: l.ValidFrom,
|
|
||||||
ValidUntil: l.ValidUntil,
|
|
||||||
Status: l.Status},
|
|
||||||
}
|
|
||||||
licensesV2 = append(licensesV2, licenseV2)
|
|
||||||
}
|
|
||||||
return licensesV2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) listLicensesV2(w http.ResponseWriter, r *http.Request) {
|
|
||||||
licensesV3, apierr := ah.LM().GetLicensesV3(r.Context())
|
|
||||||
if apierr != nil {
|
|
||||||
RespondError(w, apierr, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
licenses := convertLicenseV3ToLicenseV2(licensesV3)
|
|
||||||
|
|
||||||
resp := model.Licenses{
|
|
||||||
TrialStart: -1,
|
|
||||||
TrialEnd: -1,
|
|
||||||
OnTrial: false,
|
|
||||||
WorkSpaceBlock: false,
|
|
||||||
TrialConvertedToSubscription: false,
|
|
||||||
GracePeriodEnd: -1,
|
|
||||||
Licenses: licenses,
|
|
||||||
}
|
|
||||||
|
|
||||||
ah.Respond(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ah *APIHandler) portalSession(w http.ResponseWriter, r *http.Request) {
|
|
||||||
portalRequest := &model.PortalRequest{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(portalRequest); err != nil {
|
|
||||||
RespondError(w, model.BadRequest(err), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
license := ah.LM().GetActiveLicense()
|
|
||||||
if license == nil {
|
|
||||||
RespondError(w, model.BadRequestStr("cannot request the portal session without license key"), nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectUrl, err := signozio.PortalSession(r.Context(), portalRequest, license.Key, ah.Signoz.Zeus)
|
|
||||||
if err != nil {
|
|
||||||
render.Error(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ah.Respond(w, getCheckoutPortalResponse(redirectUrl))
|
|
||||||
}
|
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/ee/query-service/dao/sqlite"
|
"github.com/SigNoz/signoz/ee/query-service/dao/sqlite"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
"github.com/SigNoz/signoz/ee/query-service/integrations/gateway"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/rules"
|
"github.com/SigNoz/signoz/ee/query-service/rules"
|
||||||
|
"github.com/SigNoz/signoz/ee/query-service/usage"
|
||||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||||
"github.com/SigNoz/signoz/pkg/cache"
|
"github.com/SigNoz/signoz/pkg/cache"
|
||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
@ -30,9 +31,6 @@ import (
|
|||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
"github.com/soheilhy/cmux"
|
"github.com/soheilhy/cmux"
|
||||||
|
|
||||||
licensepkg "github.com/SigNoz/signoz/ee/query-service/license"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/usage"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
"github.com/SigNoz/signoz/pkg/query-service/agentConf"
|
||||||
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
baseapp "github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
@ -96,12 +94,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// initiate license manager
|
|
||||||
lm, err := licensepkg.StartManager(serverOptions.SigNoz.SQLStore.SQLxDB(), serverOptions.SigNoz.SQLStore, serverOptions.SigNoz.Zeus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -168,11 +160,11 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// start the usagemanager
|
// start the usagemanager
|
||||||
usageManager, err := usage.New(modelDao, lm.GetRepo(), serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus)
|
usageManager, err := usage.New(modelDao, serverOptions.SigNoz.Licensing, serverOptions.SigNoz.TelemetryStore.ClickhouseDB(), serverOptions.SigNoz.Zeus, serverOptions.SigNoz.Modules.Organization)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
err = usageManager.Start()
|
err = usageManager.Start(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -197,8 +189,6 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
AppDao: modelDao,
|
AppDao: modelDao,
|
||||||
RulesManager: rm,
|
RulesManager: rm,
|
||||||
UsageManager: usageManager,
|
UsageManager: usageManager,
|
||||||
FeatureFlags: lm,
|
|
||||||
LicenseManager: lm,
|
|
||||||
IntegrationsController: integrationsController,
|
IntegrationsController: integrationsController,
|
||||||
CloudIntegrationsController: cloudIntegrationsController,
|
CloudIntegrationsController: cloudIntegrationsController,
|
||||||
LogsParsingPipelineController: logParsingPipelineController,
|
LogsParsingPipelineController: logParsingPipelineController,
|
||||||
@ -431,15 +421,15 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Stop() error {
|
func (s *Server) Stop(ctx context.Context) error {
|
||||||
if s.httpServer != nil {
|
if s.httpServer != nil {
|
||||||
if err := s.httpServer.Shutdown(context.Background()); err != nil {
|
if err := s.httpServer.Shutdown(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.privateHTTP != nil {
|
if s.privateHTTP != nil {
|
||||||
if err := s.privateHTTP.Shutdown(context.Background()); err != nil {
|
if err := s.privateHTTP.Shutdown(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -447,11 +437,11 @@ func (s *Server) Stop() error {
|
|||||||
s.opampServer.Stop()
|
s.opampServer.Stop()
|
||||||
|
|
||||||
if s.ruleManager != nil {
|
if s.ruleManager != nil {
|
||||||
s.ruleManager.Stop(context.Background())
|
s.ruleManager.Stop(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop usage manager
|
// stop usage manager
|
||||||
s.usageManager.Stop()
|
s.usageManager.Stop(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
package signozio
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/pkg/zeus"
|
|
||||||
"github.com/tidwall/gjson"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ValidateLicenseV3(ctx context.Context, licenseKey string, zeus zeus.Zeus) (*model.LicenseV3, error) {
|
|
||||||
data, err := zeus.GetLicense(ctx, licenseKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var m map[string]any
|
|
||||||
if err = json.Unmarshal(data, &m); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
license, err := model.NewLicenseV3(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return license, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendUsage reports the usage of signoz to license server
|
|
||||||
func SendUsage(ctx context.Context, usage model.UsagePayload, zeus zeus.Zeus) error {
|
|
||||||
body, err := json.Marshal(usage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return zeus.PutMeters(ctx, usage.LicenseKey.String(), body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckoutSession(ctx context.Context, checkoutRequest *model.CheckoutRequest, licenseKey string, zeus zeus.Zeus) (string, error) {
|
|
||||||
body, err := json.Marshal(checkoutRequest)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := zeus.GetCheckoutURL(ctx, licenseKey, body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gjson.GetBytes(response, "url").String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func PortalSession(ctx context.Context, portalRequest *model.PortalRequest, licenseKey string, zeus zeus.Zeus) (string, error) {
|
|
||||||
body, err := json.Marshal(portalRequest)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := zeus.GetPortalURL(ctx, licenseKey, body)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return gjson.GetBytes(response, "url").String(), nil
|
|
||||||
}
|
|
@ -1,248 +0,0 @@
|
|||||||
package license
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
"github.com/mattn/go-sqlite3"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Repo is license repo. stores license keys in a secured DB
|
|
||||||
type Repo struct {
|
|
||||||
db *sqlx.DB
|
|
||||||
store sqlstore.SQLStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewLicenseRepo initiates a new license repo
|
|
||||||
func NewLicenseRepo(db *sqlx.DB, store sqlstore.SQLStore) Repo {
|
|
||||||
return Repo{
|
|
||||||
db: db,
|
|
||||||
store: store,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
|
||||||
licensesData := []model.LicenseDB{}
|
|
||||||
licenseV3Data := []*model.LicenseV3{}
|
|
||||||
|
|
||||||
query := "SELECT id,key,data FROM licenses_v3"
|
|
||||||
|
|
||||||
err := r.db.Select(&licensesData, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get licenses from db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range licensesData {
|
|
||||||
var licenseData map[string]interface{}
|
|
||||||
err := json.Unmarshal([]byte(l.Data), &licenseData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
|
|
||||||
}
|
|
||||||
licenseV3Data = append(licenseV3Data, license)
|
|
||||||
}
|
|
||||||
|
|
||||||
return licenseV3Data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetActiveLicense fetches the latest active license from DB.
|
|
||||||
// If the license is not present, expect a nil license and a nil error in the output.
|
|
||||||
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
|
||||||
activeLicenseV3, err := r.GetActiveLicenseV3(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if activeLicenseV3 == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
activeLicenseV2 := model.ConvertLicenseV3ToLicenseV2(activeLicenseV3)
|
|
||||||
return activeLicenseV2, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error) {
|
|
||||||
var err error
|
|
||||||
licenses := []model.LicenseDB{}
|
|
||||||
|
|
||||||
query := "SELECT id,key,data FROM licenses_v3"
|
|
||||||
|
|
||||||
err = r.db.Select(&licenses, query)
|
|
||||||
if err != nil {
|
|
||||||
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
var active *model.LicenseV3
|
|
||||||
for _, l := range licenses {
|
|
||||||
var licenseData map[string]interface{}
|
|
||||||
err := json.Unmarshal([]byte(l.Data), &licenseData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to unmarshal data into licenseData : %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
license, err := model.NewLicenseV3WithIDAndKey(l.ID, l.Key, licenseData)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get licenses v3 schema : %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if active == nil &&
|
|
||||||
(license.ValidFrom != 0) &&
|
|
||||||
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
|
||||||
active = license
|
|
||||||
}
|
|
||||||
if active != nil &&
|
|
||||||
license.ValidFrom > active.ValidFrom &&
|
|
||||||
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
|
||||||
active = license
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return active, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// InsertLicenseV3 inserts a new license v3 in db
|
|
||||||
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {
|
|
||||||
|
|
||||||
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
|
|
||||||
|
|
||||||
// licsense is the entity of zeus so putting the entire license here without defining schema
|
|
||||||
licenseData, err := json.Marshal(l.Data)
|
|
||||||
if err != nil {
|
|
||||||
return &model.ApiError{Typ: basemodel.ErrorBadData, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = r.db.ExecContext(ctx,
|
|
||||||
query,
|
|
||||||
l.ID,
|
|
||||||
l.Key,
|
|
||||||
string(licenseData),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if sqliteErr, ok := err.(sqlite3.Error); ok {
|
|
||||||
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
|
|
||||||
zap.L().Error("error in inserting license data: ", zap.Error(sqliteErr))
|
|
||||||
return &model.ApiError{Typ: model.ErrorConflict, Err: sqliteErr}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
zap.L().Error("error in inserting license data: ", zap.Error(err))
|
|
||||||
return &model.ApiError{Typ: basemodel.ErrorExec, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateLicenseV3 updates a new license v3 in db
|
|
||||||
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
|
||||||
|
|
||||||
// the key and id for the license can't change so only update the data here!
|
|
||||||
query := `UPDATE licenses_v3 SET data=$1 WHERE id=$2;`
|
|
||||||
|
|
||||||
license, err := json.Marshal(l.Data)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("insert license failed: license marshal error")
|
|
||||||
}
|
|
||||||
_, err = r.db.ExecContext(ctx,
|
|
||||||
query,
|
|
||||||
license,
|
|
||||||
l.ID,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("error in updating license data: ", zap.Error(err))
|
|
||||||
return fmt.Errorf("failed to update license in db: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) CreateFeature(req *types.FeatureStatus) *basemodel.ApiError {
|
|
||||||
|
|
||||||
_, err := r.store.BunDB().NewInsert().
|
|
||||||
Model(req).
|
|
||||||
Exec(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return &basemodel.ApiError{Typ: basemodel.ErrorInternal, Err: err}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetFeature(featureName string) (types.FeatureStatus, error) {
|
|
||||||
var feature types.FeatureStatus
|
|
||||||
|
|
||||||
err := r.store.BunDB().NewSelect().
|
|
||||||
Model(&feature).
|
|
||||||
Where("name = ?", featureName).
|
|
||||||
Scan(context.Background())
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return feature, err
|
|
||||||
}
|
|
||||||
if feature.Name == "" {
|
|
||||||
return feature, basemodel.ErrFeatureUnavailable{Key: featureName}
|
|
||||||
}
|
|
||||||
return feature, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) GetAllFeatures() ([]basemodel.Feature, error) {
|
|
||||||
|
|
||||||
var feature []basemodel.Feature
|
|
||||||
|
|
||||||
err := r.db.Select(&feature,
|
|
||||||
`SELECT * FROM feature_status;`)
|
|
||||||
if err != nil {
|
|
||||||
return feature, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return feature, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) UpdateFeature(req types.FeatureStatus) error {
|
|
||||||
|
|
||||||
_, err := r.store.BunDB().NewUpdate().
|
|
||||||
Model(&req).
|
|
||||||
Where("name = ?", req.Name).
|
|
||||||
Exec(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Repo) InitFeatures(req []types.FeatureStatus) error {
|
|
||||||
// get a feature by name, if it doesn't exist, create it. If it does exist, update it.
|
|
||||||
for _, feature := range req {
|
|
||||||
currentFeature, err := r.GetFeature(feature.Name)
|
|
||||||
if err != nil && err == sql.ErrNoRows {
|
|
||||||
err := r.CreateFeature(&feature)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
feature.Usage = int(currentFeature.Usage)
|
|
||||||
if feature.Usage >= feature.UsageLimit && feature.UsageLimit != -1 {
|
|
||||||
feature.Active = false
|
|
||||||
}
|
|
||||||
err = r.UpdateFeature(feature)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,318 +0,0 @@
|
|||||||
package license
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
|
||||||
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
baseconstants "github.com/SigNoz/signoz/pkg/query-service/constants"
|
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
|
||||||
"github.com/SigNoz/signoz/pkg/zeus"
|
|
||||||
|
|
||||||
validate "github.com/SigNoz/signoz/ee/query-service/integrations/signozio"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/telemetry"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
var LM *Manager
|
|
||||||
|
|
||||||
// validate and update license every 24 hours
|
|
||||||
var validationFrequency = 24 * 60 * time.Minute
|
|
||||||
|
|
||||||
type Manager struct {
|
|
||||||
repo *Repo
|
|
||||||
zeus zeus.Zeus
|
|
||||||
mutex sync.Mutex
|
|
||||||
validatorRunning bool
|
|
||||||
// end the license validation, this is important to gracefully
|
|
||||||
// stopping validation and protect in-consistent updates
|
|
||||||
done chan struct{}
|
|
||||||
// terminated waits for the validate go routine to end
|
|
||||||
terminated chan struct{}
|
|
||||||
// last time the license was validated
|
|
||||||
lastValidated int64
|
|
||||||
// keep track of validation failure attempts
|
|
||||||
failedAttempts uint64
|
|
||||||
// keep track of active license and features
|
|
||||||
activeLicenseV3 *model.LicenseV3
|
|
||||||
activeFeatures basemodel.FeatureSet
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartManager(db *sqlx.DB, store sqlstore.SQLStore, zeus zeus.Zeus, features ...basemodel.Feature) (*Manager, error) {
|
|
||||||
if LM != nil {
|
|
||||||
return LM, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
repo := NewLicenseRepo(db, store)
|
|
||||||
m := &Manager{
|
|
||||||
repo: &repo,
|
|
||||||
zeus: zeus,
|
|
||||||
}
|
|
||||||
if err := m.start(features...); err != nil {
|
|
||||||
return m, err
|
|
||||||
}
|
|
||||||
|
|
||||||
LM = m
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// start loads active license in memory and initiates validator
|
|
||||||
func (lm *Manager) start(features ...basemodel.Feature) error {
|
|
||||||
return lm.LoadActiveLicenseV3(features...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) Stop() {
|
|
||||||
close(lm.done)
|
|
||||||
<-lm.terminated
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) SetActiveV3(l *model.LicenseV3, features ...basemodel.Feature) {
|
|
||||||
lm.mutex.Lock()
|
|
||||||
defer lm.mutex.Unlock()
|
|
||||||
|
|
||||||
if l == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
lm.activeLicenseV3 = l
|
|
||||||
lm.activeFeatures = append(l.Features, features...)
|
|
||||||
// set default features
|
|
||||||
setDefaultFeatures(lm)
|
|
||||||
|
|
||||||
err := lm.InitFeatures(lm.activeFeatures)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Panic("Couldn't activate features", zap.Error(err))
|
|
||||||
}
|
|
||||||
if !lm.validatorRunning {
|
|
||||||
// we want to make sure only one validator runs,
|
|
||||||
// we already have lock() so good to go
|
|
||||||
lm.validatorRunning = true
|
|
||||||
go lm.ValidatorV3(context.Background())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func setDefaultFeatures(lm *Manager) {
|
|
||||||
lm.activeFeatures = append(lm.activeFeatures, baseconstants.DEFAULT_FEATURE_SET...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
|
||||||
active, err := lm.repo.GetActiveLicenseV3(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if active != nil {
|
|
||||||
lm.SetActiveV3(active, features...)
|
|
||||||
} else {
|
|
||||||
zap.L().Info("No active license found, defaulting to basic plan")
|
|
||||||
// if no active license is found, we default to basic(free) plan with all default features
|
|
||||||
lm.activeFeatures = model.BasicPlan
|
|
||||||
setDefaultFeatures(lm)
|
|
||||||
err := lm.InitFeatures(lm.activeFeatures)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("Couldn't initialize features", zap.Error(err))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.LicenseV3, apiError *model.ApiError) {
|
|
||||||
|
|
||||||
licenses, err := lm.repo.GetLicensesV3(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, model.InternalError(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, l := range licenses {
|
|
||||||
if lm.activeLicenseV3 != nil && l.Key == lm.activeLicenseV3.Key {
|
|
||||||
l.IsCurrent = true
|
|
||||||
}
|
|
||||||
if l.ValidUntil == -1 {
|
|
||||||
// for subscriptions, there is no end-date as such
|
|
||||||
// but for showing user some validity we default one year timespan
|
|
||||||
l.ValidUntil = l.ValidFrom + 31556926
|
|
||||||
}
|
|
||||||
response = append(response, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validator validates license after an epoch of time
|
|
||||||
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
|
||||||
zap.L().Info("ValidatorV3 started!")
|
|
||||||
defer close(lm.terminated)
|
|
||||||
|
|
||||||
tick := time.NewTicker(validationFrequency)
|
|
||||||
defer tick.Stop()
|
|
||||||
|
|
||||||
_ = lm.ValidateV3(ctx)
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-lm.done:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case <-lm.done:
|
|
||||||
return
|
|
||||||
case <-tick.C:
|
|
||||||
_ = lm.ValidateV3(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) RefreshLicense(ctx context.Context) error {
|
|
||||||
license, err := validate.ValidateLicenseV3(ctx, lm.activeLicenseV3.Key, lm.zeus)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = lm.repo.UpdateLicenseV3(ctx, license)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
lm.SetActiveV3(license)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
|
|
||||||
if lm.activeLicenseV3 == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
lm.mutex.Lock()
|
|
||||||
|
|
||||||
lm.lastValidated = time.Now().Unix()
|
|
||||||
if reterr != nil {
|
|
||||||
zap.L().Error("License validation completed with error", zap.Error(reterr))
|
|
||||||
|
|
||||||
atomic.AddUint64(&lm.failedAttempts, 1)
|
|
||||||
// default to basic plan if validation fails for three consecutive times
|
|
||||||
if atomic.LoadUint64(&lm.failedAttempts) > 3 {
|
|
||||||
zap.L().Error("License validation completed with error for three consecutive times, defaulting to basic plan", zap.String("license_id", lm.activeLicenseV3.ID), zap.Bool("license_validation", false))
|
|
||||||
lm.activeLicenseV3 = nil
|
|
||||||
lm.activeFeatures = model.BasicPlan
|
|
||||||
setDefaultFeatures(lm)
|
|
||||||
err := lm.InitFeatures(lm.activeFeatures)
|
|
||||||
if err != nil {
|
|
||||||
zap.L().Error("Couldn't initialize features", zap.Error(err))
|
|
||||||
}
|
|
||||||
lm.done <- struct{}{}
|
|
||||||
lm.validatorRunning = false
|
|
||||||
}
|
|
||||||
|
|
||||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
|
||||||
map[string]interface{}{"err": reterr.Error()}, "", true, false)
|
|
||||||
} else {
|
|
||||||
// reset the failed attempts counter
|
|
||||||
atomic.StoreUint64(&lm.failedAttempts, 0)
|
|
||||||
zap.L().Info("License validation completed with no errors")
|
|
||||||
}
|
|
||||||
|
|
||||||
lm.mutex.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := lm.RefreshLicense(ctx)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) ActivateV3(ctx context.Context, licenseKey string) (*model.LicenseV3, error) {
|
|
||||||
license, err := validate.ValidateLicenseV3(ctx, licenseKey, lm.zeus)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert the new license to the sqlite db
|
|
||||||
modelErr := lm.repo.InsertLicenseV3(ctx, license)
|
|
||||||
if modelErr != nil {
|
|
||||||
zap.L().Error("failed to activate license", zap.Error(modelErr))
|
|
||||||
return nil, modelErr
|
|
||||||
}
|
|
||||||
|
|
||||||
// license is valid, activate it
|
|
||||||
lm.SetActiveV3(license)
|
|
||||||
return license, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) GetActiveLicense() *model.LicenseV3 {
|
|
||||||
return lm.activeLicenseV3
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckFeature will be internally used by backend routines
|
|
||||||
// for feature gating
|
|
||||||
func (lm *Manager) CheckFeature(featureKey string) error {
|
|
||||||
feature, err := lm.repo.GetFeature(featureKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if feature.Active {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return basemodel.ErrFeatureUnavailable{Key: featureKey}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFeatureFlags returns current active features
|
|
||||||
func (lm *Manager) GetFeatureFlags() (basemodel.FeatureSet, error) {
|
|
||||||
return lm.repo.GetAllFeatures()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) InitFeatures(features basemodel.FeatureSet) error {
|
|
||||||
featureStatus := make([]types.FeatureStatus, len(features))
|
|
||||||
for i, f := range features {
|
|
||||||
featureStatus[i] = types.FeatureStatus{
|
|
||||||
Name: f.Name,
|
|
||||||
Active: f.Active,
|
|
||||||
Usage: int(f.Usage),
|
|
||||||
UsageLimit: int(f.UsageLimit),
|
|
||||||
Route: f.Route,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lm.repo.InitFeatures(featureStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) UpdateFeatureFlag(feature basemodel.Feature) error {
|
|
||||||
return lm.repo.UpdateFeature(types.FeatureStatus{
|
|
||||||
Name: feature.Name,
|
|
||||||
Active: feature.Active,
|
|
||||||
Usage: int(feature.Usage),
|
|
||||||
UsageLimit: int(feature.UsageLimit),
|
|
||||||
Route: feature.Route,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lm *Manager) GetFeatureFlag(key string) (basemodel.Feature, error) {
|
|
||||||
featureStatus, err := lm.repo.GetFeature(key)
|
|
||||||
if err != nil {
|
|
||||||
return basemodel.Feature{}, err
|
|
||||||
}
|
|
||||||
return basemodel.Feature{
|
|
||||||
Name: featureStatus.Name,
|
|
||||||
Active: featureStatus.Active,
|
|
||||||
Usage: int64(featureStatus.Usage),
|
|
||||||
UsageLimit: int64(featureStatus.UsageLimit),
|
|
||||||
Route: featureStatus.Route,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRepo return the license repo
|
|
||||||
func (lm *Manager) GetRepo() *Repo {
|
|
||||||
return lm.repo
|
|
||||||
}
|
|
@ -6,6 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/ee/licensing"
|
||||||
|
"github.com/SigNoz/signoz/ee/licensing/httplicensing"
|
||||||
eeuserimpl "github.com/SigNoz/signoz/ee/modules/user/impluser"
|
eeuserimpl "github.com/SigNoz/signoz/ee/modules/user/impluser"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/app"
|
"github.com/SigNoz/signoz/ee/query-service/app"
|
||||||
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
|
||||||
@ -16,6 +18,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||||
"github.com/SigNoz/signoz/pkg/emailing"
|
"github.com/SigNoz/signoz/pkg/emailing"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
pkglicensing "github.com/SigNoz/signoz/pkg/licensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||||
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/pkg/signoz"
|
"github.com/SigNoz/signoz/pkg/signoz"
|
||||||
@ -23,6 +26,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
|
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
"github.com/SigNoz/signoz/pkg/version"
|
"github.com/SigNoz/signoz/pkg/version"
|
||||||
|
pkgzeus "github.com/SigNoz/signoz/pkg/zeus"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zapcore"
|
"go.uber.org/zap/zapcore"
|
||||||
@ -90,8 +94,9 @@ func main() {
|
|||||||
loggerMgr := initZapLog()
|
loggerMgr := initZapLog()
|
||||||
zap.ReplaceGlobals(loggerMgr)
|
zap.ReplaceGlobals(loggerMgr)
|
||||||
defer loggerMgr.Sync() // flushes buffer, if any
|
defer loggerMgr.Sync() // flushes buffer, if any
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
config, err := signoz.NewConfig(context.Background(), config.ResolverConfig{
|
config, err := signoz.NewConfig(ctx, config.ResolverConfig{
|
||||||
Uris: []string{"env:"},
|
Uris: []string{"env:"},
|
||||||
ProviderFactories: []config.ProviderFactory{
|
ProviderFactories: []config.ProviderFactory{
|
||||||
envprovider.NewFactory(),
|
envprovider.NewFactory(),
|
||||||
@ -129,6 +134,10 @@ func main() {
|
|||||||
config,
|
config,
|
||||||
zeus.Config(),
|
zeus.Config(),
|
||||||
httpzeus.NewProviderFactory(),
|
httpzeus.NewProviderFactory(),
|
||||||
|
licensing.Config(24*time.Hour, 3),
|
||||||
|
func(sqlstore sqlstore.SQLStore, zeus pkgzeus.Zeus) factory.ProviderFactory[pkglicensing.Licensing, pkglicensing.Config] {
|
||||||
|
return httplicensing.NewProviderFactory(sqlstore, zeus)
|
||||||
|
},
|
||||||
signoz.NewEmailingProviderFactories(),
|
signoz.NewEmailingProviderFactories(),
|
||||||
signoz.NewCacheProviderFactories(),
|
signoz.NewCacheProviderFactories(),
|
||||||
signoz.NewWebProviderFactories(),
|
signoz.NewWebProviderFactories(),
|
||||||
@ -163,22 +172,22 @@ func main() {
|
|||||||
zap.L().Fatal("Failed to create server", zap.Error(err))
|
zap.L().Fatal("Failed to create server", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := server.Start(context.Background()); err != nil {
|
if err := server.Start(ctx); err != nil {
|
||||||
zap.L().Fatal("Could not start server", zap.Error(err))
|
zap.L().Fatal("Could not start server", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
signoz.Start(context.Background())
|
signoz.Start(ctx)
|
||||||
|
|
||||||
if err := signoz.Wait(context.Background()); err != nil {
|
if err := signoz.Wait(ctx); err != nil {
|
||||||
zap.L().Fatal("Failed to start signoz", zap.Error(err))
|
zap.L().Fatal("Failed to start signoz", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = server.Stop()
|
err = server.Stop(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("Failed to stop server", zap.Error(err))
|
zap.L().Fatal("Failed to stop server", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
err = signoz.Stop(context.Background())
|
err = signoz.Stop(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Fatal("Failed to stop signoz", zap.Error(err))
|
zap.L().Fatal("Failed to stop signoz", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
@ -1,244 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type License struct {
|
|
||||||
Key string `json:"key" db:"key"`
|
|
||||||
ActivationId string `json:"activationId" db:"activationId"`
|
|
||||||
CreatedAt time.Time `db:"created_at"`
|
|
||||||
|
|
||||||
// PlanDetails contains the encrypted plan info
|
|
||||||
PlanDetails string `json:"planDetails" db:"planDetails"`
|
|
||||||
|
|
||||||
// stores parsed license details
|
|
||||||
LicensePlan
|
|
||||||
|
|
||||||
FeatureSet basemodel.FeatureSet
|
|
||||||
|
|
||||||
// populated in case license has any errors
|
|
||||||
ValidationMessage string `db:"validationMessage"`
|
|
||||||
|
|
||||||
// used only for sending details to front-end
|
|
||||||
IsCurrent bool `json:"isCurrent"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *License) MarshalJSON() ([]byte, error) {
|
|
||||||
|
|
||||||
return json.Marshal(&struct {
|
|
||||||
Key string `json:"key" db:"key"`
|
|
||||||
ActivationId string `json:"activationId" db:"activationId"`
|
|
||||||
ValidationMessage string `db:"validationMessage"`
|
|
||||||
IsCurrent bool `json:"isCurrent"`
|
|
||||||
PlanKey string `json:"planKey"`
|
|
||||||
ValidFrom time.Time `json:"ValidFrom"`
|
|
||||||
ValidUntil time.Time `json:"ValidUntil"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}{
|
|
||||||
Key: l.Key,
|
|
||||||
ActivationId: l.ActivationId,
|
|
||||||
IsCurrent: l.IsCurrent,
|
|
||||||
PlanKey: l.PlanKey,
|
|
||||||
ValidFrom: time.Unix(l.ValidFrom, 0),
|
|
||||||
ValidUntil: time.Unix(l.ValidUntil, 0),
|
|
||||||
Status: l.Status,
|
|
||||||
ValidationMessage: l.ValidationMessage,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicensePlan struct {
|
|
||||||
PlanKey string `json:"planKey"`
|
|
||||||
ValidFrom int64 `json:"validFrom"`
|
|
||||||
ValidUntil int64 `json:"validUntil"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Licenses struct {
|
|
||||||
TrialStart int64 `json:"trialStart"`
|
|
||||||
TrialEnd int64 `json:"trialEnd"`
|
|
||||||
OnTrial bool `json:"onTrial"`
|
|
||||||
WorkSpaceBlock bool `json:"workSpaceBlock"`
|
|
||||||
TrialConvertedToSubscription bool `json:"trialConvertedToSubscription"`
|
|
||||||
GracePeriodEnd int64 `json:"gracePeriodEnd"`
|
|
||||||
Licenses []License `json:"licenses"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SubscriptionServerResp struct {
|
|
||||||
Status string `json:"status"`
|
|
||||||
Data Licenses `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Plan struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicenseDB struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Key string `json:"key"`
|
|
||||||
Data string `json:"data"`
|
|
||||||
}
|
|
||||||
type LicenseV3 struct {
|
|
||||||
ID string
|
|
||||||
Key string
|
|
||||||
Data map[string]interface{}
|
|
||||||
PlanName string
|
|
||||||
Features basemodel.FeatureSet
|
|
||||||
Status string
|
|
||||||
IsCurrent bool
|
|
||||||
ValidFrom int64
|
|
||||||
ValidUntil int64
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
|
|
||||||
var zeroValue T
|
|
||||||
if val, ok := data[key]; ok {
|
|
||||||
if value, ok := val.(T); ok {
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
|
|
||||||
}
|
|
||||||
return zeroValue, fmt.Errorf("%s key is missing", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLicenseV3(data map[string]interface{}) (*LicenseV3, error) {
|
|
||||||
var features basemodel.FeatureSet
|
|
||||||
|
|
||||||
// extract id from data
|
|
||||||
licenseID, err := extractKeyFromMapStringInterface[string](data, "id")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
delete(data, "id")
|
|
||||||
|
|
||||||
// extract key from data
|
|
||||||
licenseKey, err := extractKeyFromMapStringInterface[string](data, "key")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
delete(data, "key")
|
|
||||||
|
|
||||||
// extract status from data
|
|
||||||
status, err := extractKeyFromMapStringInterface[string](data, "status")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
planMap, err := extractKeyFromMapStringInterface[map[string]any](data, "plan")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// if license status is invalid then default it to basic
|
|
||||||
if status == LicenseStatusInvalid {
|
|
||||||
planName = PlanNameBasic
|
|
||||||
}
|
|
||||||
|
|
||||||
featuresFromZeus := basemodel.FeatureSet{}
|
|
||||||
if _features, ok := data["features"]; ok {
|
|
||||||
featuresData, err := json.Marshal(_features)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to marshal features data")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to unmarshal features data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch planName {
|
|
||||||
case PlanNameEnterprise:
|
|
||||||
features = append(features, EnterprisePlan...)
|
|
||||||
case PlanNameBasic:
|
|
||||||
features = append(features, BasicPlan...)
|
|
||||||
default:
|
|
||||||
features = append(features, BasicPlan...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(featuresFromZeus) > 0 {
|
|
||||||
for _, feature := range featuresFromZeus {
|
|
||||||
exists := false
|
|
||||||
for i, existingFeature := range features {
|
|
||||||
if existingFeature.Name == feature.Name {
|
|
||||||
features[i] = feature // Replace existing feature
|
|
||||||
exists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
features = append(features, feature) // Append if it doesn't exist
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data["features"] = features
|
|
||||||
|
|
||||||
_validFrom, err := extractKeyFromMapStringInterface[float64](data, "valid_from")
|
|
||||||
if err != nil {
|
|
||||||
_validFrom = 0
|
|
||||||
}
|
|
||||||
validFrom := int64(_validFrom)
|
|
||||||
|
|
||||||
_validUntil, err := extractKeyFromMapStringInterface[float64](data, "valid_until")
|
|
||||||
if err != nil {
|
|
||||||
_validUntil = 0
|
|
||||||
}
|
|
||||||
validUntil := int64(_validUntil)
|
|
||||||
|
|
||||||
return &LicenseV3{
|
|
||||||
ID: licenseID,
|
|
||||||
Key: licenseKey,
|
|
||||||
Data: data,
|
|
||||||
PlanName: planName,
|
|
||||||
Features: features,
|
|
||||||
ValidFrom: validFrom,
|
|
||||||
ValidUntil: validUntil,
|
|
||||||
Status: status,
|
|
||||||
}, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLicenseV3WithIDAndKey(id string, key string, data map[string]interface{}) (*LicenseV3, error) {
|
|
||||||
licenseDataWithIdAndKey := data
|
|
||||||
licenseDataWithIdAndKey["id"] = id
|
|
||||||
licenseDataWithIdAndKey["key"] = key
|
|
||||||
return NewLicenseV3(licenseDataWithIdAndKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertLicenseV3ToLicenseV2(l *LicenseV3) *License {
|
|
||||||
planKeyFromPlanName, ok := MapOldPlanKeyToNewPlanName[l.PlanName]
|
|
||||||
if !ok {
|
|
||||||
planKeyFromPlanName = Basic
|
|
||||||
}
|
|
||||||
return &License{
|
|
||||||
Key: l.Key,
|
|
||||||
ActivationId: "",
|
|
||||||
PlanDetails: "",
|
|
||||||
FeatureSet: l.Features,
|
|
||||||
ValidationMessage: "",
|
|
||||||
IsCurrent: l.IsCurrent,
|
|
||||||
LicensePlan: LicensePlan{
|
|
||||||
PlanKey: planKeyFromPlanName,
|
|
||||||
ValidFrom: l.ValidFrom,
|
|
||||||
ValidUntil: l.ValidUntil,
|
|
||||||
Status: l.Status},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckoutRequest struct {
|
|
||||||
SuccessURL string `json:"url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PortalRequest struct {
|
|
||||||
SuccessURL string `json:"url"`
|
|
||||||
}
|
|
@ -1,170 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewLicenseV3(t *testing.T) {
|
|
||||||
testCases := []struct {
|
|
||||||
name string
|
|
||||||
data []byte
|
|
||||||
pass bool
|
|
||||||
expected *LicenseV3
|
|
||||||
error error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Error for missing license id",
|
|
||||||
data: []byte(`{}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("id key is missing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for license id not being a valid string",
|
|
||||||
data: []byte(`{"id": 10}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("id key is not a valid string"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for missing license key",
|
|
||||||
data: []byte(`{"id":"does-not-matter"}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("key key is missing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for invalid string license key",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":10}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("key key is not a valid string"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for missing license status",
|
|
||||||
data: []byte(`{"id":"does-not-matter", "key": "does-not-matter","category":"FREE"}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("status key is missing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for invalid string license status",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key": "does-not-matter", "category":"FREE", "status":10}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("status key is not a valid string"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for missing license plan",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("plan key is missing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for invalid json license plan",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("plan key is not a valid map[string]interface {}"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Error for invalid license plan",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
|
|
||||||
pass: false,
|
|
||||||
error: errors.New("name key is missing"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Parse the entire license properly",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
|
||||||
pass: true,
|
|
||||||
expected: &LicenseV3{
|
|
||||||
ID: "does-not-matter",
|
|
||||||
Key: "does-not-matter-key",
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"plan": map[string]interface{}{
|
|
||||||
"name": "ENTERPRISE",
|
|
||||||
},
|
|
||||||
"category": "FREE",
|
|
||||||
"status": "ACTIVE",
|
|
||||||
"valid_from": float64(1730899309),
|
|
||||||
"valid_until": float64(-1),
|
|
||||||
},
|
|
||||||
PlanName: PlanNameEnterprise,
|
|
||||||
ValidFrom: 1730899309,
|
|
||||||
ValidUntil: -1,
|
|
||||||
Status: "ACTIVE",
|
|
||||||
IsCurrent: false,
|
|
||||||
Features: model.FeatureSet{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Fallback to basic plan if license status is invalid",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
|
||||||
pass: true,
|
|
||||||
expected: &LicenseV3{
|
|
||||||
ID: "does-not-matter",
|
|
||||||
Key: "does-not-matter-key",
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"plan": map[string]interface{}{
|
|
||||||
"name": "ENTERPRISE",
|
|
||||||
},
|
|
||||||
"category": "FREE",
|
|
||||||
"status": "INVALID",
|
|
||||||
"valid_from": float64(1730899309),
|
|
||||||
"valid_until": float64(-1),
|
|
||||||
},
|
|
||||||
PlanName: PlanNameBasic,
|
|
||||||
ValidFrom: 1730899309,
|
|
||||||
ValidUntil: -1,
|
|
||||||
Status: "INVALID",
|
|
||||||
IsCurrent: false,
|
|
||||||
Features: model.FeatureSet{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "fallback states for validFrom and validUntil",
|
|
||||||
data: []byte(`{"id":"does-not-matter","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from":1234.456,"valid_until":5678.567}`),
|
|
||||||
pass: true,
|
|
||||||
expected: &LicenseV3{
|
|
||||||
ID: "does-not-matter",
|
|
||||||
Key: "does-not-matter-key",
|
|
||||||
Data: map[string]interface{}{
|
|
||||||
"plan": map[string]interface{}{
|
|
||||||
"name": "ENTERPRISE",
|
|
||||||
},
|
|
||||||
"valid_from": 1234.456,
|
|
||||||
"valid_until": 5678.567,
|
|
||||||
"category": "FREE",
|
|
||||||
"status": "ACTIVE",
|
|
||||||
},
|
|
||||||
PlanName: PlanNameEnterprise,
|
|
||||||
ValidFrom: 1234,
|
|
||||||
ValidUntil: 5678,
|
|
||||||
Status: "ACTIVE",
|
|
||||||
IsCurrent: false,
|
|
||||||
Features: model.FeatureSet{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
var licensePayload map[string]interface{}
|
|
||||||
err := json.Unmarshal(tc.data, &licensePayload)
|
|
||||||
require.NoError(t, err)
|
|
||||||
license, err := NewLicenseV3(licensePayload)
|
|
||||||
if license != nil {
|
|
||||||
license.Features = make(model.FeatureSet, 0)
|
|
||||||
delete(license.Data, "features")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tc.pass {
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotNil(t, license)
|
|
||||||
assert.Equal(t, tc.expected, license)
|
|
||||||
} else {
|
|
||||||
require.Error(t, err)
|
|
||||||
assert.EqualError(t, err, tc.error.Error())
|
|
||||||
require.Nil(t, license)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,8 +15,9 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/dao"
|
"github.com/SigNoz/signoz/ee/query-service/dao"
|
||||||
"github.com/SigNoz/signoz/ee/query-service/license"
|
|
||||||
"github.com/SigNoz/signoz/ee/query-service/model"
|
"github.com/SigNoz/signoz/ee/query-service/model"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/modules/organization"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/utils/encryption"
|
"github.com/SigNoz/signoz/pkg/query-service/utils/encryption"
|
||||||
"github.com/SigNoz/signoz/pkg/zeus"
|
"github.com/SigNoz/signoz/pkg/zeus"
|
||||||
)
|
)
|
||||||
@ -35,49 +36,57 @@ var (
|
|||||||
type Manager struct {
|
type Manager struct {
|
||||||
clickhouseConn clickhouse.Conn
|
clickhouseConn clickhouse.Conn
|
||||||
|
|
||||||
licenseRepo *license.Repo
|
licenseService licensing.Licensing
|
||||||
|
|
||||||
scheduler *gocron.Scheduler
|
scheduler *gocron.Scheduler
|
||||||
|
|
||||||
modelDao dao.ModelDao
|
modelDao dao.ModelDao
|
||||||
|
|
||||||
zeus zeus.Zeus
|
zeus zeus.Zeus
|
||||||
|
|
||||||
|
organizationModule organization.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(modelDao dao.ModelDao, licenseRepo *license.Repo, clickhouseConn clickhouse.Conn, zeus zeus.Zeus) (*Manager, error) {
|
func New(modelDao dao.ModelDao, licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus,organizationModule organization.Module) (*Manager, error) {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
clickhouseConn: clickhouseConn,
|
clickhouseConn: clickhouseConn,
|
||||||
licenseRepo: licenseRepo,
|
licenseService: licenseService,
|
||||||
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
|
||||||
modelDao: modelDao,
|
modelDao: modelDao,
|
||||||
zeus: zeus,
|
zeus: zeus,
|
||||||
|
organizationModule: organizationModule,
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// start loads collects and exports any exported snapshot and starts the exporter
|
// start loads collects and exports any exported snapshot and starts the exporter
|
||||||
func (lm *Manager) Start() error {
|
func (lm *Manager) Start(ctx context.Context) error {
|
||||||
// compares the locker and stateUnlocked if both are same lock is applied else returns error
|
// compares the locker and stateUnlocked if both are same lock is applied else returns error
|
||||||
if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
|
if !atomic.CompareAndSwapUint32(&locker, stateUnlocked, stateLocked) {
|
||||||
return fmt.Errorf("usage exporter is locked")
|
return fmt.Errorf("usage exporter is locked")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := lm.scheduler.Do(func() { lm.UploadUsage() })
|
// upload usage once when starting the service
|
||||||
|
|
||||||
|
_, err := lm.scheduler.Do(func() { lm.UploadUsage(ctx) })
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// upload usage once when starting the service
|
lm.UploadUsage(ctx)
|
||||||
lm.UploadUsage()
|
|
||||||
|
|
||||||
lm.scheduler.StartAsync()
|
lm.scheduler.StartAsync()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (lm *Manager) UploadUsage() {
|
func (lm *Manager) UploadUsage(ctx context.Context) {
|
||||||
ctx := context.Background()
|
|
||||||
|
organizations, err := lm.organizationModule.GetAll(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
zap.L().Error("failed to get organizations", zap.Error(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, organization := range organizations {
|
||||||
// check if license is present or not
|
// check if license is present or not
|
||||||
license, err := lm.licenseRepo.GetActiveLicense(ctx)
|
license, err := lm.licenseService.GetActive(ctx, organization.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Error("failed to get active license", zap.Error(err))
|
zap.L().Error("failed to get active license", zap.Error(err))
|
||||||
return
|
return
|
||||||
@ -169,14 +178,14 @@ func (lm *Manager) UploadUsage() {
|
|||||||
// not returning error here since it is captured in the failed count
|
// not returning error here since it is captured in the failed count
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (lm *Manager) Stop() {
|
func (lm *Manager) Stop(ctx context.Context) {
|
||||||
lm.scheduler.Stop()
|
lm.scheduler.Stop()
|
||||||
|
|
||||||
zap.L().Info("sending usage data before shutting down")
|
zap.L().Info("sending usage data before shutting down")
|
||||||
// send usage before shutting down
|
// send usage before shutting down
|
||||||
lm.UploadUsage()
|
lm.UploadUsage(ctx)
|
||||||
|
|
||||||
atomic.StoreUint32(&locker, stateUnlocked)
|
atomic.StoreUint32(&locker, stateUnlocked)
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
user,
|
user,
|
||||||
isLoggedIn: isLoggedInState,
|
isLoggedIn: isLoggedInState,
|
||||||
isFetchingOrgPreferences,
|
isFetchingOrgPreferences,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
featureFlags,
|
featureFlags,
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
@ -145,16 +145,16 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
if (!isFetchingActiveLicense && activeLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
const currentRoute = mapRoutes.get('current');
|
||||||
|
|
||||||
const isTerminated = activeLicenseV3.state === LicenseState.TERMINATED;
|
const isTerminated = activeLicense.state === LicenseState.TERMINATED;
|
||||||
const isExpired = activeLicenseV3.state === LicenseState.EXPIRED;
|
const isExpired = activeLicense.state === LicenseState.EXPIRED;
|
||||||
const isCancelled = activeLicenseV3.state === LicenseState.CANCELLED;
|
const isCancelled = activeLicense.state === LicenseState.CANCELLED;
|
||||||
|
|
||||||
const isWorkspaceAccessRestricted = isTerminated || isExpired || isCancelled;
|
const isWorkspaceAccessRestricted = isTerminated || isExpired || isCancelled;
|
||||||
|
|
||||||
const { platform } = activeLicenseV3;
|
const { platform } = activeLicense;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isWorkspaceAccessRestricted &&
|
isWorkspaceAccessRestricted &&
|
||||||
@ -164,26 +164,26 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
navigateToWorkSpaceAccessRestricted(currentRoute);
|
navigateToWorkSpaceAccessRestricted(currentRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3, mapRoutes, pathname]);
|
}, [isFetchingActiveLicense, activeLicense, mapRoutes, pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3) {
|
if (!isFetchingActiveLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
const currentRoute = mapRoutes.get('current');
|
||||||
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shouldBlockWorkspace &&
|
shouldBlockWorkspace &&
|
||||||
currentRoute &&
|
currentRoute &&
|
||||||
activeLicenseV3?.platform === LicensePlatform.CLOUD
|
activeLicense?.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
navigateToWorkSpaceBlocked(currentRoute);
|
navigateToWorkSpaceBlocked(currentRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [
|
}, [
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo?.workSpaceBlock,
|
trialInfo?.workSpaceBlock,
|
||||||
activeLicenseV3?.platform,
|
activeLicense?.platform,
|
||||||
mapRoutes,
|
mapRoutes,
|
||||||
pathname,
|
pathname,
|
||||||
]);
|
]);
|
||||||
@ -197,20 +197,20 @@ function PrivateRoute({ children }: PrivateRouteProps): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
if (!isFetchingActiveLicense && activeLicense) {
|
||||||
const currentRoute = mapRoutes.get('current');
|
const currentRoute = mapRoutes.get('current');
|
||||||
const shouldSuspendWorkspace =
|
const shouldSuspendWorkspace =
|
||||||
activeLicenseV3.state === LicenseState.DEFAULTED;
|
activeLicense.state === LicenseState.DEFAULTED;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
shouldSuspendWorkspace &&
|
shouldSuspendWorkspace &&
|
||||||
currentRoute &&
|
currentRoute &&
|
||||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
navigateToWorkSpaceSuspended(currentRoute);
|
navigateToWorkSpaceSuspended(currentRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3, mapRoutes, pathname]);
|
}, [isFetchingActiveLicense, activeLicense, mapRoutes, pathname]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (org && org.length > 0 && org[0].id !== undefined) {
|
if (org && org.length > 0 && org[0].id !== undefined) {
|
||||||
|
@ -13,9 +13,9 @@ import AppLayout from 'container/AppLayout';
|
|||||||
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { KeyboardHotkeysProvider } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import { useThemeConfig } from 'hooks/useDarkMode';
|
import { useThemeConfig } from 'hooks/useDarkMode';
|
||||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||||
import { LICENSE_PLAN_KEY } from 'hooks/useLicense';
|
|
||||||
import { NotificationProvider } from 'hooks/useNotifications';
|
import { NotificationProvider } from 'hooks/useNotifications';
|
||||||
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
import { ResourceProvider } from 'hooks/useResourceAttribute';
|
||||||
|
import { StatusCodes } from 'http-status-codes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
import ErrorBoundaryFallback from 'pages/ErrorBoundaryFallback/ErrorBoundaryFallback';
|
||||||
import posthog from 'posthog-js';
|
import posthog from 'posthog-js';
|
||||||
@ -41,14 +41,13 @@ import defaultRoutes, {
|
|||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
const themeConfig = useThemeConfig();
|
const themeConfig = useThemeConfig();
|
||||||
const {
|
const {
|
||||||
licenses,
|
|
||||||
user,
|
user,
|
||||||
isFetchingUser,
|
isFetchingUser,
|
||||||
isFetchingLicenses,
|
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
|
activeLicenseFetchError,
|
||||||
userFetchError,
|
userFetchError,
|
||||||
featureFlagsFetchError,
|
featureFlagsFetchError,
|
||||||
isLoggedIn: isLoggedInState,
|
isLoggedIn: isLoggedInState,
|
||||||
@ -66,7 +65,7 @@ function App(): JSX.Element {
|
|||||||
const enableAnalytics = useCallback(
|
const enableAnalytics = useCallback(
|
||||||
(user: IUser): void => {
|
(user: IUser): void => {
|
||||||
// wait for the required data to be loaded before doing init for anything!
|
// wait for the required data to be loaded before doing init for anything!
|
||||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3 && org) {
|
if (!isFetchingActiveLicense && activeLicense && org) {
|
||||||
const orgName =
|
const orgName =
|
||||||
org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
|
org && Array.isArray(org) && org.length > 0 ? org[0].displayName : '';
|
||||||
|
|
||||||
@ -153,8 +152,8 @@ function App(): JSX.Element {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
hostname,
|
hostname,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
org,
|
org,
|
||||||
trialInfo?.trialConvertedToSubscription,
|
trialInfo?.trialConvertedToSubscription,
|
||||||
],
|
],
|
||||||
@ -163,18 +162,17 @@ function App(): JSX.Element {
|
|||||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFetchingLicenses &&
|
!isFetchingActiveLicense &&
|
||||||
licenses &&
|
(activeLicense || activeLicenseFetchError) &&
|
||||||
!isFetchingUser &&
|
!isFetchingUser &&
|
||||||
user &&
|
user &&
|
||||||
!!user.email
|
!!user.email
|
||||||
) {
|
) {
|
||||||
const isOnBasicPlan =
|
const isOnBasicPlan =
|
||||||
licenses.licenses?.some(
|
activeLicenseFetchError &&
|
||||||
(license) =>
|
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||||
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
|
activeLicenseFetchError?.getHttpStatusCode(),
|
||||||
) || licenses.licenses === null;
|
);
|
||||||
|
|
||||||
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
const isIdentifiedUser = getLocalStorageApi(LOCALSTORAGE.IS_IDENTIFIED_USER);
|
||||||
|
|
||||||
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
if (isLoggedInState && user && user.id && user.email && !isIdentifiedUser) {
|
||||||
@ -204,11 +202,12 @@ function App(): JSX.Element {
|
|||||||
}, [
|
}, [
|
||||||
isLoggedInState,
|
isLoggedInState,
|
||||||
user,
|
user,
|
||||||
licenses,
|
|
||||||
isCloudUser,
|
isCloudUser,
|
||||||
isEnterpriseSelfHostedUser,
|
isEnterpriseSelfHostedUser,
|
||||||
isFetchingLicenses,
|
isFetchingActiveLicense,
|
||||||
isFetchingUser,
|
isFetchingUser,
|
||||||
|
activeLicense,
|
||||||
|
activeLicenseFetchError,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -231,8 +230,7 @@ function App(): JSX.Element {
|
|||||||
if (
|
if (
|
||||||
!isFetchingFeatureFlags &&
|
!isFetchingFeatureFlags &&
|
||||||
(featureFlags || featureFlagsFetchError) &&
|
(featureFlags || featureFlagsFetchError) &&
|
||||||
licenses &&
|
activeLicense &&
|
||||||
activeLicenseV3 &&
|
|
||||||
trialInfo
|
trialInfo
|
||||||
) {
|
) {
|
||||||
let isChatSupportEnabled = false;
|
let isChatSupportEnabled = false;
|
||||||
@ -270,8 +268,7 @@ function App(): JSX.Element {
|
|||||||
featureFlags,
|
featureFlags,
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
featureFlagsFetchError,
|
featureFlagsFetchError,
|
||||||
licenses,
|
activeLicense,
|
||||||
activeLicenseV3,
|
|
||||||
trialInfo,
|
trialInfo,
|
||||||
isCloudUser,
|
isCloudUser,
|
||||||
isEnterpriseSelfHostedUser,
|
isEnterpriseSelfHostedUser,
|
||||||
@ -333,7 +330,7 @@ function App(): JSX.Element {
|
|||||||
// if the user is in logged in state
|
// if the user is in logged in state
|
||||||
if (isLoggedInState) {
|
if (isLoggedInState) {
|
||||||
// if the setup calls are loading then return a spinner
|
// if the setup calls are loading then return a spinner
|
||||||
if (isFetchingLicenses || isFetchingUser || isFetchingFeatureFlags) {
|
if (isFetchingActiveLicense || isFetchingUser || isFetchingFeatureFlags) {
|
||||||
return <Spinner tip="Loading..." />;
|
return <Spinner tip="Loading..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -345,7 +342,11 @@ function App(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
|
// if all of the data is not set then return a spinner, this is required because there is some gap between loading states and data setting
|
||||||
if ((!licenses || !user.email || !featureFlags) && !userFetchError) {
|
if (
|
||||||
|
(!activeLicense || !user.email || !featureFlags) &&
|
||||||
|
!userFetchError &&
|
||||||
|
!activeLicenseFetchError
|
||||||
|
) {
|
||||||
return <Spinner tip="Loading..." />;
|
return <Spinner tip="Loading..." />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,29 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import {
|
|
||||||
CheckoutRequestPayloadProps,
|
|
||||||
CheckoutSuccessPayloadProps,
|
|
||||||
} from 'types/api/billing/checkout';
|
|
||||||
|
|
||||||
const updateCreditCardApi = async (
|
|
||||||
props: CheckoutRequestPayloadProps,
|
|
||||||
): Promise<SuccessResponse<CheckoutSuccessPayloadProps> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post('/checkout', {
|
|
||||||
url: props.url,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default updateCreditCardApi;
|
|
@ -1,29 +0,0 @@
|
|||||||
import axios from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import {
|
|
||||||
CheckoutRequestPayloadProps,
|
|
||||||
CheckoutSuccessPayloadProps,
|
|
||||||
} from 'types/api/billing/checkout';
|
|
||||||
|
|
||||||
const manageCreditCardApi = async (
|
|
||||||
props: CheckoutRequestPayloadProps,
|
|
||||||
): Promise<SuccessResponse<CheckoutSuccessPayloadProps> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post('/portal', {
|
|
||||||
url: props.url,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default manageCreditCardApi;
|
|
@ -1,26 +0,0 @@
|
|||||||
import { ApiV3Instance as axios } from 'api';
|
|
||||||
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
|
|
||||||
import { AxiosError } from 'axios';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps, Props } from 'types/api/licenses/apply';
|
|
||||||
|
|
||||||
const apply = async (
|
|
||||||
props: Props,
|
|
||||||
): Promise<SuccessResponse<PayloadProps> | ErrorResponse> => {
|
|
||||||
try {
|
|
||||||
const response = await axios.post('/licenses', {
|
|
||||||
key: props.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return ErrorResponseHandler(error as AxiosError);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default apply;
|
|
@ -1,18 +0,0 @@
|
|||||||
import { ApiV3Instance as axios } from 'api';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps } from 'types/api/licenses/getAll';
|
|
||||||
|
|
||||||
const getAll = async (): Promise<
|
|
||||||
SuccessResponse<PayloadProps> | ErrorResponse
|
|
||||||
> => {
|
|
||||||
const response = await axios.get('/licenses');
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getAll;
|
|
@ -1,18 +0,0 @@
|
|||||||
import { ApiV3Instance as axios } from 'api';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { LicenseV3EventQueueResModel } from 'types/api/licensesV3/getActive';
|
|
||||||
|
|
||||||
const getActive = async (): Promise<
|
|
||||||
SuccessResponse<LicenseV3EventQueueResModel> | ErrorResponse
|
|
||||||
> => {
|
|
||||||
const response = await axios.get('/licenses/active');
|
|
||||||
|
|
||||||
return {
|
|
||||||
statusCode: 200,
|
|
||||||
error: null,
|
|
||||||
message: response.data.status,
|
|
||||||
payload: response.data.data,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default getActive;
|
|
28
frontend/src/api/v1/checkout/create.ts
Normal file
28
frontend/src/api/v1/checkout/create.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||||
|
import {
|
||||||
|
CheckoutRequestPayloadProps,
|
||||||
|
CheckoutSuccessPayloadProps,
|
||||||
|
PayloadProps,
|
||||||
|
} from 'types/api/billing/checkout';
|
||||||
|
|
||||||
|
const updateCreditCardApi = async (
|
||||||
|
props: CheckoutRequestPayloadProps,
|
||||||
|
): Promise<SuccessResponseV2<CheckoutSuccessPayloadProps>> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post<PayloadProps>('/checkout', {
|
||||||
|
url: props.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpStatusCode: response.status,
|
||||||
|
data: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default updateCreditCardApi;
|
28
frontend/src/api/v1/portal/create.ts
Normal file
28
frontend/src/api/v1/portal/create.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import axios from 'api';
|
||||||
|
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||||
|
import {
|
||||||
|
CheckoutRequestPayloadProps,
|
||||||
|
CheckoutSuccessPayloadProps,
|
||||||
|
PayloadProps,
|
||||||
|
} from 'types/api/billing/checkout';
|
||||||
|
|
||||||
|
const manageCreditCardApi = async (
|
||||||
|
props: CheckoutRequestPayloadProps,
|
||||||
|
): Promise<SuccessResponseV2<CheckoutSuccessPayloadProps>> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post<PayloadProps>('/portal', {
|
||||||
|
url: props.url,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpStatusCode: response.status,
|
||||||
|
data: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default manageCreditCardApi;
|
25
frontend/src/api/v3/licenses/active/get.ts
Normal file
25
frontend/src/api/v3/licenses/active/get.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ApiV3Instance as axios } from 'api';
|
||||||
|
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||||
|
import {
|
||||||
|
LicenseEventQueueResModel,
|
||||||
|
PayloadProps,
|
||||||
|
} from 'types/api/licensesV3/getActive';
|
||||||
|
|
||||||
|
const getActive = async (): Promise<
|
||||||
|
SuccessResponseV2<LicenseEventQueueResModel>
|
||||||
|
> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get<PayloadProps>('/licenses/active');
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpStatusCode: response.status,
|
||||||
|
data: response.data.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getActive;
|
24
frontend/src/api/v3/licenses/put.ts
Normal file
24
frontend/src/api/v3/licenses/put.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiV3Instance as axios } from 'api';
|
||||||
|
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
|
||||||
|
import { AxiosError } from 'axios';
|
||||||
|
import { ErrorV2Resp, SuccessResponseV2 } from 'types/api';
|
||||||
|
import { PayloadProps, Props } from 'types/api/licenses/apply';
|
||||||
|
|
||||||
|
const apply = async (
|
||||||
|
props: Props,
|
||||||
|
): Promise<SuccessResponseV2<PayloadProps>> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post<PayloadProps>('/licenses', {
|
||||||
|
key: props.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
httpStatusCode: response.status,
|
||||||
|
data: response.data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
ErrorResponseHandlerV2(error as AxiosError<ErrorV2Resp>);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default apply;
|
@ -1,14 +1,14 @@
|
|||||||
import { Button, Modal, Typography } from 'antd';
|
import { Button, Modal, Typography } from 'antd';
|
||||||
import updateCreditCardApi from 'api/billing/checkout';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import updateCreditCardApi from 'api/v1/checkout/create';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { CreditCard, X } from 'lucide-react';
|
import { CreditCard, X } from 'lucide-react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
export default function ChatSupportGateway(): JSX.Element {
|
export default function ChatSupportGateway(): JSX.Element {
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
@ -18,20 +18,21 @@ export default function ChatSupportGateway(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleBillingOnSuccess = (
|
const handleBillingOnSuccess = (
|
||||||
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||||
): void => {
|
): void => {
|
||||||
if (data?.payload?.redirectURL) {
|
if (data?.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBillingOnError = (): void => {
|
const handleBillingOnError = (error: APIError): void => {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: SOMETHING_WENT_WRONG,
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import './LaunchChatSupport.styles.scss';
|
import './LaunchChatSupport.styles.scss';
|
||||||
|
|
||||||
import { Button, Modal, Tooltip, Typography } from 'antd';
|
import { Button, Modal, Tooltip, Typography } from 'antd';
|
||||||
import updateCreditCardApi from 'api/billing/checkout';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import updateCreditCardApi from 'api/v1/checkout/create';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
@ -14,8 +13,9 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
export interface LaunchChatSupportProps {
|
export interface LaunchChatSupportProps {
|
||||||
eventName: string;
|
eventName: string;
|
||||||
@ -118,20 +118,21 @@ function LaunchChatSupport({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleBillingOnSuccess = (
|
const handleBillingOnSuccess = (
|
||||||
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||||
): void => {
|
): void => {
|
||||||
if (data?.payload?.redirectURL) {
|
if (data?.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBillingOnError = (): void => {
|
const handleBillingOnError = (error: APIError): void => {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: SOMETHING_WENT_WRONG,
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -5,16 +5,15 @@ import './AppLayout.styles.scss';
|
|||||||
|
|
||||||
import * as Sentry from '@sentry/react';
|
import * as Sentry from '@sentry/react';
|
||||||
import { Flex } from 'antd';
|
import { Flex } from 'antd';
|
||||||
import manageCreditCardApi from 'api/billing/manage';
|
|
||||||
import getLocalStorageApi from 'api/browser/localstorage/get';
|
import getLocalStorageApi from 'api/browser/localstorage/get';
|
||||||
import setLocalStorageApi from 'api/browser/localstorage/set';
|
import setLocalStorageApi from 'api/browser/localstorage/set';
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import manageCreditCardApi from 'api/v1/portal/create';
|
||||||
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
|
import getUserLatestVersion from 'api/v1/version/getLatestVersion';
|
||||||
import getUserVersion from 'api/v1/version/getVersion';
|
import getUserVersion from 'api/v1/version/getVersion';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
|
import ChatSupportGateway from 'components/ChatSupportGateway/ChatSupportGateway';
|
||||||
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
|
||||||
import { Events } from 'constants/events';
|
import { Events } from 'constants/events';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { LOCALSTORAGE } from 'constants/localStorage';
|
import { LOCALSTORAGE } from 'constants/localStorage';
|
||||||
@ -51,8 +50,9 @@ import {
|
|||||||
UPDATE_LATEST_VERSION,
|
UPDATE_LATEST_VERSION,
|
||||||
UPDATE_LATEST_VERSION_ERROR,
|
UPDATE_LATEST_VERSION_ERROR,
|
||||||
} from 'types/actions/app';
|
} from 'types/actions/app';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import {
|
import {
|
||||||
LicenseEvent,
|
LicenseEvent,
|
||||||
LicensePlatform,
|
LicensePlatform,
|
||||||
@ -75,8 +75,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
user,
|
user,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
featureFlags,
|
featureFlags,
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
featureFlagsFetchError,
|
featureFlagsFetchError,
|
||||||
@ -93,20 +93,21 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
const [slowApiWarningShown, setSlowApiWarningShown] = useState(false);
|
const [slowApiWarningShown, setSlowApiWarningShown] = useState(false);
|
||||||
|
|
||||||
const handleBillingOnSuccess = (
|
const handleBillingOnSuccess = (
|
||||||
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||||
): void => {
|
): void => {
|
||||||
if (data?.payload?.redirectURL) {
|
if (data?.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBillingOnError = (): void => {
|
const handleBillingOnError = (error: APIError): void => {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: SOMETHING_WENT_WRONG,
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -260,8 +261,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFetchingActiveLicenseV3 &&
|
!isFetchingActiveLicense &&
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
trialInfo?.onTrial &&
|
trialInfo?.onTrial &&
|
||||||
!trialInfo?.trialConvertedToSubscription &&
|
!trialInfo?.trialConvertedToSubscription &&
|
||||||
!trialInfo?.workSpaceBlock &&
|
!trialInfo?.workSpaceBlock &&
|
||||||
@ -269,16 +270,16 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
) {
|
) {
|
||||||
setShowTrialExpiryBanner(true);
|
setShowTrialExpiryBanner(true);
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3, trialInfo]);
|
}, [isFetchingActiveLicense, activeLicense, trialInfo]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3 && activeLicenseV3) {
|
if (!isFetchingActiveLicense && activeLicense) {
|
||||||
const isTerminated = activeLicenseV3.state === LicenseState.TERMINATED;
|
const isTerminated = activeLicense.state === LicenseState.TERMINATED;
|
||||||
const isExpired = activeLicenseV3.state === LicenseState.EXPIRED;
|
const isExpired = activeLicense.state === LicenseState.EXPIRED;
|
||||||
const isCancelled = activeLicenseV3.state === LicenseState.CANCELLED;
|
const isCancelled = activeLicense.state === LicenseState.CANCELLED;
|
||||||
const isDefaulted = activeLicenseV3.state === LicenseState.DEFAULTED;
|
const isDefaulted = activeLicense.state === LicenseState.DEFAULTED;
|
||||||
const isEvaluationExpired =
|
const isEvaluationExpired =
|
||||||
activeLicenseV3.state === LicenseState.EVALUATION_EXPIRED;
|
activeLicense.state === LicenseState.EVALUATION_EXPIRED;
|
||||||
|
|
||||||
const isWorkspaceAccessRestricted =
|
const isWorkspaceAccessRestricted =
|
||||||
isTerminated ||
|
isTerminated ||
|
||||||
@ -287,7 +288,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isDefaulted ||
|
isDefaulted ||
|
||||||
isEvaluationExpired;
|
isEvaluationExpired;
|
||||||
|
|
||||||
const { platform } = activeLicenseV3;
|
const { platform } = activeLicense;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isWorkspaceAccessRestricted &&
|
isWorkspaceAccessRestricted &&
|
||||||
@ -296,17 +297,17 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
setShowWorkspaceRestricted(true);
|
setShowWorkspaceRestricted(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
}, [isFetchingActiveLicense, activeLicense]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFetchingActiveLicenseV3 &&
|
!isFetchingActiveLicense &&
|
||||||
!isNull(activeLicenseV3) &&
|
!isNull(activeLicense) &&
|
||||||
activeLicenseV3?.event_queue?.event === LicenseEvent.DEFAULT
|
activeLicense?.event_queue?.event === LicenseEvent.DEFAULT
|
||||||
) {
|
) {
|
||||||
setShowPaymentFailedWarning(true);
|
setShowPaymentFailedWarning(true);
|
||||||
}
|
}
|
||||||
}, [activeLicenseV3, isFetchingActiveLicenseV3]);
|
}, [activeLicense, isFetchingActiveLicense]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// after logging out hide the trial expiry banner
|
// after logging out hide the trial expiry banner
|
||||||
@ -392,7 +393,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
if (
|
if (
|
||||||
!isFetchingFeatureFlags &&
|
!isFetchingFeatureFlags &&
|
||||||
(featureFlags || featureFlagsFetchError) &&
|
(featureFlags || featureFlagsFetchError) &&
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
trialInfo
|
trialInfo
|
||||||
) {
|
) {
|
||||||
let isChatSupportEnabled = false;
|
let isChatSupportEnabled = false;
|
||||||
@ -421,7 +422,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
isCloudUserVal,
|
isCloudUserVal,
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -523,14 +524,14 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
|
|
||||||
const renderWorkspaceRestrictedBanner = (): JSX.Element => (
|
const renderWorkspaceRestrictedBanner = (): JSX.Element => (
|
||||||
<div className="workspace-restricted-banner">
|
<div className="workspace-restricted-banner">
|
||||||
{activeLicenseV3?.state === LicenseState.TERMINATED && (
|
{activeLicense?.state === LicenseState.TERMINATED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is terminated, enterprise features have been disabled.
|
Your SigNoz license is terminated, enterprise features have been disabled.
|
||||||
Please contact support at{' '}
|
Please contact support at{' '}
|
||||||
<a href="mailto:support@signoz.io">support@signoz.io</a> for new license
|
<a href="mailto:support@signoz.io">support@signoz.io</a> for new license
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeLicenseV3?.state === LicenseState.EXPIRED && (
|
{activeLicense?.state === LicenseState.EXPIRED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license has expired. Please contact support at{' '}
|
Your SigNoz license has expired. Please contact support at{' '}
|
||||||
<a href="mailto:support@signoz.io">support@signoz.io</a> for renewal to
|
<a href="mailto:support@signoz.io">support@signoz.io</a> for renewal to
|
||||||
@ -544,7 +545,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeLicenseV3?.state === LicenseState.CANCELLED && (
|
{activeLicense?.state === LicenseState.CANCELLED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is cancelled. Please contact support at{' '}
|
Your SigNoz license is cancelled. Please contact support at{' '}
|
||||||
<a href="mailto:support@signoz.io">support@signoz.io</a> for reactivation
|
<a href="mailto:support@signoz.io">support@signoz.io</a> for reactivation
|
||||||
@ -559,7 +560,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeLicenseV3?.state === LicenseState.DEFAULTED && (
|
{activeLicense?.state === LicenseState.DEFAULTED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is defaulted. Please clear the bill to continue using
|
Your SigNoz license is defaulted. Please clear the bill to continue using
|
||||||
the enterprise features. Contact support at{' '}
|
the enterprise features. Contact support at{' '}
|
||||||
@ -575,7 +576,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{activeLicenseV3?.state === LicenseState.EVALUATION_EXPIRED && (
|
{activeLicense?.state === LicenseState.EVALUATION_EXPIRED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz trial has ended. Please contact support at{' '}
|
Your SigNoz trial has ended. Please contact support at{' '}
|
||||||
<a href="mailto:support@signoz.io">support@signoz.io</a> for next steps to
|
<a href="mailto:support@signoz.io">support@signoz.io</a> for next steps to
|
||||||
@ -624,7 +625,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
|
|||||||
Your bill payment has failed. Your workspace will get suspended on{' '}
|
Your bill payment has failed. Your workspace will get suspended on{' '}
|
||||||
<span>
|
<span>
|
||||||
{getFormattedDateWithMinutes(
|
{getFormattedDateWithMinutes(
|
||||||
dayjs(activeLicenseV3?.event_queue?.scheduled_at).unix() || Date.now(),
|
dayjs(activeLicense?.event_queue?.scheduled_at).unix() || Date.now(),
|
||||||
)}
|
)}
|
||||||
.
|
.
|
||||||
</span>
|
</span>
|
||||||
|
@ -16,10 +16,10 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import { ColumnsType } from 'antd/es/table';
|
import { ColumnsType } from 'antd/es/table';
|
||||||
import updateCreditCardApi from 'api/billing/checkout';
|
|
||||||
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
|
import getUsage, { UsageResponsePayloadProps } from 'api/billing/getUsage';
|
||||||
import manageCreditCardApi from 'api/billing/manage';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import updateCreditCardApi from 'api/v1/checkout/create';
|
||||||
|
import manageCreditCardApi from 'api/v1/portal/create';
|
||||||
import Spinner from 'components/Spinner';
|
import Spinner from 'components/Spinner';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
@ -31,9 +31,8 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMutation, useQuery } from 'react-query';
|
import { useMutation, useQuery } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
import { License } from 'types/api/licenses/def';
|
|
||||||
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
|
||||||
|
|
||||||
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
|
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
|
||||||
@ -126,7 +125,6 @@ export default function BillingContainer(): JSX.Element {
|
|||||||
const daysRemainingStr = t('days_remaining');
|
const daysRemainingStr = t('days_remaining');
|
||||||
const [headerText, setHeaderText] = useState('');
|
const [headerText, setHeaderText] = useState('');
|
||||||
const [billAmount, setBillAmount] = useState(0);
|
const [billAmount, setBillAmount] = useState(0);
|
||||||
const [activeLicense, setActiveLicense] = useState<License | null>(null);
|
|
||||||
const [daysRemaining, setDaysRemaining] = useState(0);
|
const [daysRemaining, setDaysRemaining] = useState(0);
|
||||||
const [isFreeTrial, setIsFreeTrial] = useState(false);
|
const [isFreeTrial, setIsFreeTrial] = useState(false);
|
||||||
const [data, setData] = useState<any[]>([]);
|
const [data, setData] = useState<any[]>([]);
|
||||||
@ -137,11 +135,10 @@ export default function BillingContainer(): JSX.Element {
|
|||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
org,
|
org,
|
||||||
licenses,
|
|
||||||
trialInfo,
|
trialInfo,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
activeLicenseV3FetchError,
|
activeLicenseFetchError,
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
|
|
||||||
@ -216,14 +213,9 @@ export default function BillingContainer(): JSX.Element {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const activeValidLicense =
|
|
||||||
licenses?.licenses?.find((license) => license.isCurrent === true) || null;
|
|
||||||
|
|
||||||
setActiveLicense(activeValidLicense);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isFetchingActiveLicenseV3 &&
|
!isFetchingActiveLicense &&
|
||||||
!activeLicenseV3FetchError &&
|
!activeLicenseFetchError &&
|
||||||
trialInfo?.onTrial
|
trialInfo?.onTrial
|
||||||
) {
|
) {
|
||||||
const remainingDays = getRemainingDays(trialInfo?.trialEnd);
|
const remainingDays = getRemainingDays(trialInfo?.trialEnd);
|
||||||
@ -238,12 +230,11 @@ export default function BillingContainer(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
licenses?.licenses,
|
activeLicense,
|
||||||
activeLicenseV3,
|
|
||||||
trialInfo?.onTrial,
|
trialInfo?.onTrial,
|
||||||
trialInfo?.trialEnd,
|
trialInfo?.trialEnd,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
activeLicenseV3FetchError,
|
activeLicenseFetchError,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const columns: ColumnsType<DataType> = [
|
const columns: ColumnsType<DataType> = [
|
||||||
@ -288,11 +279,11 @@ export default function BillingContainer(): JSX.Element {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleBillingOnSuccess = (
|
const handleBillingOnSuccess = (
|
||||||
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||||
): void => {
|
): void => {
|
||||||
if (data?.payload?.redirectURL) {
|
if (data?.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
|
@ -19,12 +19,12 @@ function DataSourceInfo({
|
|||||||
dataSentToSigNoz: boolean;
|
dataSentToSigNoz: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { activeLicenseV3 } = useAppContext();
|
const { activeLicense } = useAppContext();
|
||||||
|
|
||||||
const notSendingData = !dataSentToSigNoz;
|
const notSendingData = !dataSentToSigNoz;
|
||||||
|
|
||||||
const isEnabled =
|
const isEnabled =
|
||||||
activeLicenseV3 && activeLicenseV3.platform === LicensePlatform.CLOUD;
|
activeLicense && activeLicense.platform === LicensePlatform.CLOUD;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: deploymentsData,
|
data: deploymentsData,
|
||||||
@ -88,8 +88,8 @@ function DataSourceInfo({
|
|||||||
logEvent('Homepage: Connect dataSource clicked', {});
|
logEvent('Homepage: Connect dataSource clicked', {});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||||
} else {
|
} else {
|
||||||
@ -105,8 +105,8 @@ function DataSourceInfo({
|
|||||||
logEvent('Homepage: Connect dataSource clicked', {});
|
logEvent('Homepage: Connect dataSource clicked', {});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||||
} else {
|
} else {
|
||||||
|
@ -300,17 +300,17 @@ export default function Home(): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [hostData, k8sPodsData, handleUpdateChecklistDoneItem]);
|
}, [hostData, k8sPodsData, handleUpdateChecklistDoneItem]);
|
||||||
|
|
||||||
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
|
const { activeLicense, isFetchingActiveLicense } = useAppContext();
|
||||||
|
|
||||||
const [isEnabled, setIsEnabled] = useState(false);
|
const [isEnabled, setIsEnabled] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFetchingActiveLicenseV3) {
|
if (isFetchingActiveLicense) {
|
||||||
setIsEnabled(false);
|
setIsEnabled(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsEnabled(Boolean(activeLicenseV3?.platform === LicensePlatform.CLOUD));
|
setIsEnabled(Boolean(activeLicense?.platform === LicensePlatform.CLOUD));
|
||||||
}, [activeLicenseV3, isFetchingActiveLicenseV3]);
|
}, [activeLicense, isFetchingActiveLicense]);
|
||||||
|
|
||||||
const { data: deploymentsData } = useGetDeploymentsData(isEnabled);
|
const { data: deploymentsData } = useGetDeploymentsData(isEnabled);
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ function HomeChecklist({
|
|||||||
onSkip: (item: ChecklistItem) => void;
|
onSkip: (item: ChecklistItem) => void;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
}): JSX.Element {
|
}): JSX.Element {
|
||||||
const { user, activeLicenseV3 } = useAppContext();
|
const { user, activeLicense } = useAppContext();
|
||||||
|
|
||||||
const [completedChecklistItems, setCompletedChecklistItems] = useState<
|
const [completedChecklistItems, setCompletedChecklistItems] = useState<
|
||||||
ChecklistItem[]
|
ChecklistItem[]
|
||||||
@ -94,8 +94,8 @@ function HomeChecklist({
|
|||||||
if (item.toRoute !== ROUTES.GET_STARTED_WITH_CLOUD) {
|
if (item.toRoute !== ROUTES.GET_STARTED_WITH_CLOUD) {
|
||||||
history.push(item.toRoute || '');
|
history.push(item.toRoute || '');
|
||||||
} else if (
|
} else if (
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
history.push(item.toRoute || '');
|
history.push(item.toRoute || '');
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,7 +23,7 @@ import { Link } from 'react-router-dom';
|
|||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import {
|
import {
|
||||||
LicensePlatform,
|
LicensePlatform,
|
||||||
LicenseV3ResModel,
|
LicenseResModel,
|
||||||
} from 'types/api/licensesV3/getActive';
|
} from 'types/api/licensesV3/getActive';
|
||||||
import { ServicesList } from 'types/api/metrics/getService';
|
import { ServicesList } from 'types/api/metrics/getService';
|
||||||
import { GlobalReducer } from 'types/reducer/globalTime';
|
import { GlobalReducer } from 'types/reducer/globalTime';
|
||||||
@ -42,7 +42,7 @@ const EmptyState = memo(
|
|||||||
activeLicenseV3,
|
activeLicenseV3,
|
||||||
}: {
|
}: {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
activeLicenseV3: LicenseV3ResModel | null;
|
activeLicenseV3: LicenseResModel | null;
|
||||||
}): JSX.Element => (
|
}): JSX.Element => (
|
||||||
<div className="empty-state-container">
|
<div className="empty-state-container">
|
||||||
<div className="empty-state-content-container">
|
<div className="empty-state-content-container">
|
||||||
@ -146,7 +146,7 @@ function ServiceMetrics({
|
|||||||
GlobalReducer
|
GlobalReducer
|
||||||
>((state) => state.globalTime);
|
>((state) => state.globalTime);
|
||||||
|
|
||||||
const { user, activeLicenseV3 } = useAppContext();
|
const { user, activeLicense } = useAppContext();
|
||||||
|
|
||||||
const [timeRange, setTimeRange] = useState(() => {
|
const [timeRange, setTimeRange] = useState(() => {
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
@ -335,7 +335,7 @@ function ServiceMetrics({
|
|||||||
{servicesExist ? (
|
{servicesExist ? (
|
||||||
<ServicesListTable services={top5Services} onRowClick={handleRowClick} />
|
<ServicesListTable services={top5Services} onRowClick={handleRowClick} />
|
||||||
) : (
|
) : (
|
||||||
<EmptyState user={user} activeLicenseV3={activeLicenseV3} />
|
<EmptyState user={user} activeLicenseV3={activeLicense} />
|
||||||
)}
|
)}
|
||||||
</Card.Content>
|
</Card.Content>
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ export default function ServiceTraces({
|
|||||||
(state) => state.globalTime,
|
(state) => state.globalTime,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { user, activeLicenseV3 } = useAppContext();
|
const { user, activeLicense } = useAppContext();
|
||||||
|
|
||||||
const now = new Date().getTime();
|
const now = new Date().getTime();
|
||||||
const [timeRange, setTimeRange] = useState({
|
const [timeRange, setTimeRange] = useState({
|
||||||
@ -124,8 +124,8 @@ export default function ServiceTraces({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeLicenseV3 &&
|
activeLicense &&
|
||||||
activeLicenseV3.platform === LicensePlatform.CLOUD
|
activeLicense.platform === LicensePlatform.CLOUD
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
history.push(ROUTES.GET_STARTED_WITH_CLOUD);
|
||||||
} else {
|
} else {
|
||||||
@ -160,7 +160,7 @@ export default function ServiceTraces({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
[user?.role, activeLicenseV3],
|
[user?.role, activeLicense],
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderDashboardsList = useCallback(
|
const renderDashboardsList = useCallback(
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { Button, Form, Input } from 'antd';
|
import { Button, Form, Input } from 'antd';
|
||||||
import apply from 'api/licenses/apply';
|
import apply from 'api/v3/licenses/put';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -36,27 +37,18 @@ function ApplyLicenseForm({
|
|||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await apply({
|
await apply({
|
||||||
key: params.key,
|
key: params.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.statusCode === 200) {
|
|
||||||
await Promise.all([licenseRefetch()]);
|
await Promise.all([licenseRefetch()]);
|
||||||
|
|
||||||
notifications.success({
|
notifications.success({
|
||||||
message: 'Success',
|
message: 'Success',
|
||||||
description: t('license_applied'),
|
description: t('license_applied'),
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
notifications.error({
|
|
||||||
message: 'Error',
|
|
||||||
description: response.error || t('unexpected_error'),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: 'Error',
|
message: (e as APIError).getErrorCode(),
|
||||||
description: t('unexpected_error'),
|
description: (e as APIError).getErrorMessage(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
import { Typography } from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
|
||||||
import { ResizeTable } from 'components/ResizeTable';
|
|
||||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
|
||||||
import { useTimezone } from 'providers/Timezone';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { License } from 'types/api/licenses/def';
|
|
||||||
|
|
||||||
function ValidityColumn({ value }: { value: string }): JSX.Element {
|
|
||||||
const { formatTimezoneAdjustedTimestamp } = useTimezone();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Typography>
|
|
||||||
{formatTimezoneAdjustedTimestamp(value, DATE_TIME_FORMATS.ISO_DATETIME_UTC)}
|
|
||||||
</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function ListLicenses({ licenses }: ListLicensesProps): JSX.Element {
|
|
||||||
const { t } = useTranslation(['licenses']);
|
|
||||||
|
|
||||||
const columns: ColumnsType<License> = [
|
|
||||||
{
|
|
||||||
title: t('column_license_status'),
|
|
||||||
dataIndex: 'status',
|
|
||||||
key: 'status',
|
|
||||||
width: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('column_license_key'),
|
|
||||||
dataIndex: 'key',
|
|
||||||
key: 'key',
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('column_valid_from'),
|
|
||||||
dataIndex: 'ValidFrom',
|
|
||||||
key: 'valid from',
|
|
||||||
render: (value: string): JSX.Element => ValidityColumn({ value }),
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: t('column_valid_until'),
|
|
||||||
dataIndex: 'ValidUntil',
|
|
||||||
key: 'valid until',
|
|
||||||
render: (value: string): JSX.Element => ValidityColumn({ value }),
|
|
||||||
width: 80,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return <ResizeTable columns={columns} rowKey="id" dataSource={licenses} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ListLicensesProps {
|
|
||||||
licenses: License[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ListLicenses;
|
|
@ -4,29 +4,20 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import ApplyLicenseForm from './ApplyLicenseForm';
|
import ApplyLicenseForm from './ApplyLicenseForm';
|
||||||
import ListLicenses from './ListLicenses';
|
|
||||||
|
|
||||||
function Licenses(): JSX.Element {
|
function Licenses(): JSX.Element {
|
||||||
const { t, ready: translationsReady } = useTranslation(['licenses']);
|
const { t, ready: translationsReady } = useTranslation(['licenses']);
|
||||||
const { licenses, licensesRefetch } = useAppContext();
|
const { activeLicenseRefetch } = useAppContext();
|
||||||
|
|
||||||
if (!translationsReady) {
|
if (!translationsReady) {
|
||||||
return <Spinner tip={t('loading_licenses')} height="90vh" />;
|
return <Spinner tip={t('loading_licenses')} height="90vh" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allValidLicense =
|
|
||||||
licenses?.licenses?.filter((license) => license.isCurrent) || [];
|
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
label: t('tab_current_license'),
|
label: t('tab_current_license'),
|
||||||
key: 'licenses',
|
key: 'licenses',
|
||||||
children: <ApplyLicenseForm licenseRefetch={licensesRefetch} />,
|
children: <ApplyLicenseForm licenseRefetch={activeLicenseRefetch} />,
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('tab_license_history'),
|
|
||||||
key: 'history',
|
|
||||||
children: <ListLicenses licenses={allValidLicense} />,
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ function ServiceMetricTable({
|
|||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const { t: getText } = useTranslation(['services']);
|
const { t: getText } = useTranslation(['services']);
|
||||||
|
|
||||||
const { isFetchingActiveLicenseV3, trialInfo } = useAppContext();
|
const { isFetchingActiveLicense, trialInfo } = useAppContext();
|
||||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||||
|
|
||||||
const queries = useGetQueriesRange(queryRangeRequestData, ENTITY_VERSION_V4, {
|
const queries = useGetQueriesRange(queryRangeRequestData, ENTITY_VERSION_V4, {
|
||||||
@ -70,7 +70,7 @@ function ServiceMetricTable({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFetchingActiveLicenseV3 &&
|
!isFetchingActiveLicense &&
|
||||||
trialInfo?.onTrial &&
|
trialInfo?.onTrial &&
|
||||||
!trialInfo?.trialConvertedToSubscription &&
|
!trialInfo?.trialConvertedToSubscription &&
|
||||||
isCloudUserVal
|
isCloudUserVal
|
||||||
@ -85,7 +85,7 @@ function ServiceMetricTable({
|
|||||||
}, [
|
}, [
|
||||||
services,
|
services,
|
||||||
isCloudUserVal,
|
isCloudUserVal,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo?.onTrial,
|
trialInfo?.onTrial,
|
||||||
trialInfo?.trialConvertedToSubscription,
|
trialInfo?.trialConvertedToSubscription,
|
||||||
]);
|
]);
|
||||||
|
@ -21,13 +21,13 @@ function ServiceTraceTable({
|
|||||||
const [RPS, setRPS] = useState(0);
|
const [RPS, setRPS] = useState(0);
|
||||||
const { t: getText } = useTranslation(['services']);
|
const { t: getText } = useTranslation(['services']);
|
||||||
|
|
||||||
const { isFetchingActiveLicenseV3, trialInfo } = useAppContext();
|
const { isFetchingActiveLicense, trialInfo } = useAppContext();
|
||||||
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
const { isCloudUser: isCloudUserVal } = useGetTenantLicense();
|
||||||
const tableColumns = useMemo(() => getColumns(search, false), [search]);
|
const tableColumns = useMemo(() => getColumns(search, false), [search]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
!isFetchingActiveLicenseV3 &&
|
!isFetchingActiveLicense &&
|
||||||
trialInfo?.onTrial &&
|
trialInfo?.onTrial &&
|
||||||
!trialInfo?.trialConvertedToSubscription &&
|
!trialInfo?.trialConvertedToSubscription &&
|
||||||
isCloudUserVal
|
isCloudUserVal
|
||||||
@ -42,7 +42,7 @@ function ServiceTraceTable({
|
|||||||
}, [
|
}, [
|
||||||
services,
|
services,
|
||||||
isCloudUserVal,
|
isCloudUserVal,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo?.onTrial,
|
trialInfo?.onTrial,
|
||||||
trialInfo?.trialConvertedToSubscription,
|
trialInfo?.trialConvertedToSubscription,
|
||||||
]);
|
]);
|
||||||
|
@ -12,7 +12,7 @@ import { GlobalShortcuts } from 'constants/shortcuts/globalShortcuts';
|
|||||||
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
import { useKeyboardHotkeys } from 'hooks/hotkeys/useKeyboardHotkeys';
|
||||||
import useComponentPermission from 'hooks/useComponentPermission';
|
import useComponentPermission from 'hooks/useComponentPermission';
|
||||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||||
import { LICENSE_PLAN_KEY, LICENSE_PLAN_STATUS } from 'hooks/useLicense';
|
import { StatusCodes } from 'http-status-codes';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
@ -26,7 +26,6 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
import { AppState } from 'store/reducers';
|
import { AppState } from 'store/reducers';
|
||||||
import { License } from 'types/api/licenses/def';
|
|
||||||
import AppReducer from 'types/reducer/app';
|
import AppReducer from 'types/reducer/app';
|
||||||
import { USER_ROLES } from 'types/roles';
|
import { USER_ROLES } from 'types/roles';
|
||||||
import { checkVersionState } from 'utils/app';
|
import { checkVersionState } from 'utils/app';
|
||||||
@ -59,7 +58,13 @@ function SideNav(): JSX.Element {
|
|||||||
AppReducer
|
AppReducer
|
||||||
>((state) => state.app);
|
>((state) => state.app);
|
||||||
|
|
||||||
const { user, featureFlags, licenses, trialInfo } = useAppContext();
|
const {
|
||||||
|
user,
|
||||||
|
featureFlags,
|
||||||
|
trialInfo,
|
||||||
|
activeLicense,
|
||||||
|
activeLicenseFetchError,
|
||||||
|
} = useAppContext();
|
||||||
|
|
||||||
const isOnboardingV3Enabled = featureFlags?.find(
|
const isOnboardingV3Enabled = featureFlags?.find(
|
||||||
(flag) => flag.name === FeatureKeys.ONBOARDING_V3,
|
(flag) => flag.name === FeatureKeys.ONBOARDING_V3,
|
||||||
@ -96,14 +101,11 @@ function SideNav(): JSX.Element {
|
|||||||
|
|
||||||
const { t } = useTranslation('');
|
const { t } = useTranslation('');
|
||||||
|
|
||||||
const licenseStatus: string =
|
const licenseStatus: string = activeLicense?.status || '';
|
||||||
licenses?.licenses?.find((e: License) => e.isCurrent)?.status || '';
|
|
||||||
|
|
||||||
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
|
const isWorkspaceBlocked = trialInfo?.workSpaceBlock || false;
|
||||||
|
|
||||||
const isLicenseActive =
|
const isLicenseActive = licenseStatus === 'VALID';
|
||||||
licenseStatus?.toLocaleLowerCase() ===
|
|
||||||
LICENSE_PLAN_STATUS.VALID.toLocaleLowerCase();
|
|
||||||
|
|
||||||
const onClickSignozCloud = (): void => {
|
const onClickSignozCloud = (): void => {
|
||||||
window.open(
|
window.open(
|
||||||
@ -299,10 +301,10 @@ function SideNav(): JSX.Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isOnBasicPlan =
|
const isOnBasicPlan =
|
||||||
licenses?.licenses?.some(
|
activeLicenseFetchError &&
|
||||||
(license: License) =>
|
[StatusCodes.NOT_FOUND, StatusCodes.NOT_IMPLEMENTED].includes(
|
||||||
license.isCurrent && license.planKey === LICENSE_PLAN_KEY.BASIC_PLAN,
|
activeLicenseFetchError?.getHttpStatusCode(),
|
||||||
) || licenses?.licenses === null;
|
);
|
||||||
|
|
||||||
if (user.role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
if (user.role !== USER_ROLES.ADMIN || isOnBasicPlan) {
|
||||||
updatedMenuItems = updatedMenuItems.filter(
|
updatedMenuItems = updatedMenuItems.filter(
|
||||||
@ -347,10 +349,10 @@ function SideNav(): JSX.Element {
|
|||||||
isEnterpriseSelfHostedUser,
|
isEnterpriseSelfHostedUser,
|
||||||
isCurrentVersionError,
|
isCurrentVersionError,
|
||||||
isLatestVersion,
|
isLatestVersion,
|
||||||
licenses?.licenses,
|
|
||||||
onClickVersionHandler,
|
onClickVersionHandler,
|
||||||
t,
|
t,
|
||||||
user.role,
|
user.role,
|
||||||
|
activeLicenseFetchError,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -443,7 +445,7 @@ function SideNav(): JSX.Element {
|
|||||||
onClick={onClickShortcuts}
|
onClick={onClickShortcuts}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{licenses && !isLicenseActive && (
|
{!isLicenseActive && (
|
||||||
<NavItem
|
<NavItem
|
||||||
key="trySignozCloud"
|
key="trySignozCloud"
|
||||||
item={trySignozCloudMenuItem}
|
item={trySignozCloudMenuItem}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import getActive from 'api/licensesV3/getActive';
|
import getActive from 'api/v3/licenses/active/get';
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
import { useQuery, UseQueryResult } from 'react-query';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { LicenseV3ResModel } from 'types/api/licensesV3/getActive';
|
import APIError from 'types/api/error';
|
||||||
|
import { LicenseResModel } from 'types/api/licensesV3/getActive';
|
||||||
|
|
||||||
const useActiveLicenseV3 = (isLoggedIn: boolean): UseLicense =>
|
const useActiveLicenseV3 = (isLoggedIn: boolean): UseLicense =>
|
||||||
useQuery({
|
useQuery({
|
||||||
@ -11,9 +12,6 @@ const useActiveLicenseV3 = (isLoggedIn: boolean): UseLicense =>
|
|||||||
enabled: !!isLoggedIn,
|
enabled: !!isLoggedIn,
|
||||||
});
|
});
|
||||||
|
|
||||||
type UseLicense = UseQueryResult<
|
type UseLicense = UseQueryResult<SuccessResponseV2<LicenseResModel>, APIError>;
|
||||||
SuccessResponse<LicenseV3ResModel> | ErrorResponse,
|
|
||||||
unknown
|
|
||||||
>;
|
|
||||||
|
|
||||||
export default useActiveLicenseV3;
|
export default useActiveLicenseV3;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { AxiosError } from 'axios';
|
|
||||||
import { useAppContext } from 'providers/App/App';
|
import { useAppContext } from 'providers/App/App';
|
||||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||||
|
|
||||||
@ -8,26 +7,26 @@ export const useGetTenantLicense = (): {
|
|||||||
isCommunityUser: boolean;
|
isCommunityUser: boolean;
|
||||||
isCommunityEnterpriseUser: boolean;
|
isCommunityEnterpriseUser: boolean;
|
||||||
} => {
|
} => {
|
||||||
const { activeLicenseV3, activeLicenseV3FetchError } = useAppContext();
|
const { activeLicense, activeLicenseFetchError } = useAppContext();
|
||||||
|
|
||||||
const responsePayload = {
|
const responsePayload = {
|
||||||
isCloudUser: activeLicenseV3?.platform === LicensePlatform.CLOUD || false,
|
isCloudUser: activeLicense?.platform === LicensePlatform.CLOUD || false,
|
||||||
isEnterpriseSelfHostedUser:
|
isEnterpriseSelfHostedUser:
|
||||||
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED || false,
|
activeLicense?.platform === LicensePlatform.SELF_HOSTED || false,
|
||||||
isCommunityUser: false,
|
isCommunityUser: false,
|
||||||
isCommunityEnterpriseUser: false,
|
isCommunityEnterpriseUser: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeLicenseV3FetchError &&
|
activeLicenseFetchError &&
|
||||||
(activeLicenseV3FetchError as AxiosError)?.response?.status === 404
|
activeLicenseFetchError.getHttpStatusCode() === 404
|
||||||
) {
|
) {
|
||||||
responsePayload.isCommunityEnterpriseUser = true;
|
responsePayload.isCommunityEnterpriseUser = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeLicenseV3FetchError &&
|
activeLicenseFetchError &&
|
||||||
(activeLicenseV3FetchError as AxiosError)?.response?.status === 501
|
activeLicenseFetchError.getHttpStatusCode() === 501
|
||||||
) {
|
) {
|
||||||
responsePayload.isCommunityUser = true;
|
responsePayload.isCommunityUser = true;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export const LICENSE_PLAN_KEY = {
|
|
||||||
ENTERPRISE_PLAN: 'ENTERPRISE_PLAN',
|
|
||||||
BASIC_PLAN: 'BASIC_PLAN',
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LICENSE_PLAN_STATUS = {
|
|
||||||
VALID: 'VALID',
|
|
||||||
};
|
|
@ -1,6 +0,0 @@
|
|||||||
import { LICENSE_PLAN_KEY, LICENSE_PLAN_STATUS } from './constant';
|
|
||||||
import useLicense from './useLicense';
|
|
||||||
|
|
||||||
export default useLicense;
|
|
||||||
|
|
||||||
export { LICENSE_PLAN_KEY, LICENSE_PLAN_STATUS };
|
|
@ -1,20 +0,0 @@
|
|||||||
import getAll from 'api/licenses/getAll';
|
|
||||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
|
||||||
// import { useAppContext } from 'providers/App/App';
|
|
||||||
import { useQuery, UseQueryResult } from 'react-query';
|
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
|
||||||
import { PayloadProps } from 'types/api/licenses/getAll';
|
|
||||||
|
|
||||||
const useLicense = (isLoggedIn: boolean): UseLicense =>
|
|
||||||
useQuery({
|
|
||||||
queryFn: getAll,
|
|
||||||
queryKey: [REACT_QUERY_KEY.GET_ALL_LICENCES],
|
|
||||||
enabled: !!isLoggedIn,
|
|
||||||
});
|
|
||||||
|
|
||||||
type UseLicense = UseQueryResult<
|
|
||||||
SuccessResponse<PayloadProps> | ErrorResponse,
|
|
||||||
unknown
|
|
||||||
>;
|
|
||||||
|
|
||||||
export default useLicense;
|
|
@ -1,9 +1,8 @@
|
|||||||
import './Support.styles.scss';
|
import './Support.styles.scss';
|
||||||
|
|
||||||
import { Button, Card, Modal, Typography } from 'antd';
|
import { Button, Card, Modal, Typography } from 'antd';
|
||||||
import updateCreditCardApi from 'api/billing/checkout';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
import updateCreditCardApi from 'api/v1/checkout/create';
|
||||||
import { FeatureKeys } from 'constants/features';
|
import { FeatureKeys } from 'constants/features';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import {
|
import {
|
||||||
@ -18,8 +17,9 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
import { useHistory, useLocation } from 'react-router-dom';
|
import { useHistory, useLocation } from 'react-router-dom';
|
||||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
import { SuccessResponseV2 } from 'types/api';
|
||||||
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
import { CheckoutSuccessPayloadProps } from 'types/api/billing/checkout';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
|
|
||||||
const { Title, Text } = Typography;
|
const { Title, Text } = Typography;
|
||||||
|
|
||||||
@ -109,20 +109,21 @@ export default function Support(): JSX.Element {
|
|||||||
!isPremiumChatSupportEnabled && !trialInfo?.trialConvertedToSubscription;
|
!isPremiumChatSupportEnabled && !trialInfo?.trialConvertedToSubscription;
|
||||||
|
|
||||||
const handleBillingOnSuccess = (
|
const handleBillingOnSuccess = (
|
||||||
data: ErrorResponse | SuccessResponse<CheckoutSuccessPayloadProps, unknown>,
|
data: SuccessResponseV2<CheckoutSuccessPayloadProps>,
|
||||||
): void => {
|
): void => {
|
||||||
if (data?.payload?.redirectURL) {
|
if (data?.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBillingOnError = (): void => {
|
const handleBillingOnError = (error: APIError): void => {
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: SOMETHING_WENT_WRONG,
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -8,24 +8,24 @@ import { useEffect } from 'react';
|
|||||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||||
|
|
||||||
function WorkspaceAccessRestricted(): JSX.Element {
|
function WorkspaceAccessRestricted(): JSX.Element {
|
||||||
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
|
const { activeLicense, isFetchingActiveLicense } = useAppContext();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3) {
|
if (!isFetchingActiveLicense) {
|
||||||
const isTerminated = activeLicenseV3?.state === LicenseState.TERMINATED;
|
const isTerminated = activeLicense?.state === LicenseState.TERMINATED;
|
||||||
const isExpired = activeLicenseV3?.state === LicenseState.EXPIRED;
|
const isExpired = activeLicense?.state === LicenseState.EXPIRED;
|
||||||
const isCancelled = activeLicenseV3?.state === LicenseState.CANCELLED;
|
const isCancelled = activeLicense?.state === LicenseState.CANCELLED;
|
||||||
|
|
||||||
const isWorkspaceAccessRestricted = isTerminated || isExpired || isCancelled;
|
const isWorkspaceAccessRestricted = isTerminated || isExpired || isCancelled;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isWorkspaceAccessRestricted ||
|
!isWorkspaceAccessRestricted ||
|
||||||
activeLicenseV3.platform === LicensePlatform.SELF_HOSTED
|
activeLicense.platform === LicensePlatform.SELF_HOSTED
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.HOME);
|
history.push(ROUTES.HOME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
}, [isFetchingActiveLicense, activeLicense]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@ -44,7 +44,7 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
|||||||
width="65%"
|
width="65%"
|
||||||
>
|
>
|
||||||
<div className="workspace-access-restricted__container">
|
<div className="workspace-access-restricted__container">
|
||||||
{isFetchingActiveLicenseV3 || !activeLicenseV3 ? (
|
{isFetchingActiveLicense || !activeLicense ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -55,7 +55,7 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
|||||||
level={4}
|
level={4}
|
||||||
className="workspace-access-restricted__details"
|
className="workspace-access-restricted__details"
|
||||||
>
|
>
|
||||||
{activeLicenseV3.state === LicenseState.TERMINATED && (
|
{activeLicense.state === LicenseState.TERMINATED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is terminated, please contact support at{' '}
|
Your SigNoz license is terminated, please contact support at{' '}
|
||||||
<a href="mailto:cloud-support@signoz.io">
|
<a href="mailto:cloud-support@signoz.io">
|
||||||
@ -64,7 +64,7 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
|||||||
for a new deployment
|
for a new deployment
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeLicenseV3.state === LicenseState.EXPIRED && (
|
{activeLicense.state === LicenseState.EXPIRED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is expired, please contact support at{' '}
|
Your SigNoz license is expired, please contact support at{' '}
|
||||||
<a href="mailto:cloud-support@signoz.io">
|
<a href="mailto:cloud-support@signoz.io">
|
||||||
@ -81,7 +81,7 @@ function WorkspaceAccessRestricted(): JSX.Element {
|
|||||||
.
|
.
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeLicenseV3.state === LicenseState.CANCELLED && (
|
{activeLicense.state === LicenseState.CANCELLED && (
|
||||||
<>
|
<>
|
||||||
Your SigNoz license is cancelled, please contact support at{' '}
|
Your SigNoz license is cancelled, please contact support at{' '}
|
||||||
<a href="mailto:cloud-support@signoz.io">
|
<a href="mailto:cloud-support@signoz.io">
|
||||||
|
@ -16,8 +16,8 @@ import {
|
|||||||
Tabs,
|
Tabs,
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import updateCreditCardApi from 'api/billing/checkout';
|
|
||||||
import logEvent from 'api/common/logEvent';
|
import logEvent from 'api/common/logEvent';
|
||||||
|
import updateCreditCardApi from 'api/v1/checkout/create';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
import history from 'lib/history';
|
import history from 'lib/history';
|
||||||
@ -26,6 +26,7 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
import { LicensePlatform } from 'types/api/licensesV3/getActive';
|
||||||
import { getFormattedDate } from 'utils/timeUtils';
|
import { getFormattedDate } from 'utils/timeUtils';
|
||||||
|
|
||||||
@ -41,9 +42,9 @@ import {
|
|||||||
export default function WorkspaceBlocked(): JSX.Element {
|
export default function WorkspaceBlocked(): JSX.Element {
|
||||||
const {
|
const {
|
||||||
user,
|
user,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
} = useAppContext();
|
} = useAppContext();
|
||||||
const isAdmin = user.role === 'ADMIN';
|
const isAdmin = user.role === 'ADMIN';
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
@ -70,37 +71,38 @@ export default function WorkspaceBlocked(): JSX.Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3) {
|
if (!isFetchingActiveLicense) {
|
||||||
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
const shouldBlockWorkspace = trialInfo?.workSpaceBlock;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!shouldBlockWorkspace ||
|
!shouldBlockWorkspace ||
|
||||||
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED
|
activeLicense?.platform === LicensePlatform.SELF_HOSTED
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.HOME);
|
history.push(ROUTES.HOME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
trialInfo?.workSpaceBlock,
|
trialInfo?.workSpaceBlock,
|
||||||
activeLicenseV3?.platform,
|
activeLicense?.platform,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { mutate: updateCreditCard, isLoading } = useMutation(
|
const { mutate: updateCreditCard, isLoading } = useMutation(
|
||||||
updateCreditCardApi,
|
updateCreditCardApi,
|
||||||
{
|
{
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.payload?.redirectURL) {
|
if (data.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () =>
|
onError: (error: APIError) =>
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: t('somethingWentWrong'),
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -320,7 +322,7 @@ export default function WorkspaceBlocked(): JSX.Element {
|
|||||||
width="65%"
|
width="65%"
|
||||||
>
|
>
|
||||||
<div className="workspace-locked__container">
|
<div className="workspace-locked__container">
|
||||||
{isFetchingActiveLicenseV3 || !trialInfo ? (
|
{isFetchingActiveLicense || !trialInfo ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
Space,
|
Space,
|
||||||
Typography,
|
Typography,
|
||||||
} from 'antd';
|
} from 'antd';
|
||||||
import manageCreditCardApi from 'api/billing/manage';
|
import manageCreditCardApi from 'api/v1/portal/create';
|
||||||
import ROUTES from 'constants/routes';
|
import ROUTES from 'constants/routes';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useNotifications } from 'hooks/useNotifications';
|
import { useNotifications } from 'hooks/useNotifications';
|
||||||
@ -19,6 +19,7 @@ import { useAppContext } from 'providers/App/App';
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useMutation } from 'react-query';
|
import { useMutation } from 'react-query';
|
||||||
|
import APIError from 'types/api/error';
|
||||||
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
import { LicensePlatform, LicenseState } from 'types/api/licensesV3/getActive';
|
||||||
import { getFormattedDateWithMinutes } from 'utils/timeUtils';
|
import { getFormattedDateWithMinutes } from 'utils/timeUtils';
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ function WorkspaceSuspended(): JSX.Element {
|
|||||||
const { user } = useAppContext();
|
const { user } = useAppContext();
|
||||||
const isAdmin = user.role === 'ADMIN';
|
const isAdmin = user.role === 'ADMIN';
|
||||||
const { notifications } = useNotifications();
|
const { notifications } = useNotifications();
|
||||||
const { activeLicenseV3, isFetchingActiveLicenseV3 } = useAppContext();
|
const { activeLicense, isFetchingActiveLicense } = useAppContext();
|
||||||
|
|
||||||
const { t } = useTranslation(['failedPayment']);
|
const { t } = useTranslation(['failedPayment']);
|
||||||
|
|
||||||
@ -34,17 +35,18 @@ function WorkspaceSuspended(): JSX.Element {
|
|||||||
manageCreditCardApi,
|
manageCreditCardApi,
|
||||||
{
|
{
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data.payload?.redirectURL) {
|
if (data.data?.redirectURL) {
|
||||||
const newTab = document.createElement('a');
|
const newTab = document.createElement('a');
|
||||||
newTab.href = data.payload.redirectURL;
|
newTab.href = data.data.redirectURL;
|
||||||
newTab.target = '_blank';
|
newTab.target = '_blank';
|
||||||
newTab.rel = 'noopener noreferrer';
|
newTab.rel = 'noopener noreferrer';
|
||||||
newTab.click();
|
newTab.click();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () =>
|
onError: (error: APIError) =>
|
||||||
notifications.error({
|
notifications.error({
|
||||||
message: t('somethingWentWrong'),
|
message: error.getErrorCode(),
|
||||||
|
description: error.getErrorMessage(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -56,18 +58,18 @@ function WorkspaceSuspended(): JSX.Element {
|
|||||||
}, [manageCreditCard]);
|
}, [manageCreditCard]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isFetchingActiveLicenseV3) {
|
if (!isFetchingActiveLicense) {
|
||||||
const shouldSuspendWorkspace =
|
const shouldSuspendWorkspace =
|
||||||
activeLicenseV3?.state === LicenseState.DEFAULTED;
|
activeLicense?.state === LicenseState.DEFAULTED;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!shouldSuspendWorkspace ||
|
!shouldSuspendWorkspace ||
|
||||||
activeLicenseV3?.platform === LicensePlatform.SELF_HOSTED
|
activeLicense?.platform === LicensePlatform.SELF_HOSTED
|
||||||
) {
|
) {
|
||||||
history.push(ROUTES.HOME);
|
history.push(ROUTES.HOME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isFetchingActiveLicenseV3, activeLicenseV3]);
|
}, [isFetchingActiveLicense, activeLicense]);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Modal
|
<Modal
|
||||||
@ -99,7 +101,7 @@ function WorkspaceSuspended(): JSX.Element {
|
|||||||
width="65%"
|
width="65%"
|
||||||
>
|
>
|
||||||
<div className="workspace-suspended__container">
|
<div className="workspace-suspended__container">
|
||||||
{isFetchingActiveLicenseV3 || !activeLicenseV3 ? (
|
{isFetchingActiveLicense || !activeLicense ? (
|
||||||
<Skeleton />
|
<Skeleton />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -115,7 +117,7 @@ function WorkspaceSuspended(): JSX.Element {
|
|||||||
{t('yourDataIsSafe')}{' '}
|
{t('yourDataIsSafe')}{' '}
|
||||||
<span className="workspace-suspended__details__highlight">
|
<span className="workspace-suspended__details__highlight">
|
||||||
{getFormattedDateWithMinutes(
|
{getFormattedDateWithMinutes(
|
||||||
dayjs(activeLicenseV3?.event_queue?.scheduled_at).unix() ||
|
dayjs(activeLicense?.event_queue?.scheduled_at).unix() ||
|
||||||
Date.now(),
|
Date.now(),
|
||||||
)}
|
)}
|
||||||
</span>{' '}
|
</span>{' '}
|
||||||
|
@ -6,7 +6,6 @@ import dayjs from 'dayjs';
|
|||||||
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
import useActiveLicenseV3 from 'hooks/useActiveLicenseV3/useActiveLicenseV3';
|
||||||
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
import useGetFeatureFlag from 'hooks/useGetFeatureFlag';
|
||||||
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
import { useGlobalEventListener } from 'hooks/useGlobalEventListener';
|
||||||
import useLicense from 'hooks/useLicense';
|
|
||||||
import useGetUser from 'hooks/user/useGetUser';
|
import useGetUser from 'hooks/user/useGetUser';
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
@ -19,11 +18,10 @@ import {
|
|||||||
} from 'react';
|
} from 'react';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeaturesFlags';
|
import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeaturesFlags';
|
||||||
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
|
|
||||||
import {
|
import {
|
||||||
LicensePlatform,
|
LicensePlatform,
|
||||||
|
LicenseResModel,
|
||||||
LicenseState,
|
LicenseState,
|
||||||
LicenseV3ResModel,
|
|
||||||
TrialInfo,
|
TrialInfo,
|
||||||
} from 'types/api/licensesV3/getActive';
|
} from 'types/api/licensesV3/getActive';
|
||||||
import { Organization } from 'types/api/user/getOrganization';
|
import { Organization } from 'types/api/user/getOrganization';
|
||||||
@ -38,14 +36,10 @@ export const AppContext = createContext<IAppContext | undefined>(undefined);
|
|||||||
export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
||||||
// on load of the provider set the user defaults with access jwt , refresh jwt and user id from local storage
|
// on load of the provider set the user defaults with access jwt , refresh jwt and user id from local storage
|
||||||
const [user, setUser] = useState<IUser>(() => getUserDefaults());
|
const [user, setUser] = useState<IUser>(() => getUserDefaults());
|
||||||
const [licenses, setLicenses] = useState<LicensesResModel | null>(null);
|
const [activeLicense, setActiveLicense] = useState<LicenseResModel | null>(
|
||||||
const [
|
null,
|
||||||
activeLicenseV3,
|
);
|
||||||
setActiveLicenseV3,
|
|
||||||
] = useState<LicenseV3ResModel | null>(null);
|
|
||||||
|
|
||||||
const [trialInfo, setTrialInfo] = useState<TrialInfo | null>(null);
|
const [trialInfo, setTrialInfo] = useState<TrialInfo | null>(null);
|
||||||
|
|
||||||
const [featureFlags, setFeatureFlags] = useState<FeatureFlags[] | null>(null);
|
const [featureFlags, setFeatureFlags] = useState<FeatureFlags[] | null>(null);
|
||||||
const [orgPreferences, setOrgPreferences] = useState<OrgPreference[] | null>(
|
const [orgPreferences, setOrgPreferences] = useState<OrgPreference[] | null>(
|
||||||
null,
|
null,
|
||||||
@ -103,59 +97,40 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
|||||||
}
|
}
|
||||||
}, [userData, isFetchingUser]);
|
}, [userData, isFetchingUser]);
|
||||||
|
|
||||||
// fetcher for licenses v2
|
|
||||||
// license will be fetched if we are in logged in state
|
|
||||||
const {
|
|
||||||
data: licenseData,
|
|
||||||
isFetching: isFetchingLicenses,
|
|
||||||
error: licensesFetchError,
|
|
||||||
refetch: licensesRefetch,
|
|
||||||
} = useLicense(isLoggedIn);
|
|
||||||
useEffect(() => {
|
|
||||||
if (!isFetchingLicenses && licenseData && licenseData.payload) {
|
|
||||||
setLicenses(licenseData.payload);
|
|
||||||
}
|
|
||||||
}, [licenseData, isFetchingLicenses]);
|
|
||||||
|
|
||||||
// fetcher for licenses v3
|
// fetcher for licenses v3
|
||||||
const {
|
const {
|
||||||
data: activeLicenseV3Data,
|
data: activeLicenseData,
|
||||||
isFetching: isFetchingActiveLicenseV3,
|
isFetching: isFetchingActiveLicense,
|
||||||
error: activeLicenseV3FetchError,
|
error: activeLicenseFetchError,
|
||||||
|
refetch: activeLicenseRefetch,
|
||||||
} = useActiveLicenseV3(isLoggedIn);
|
} = useActiveLicenseV3(isLoggedIn);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (!isFetchingActiveLicense && activeLicenseData && activeLicenseData.data) {
|
||||||
!isFetchingActiveLicenseV3 &&
|
setActiveLicense(activeLicenseData.data);
|
||||||
activeLicenseV3Data &&
|
|
||||||
activeLicenseV3Data.payload
|
|
||||||
) {
|
|
||||||
setActiveLicenseV3(activeLicenseV3Data.payload);
|
|
||||||
|
|
||||||
const isOnTrial = dayjs(
|
const isOnTrial = dayjs(
|
||||||
activeLicenseV3Data.payload.free_until || Date.now(),
|
activeLicenseData.data.free_until || Date.now(),
|
||||||
).isAfter(dayjs());
|
).isAfter(dayjs());
|
||||||
|
|
||||||
const trialInfo: TrialInfo = {
|
const trialInfo: TrialInfo = {
|
||||||
trialStart: activeLicenseV3Data.payload.valid_from,
|
trialStart: activeLicenseData.data.valid_from,
|
||||||
trialEnd: dayjs(
|
trialEnd: dayjs(activeLicenseData.data.free_until || Date.now()).unix(),
|
||||||
activeLicenseV3Data.payload.free_until || Date.now(),
|
|
||||||
).unix(),
|
|
||||||
onTrial: isOnTrial,
|
onTrial: isOnTrial,
|
||||||
workSpaceBlock:
|
workSpaceBlock:
|
||||||
activeLicenseV3Data.payload.state === LicenseState.EVALUATION_EXPIRED &&
|
activeLicenseData.data.state === LicenseState.EVALUATION_EXPIRED &&
|
||||||
activeLicenseV3Data.payload.platform === LicensePlatform.CLOUD,
|
activeLicenseData.data.platform === LicensePlatform.CLOUD,
|
||||||
trialConvertedToSubscription:
|
trialConvertedToSubscription:
|
||||||
activeLicenseV3Data.payload.state !== LicenseState.ISSUED &&
|
activeLicenseData.data.state !== LicenseState.ISSUED &&
|
||||||
activeLicenseV3Data.payload.state !== LicenseState.EVALUATING &&
|
activeLicenseData.data.state !== LicenseState.EVALUATING &&
|
||||||
activeLicenseV3Data.payload.state !== LicenseState.EVALUATION_EXPIRED,
|
activeLicenseData.data.state !== LicenseState.EVALUATION_EXPIRED,
|
||||||
gracePeriodEnd: dayjs(
|
gracePeriodEnd: dayjs(
|
||||||
activeLicenseV3Data.payload.event_queue.scheduled_at || Date.now(),
|
activeLicenseData.data.event_queue.scheduled_at || Date.now(),
|
||||||
).unix(),
|
).unix(),
|
||||||
};
|
};
|
||||||
|
|
||||||
setTrialInfo(trialInfo);
|
setTrialInfo(trialInfo);
|
||||||
}
|
}
|
||||||
}, [activeLicenseV3Data, isFetchingActiveLicenseV3]);
|
}, [activeLicenseData, isFetchingActiveLicense]);
|
||||||
|
|
||||||
// fetcher for feature flags
|
// fetcher for feature flags
|
||||||
const {
|
const {
|
||||||
@ -242,9 +217,8 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
|||||||
useGlobalEventListener('LOGOUT', () => {
|
useGlobalEventListener('LOGOUT', () => {
|
||||||
setIsLoggedIn(false);
|
setIsLoggedIn(false);
|
||||||
setUser(getUserDefaults());
|
setUser(getUserDefaults());
|
||||||
setActiveLicenseV3(null);
|
setActiveLicense(null);
|
||||||
setTrialInfo(null);
|
setTrialInfo(null);
|
||||||
setLicenses(null);
|
|
||||||
setFeatureFlags(null);
|
setFeatureFlags(null);
|
||||||
setOrgPreferences(null);
|
setOrgPreferences(null);
|
||||||
setOrg(null);
|
setOrg(null);
|
||||||
@ -254,46 +228,40 @@ export function AppProvider({ children }: PropsWithChildren): JSX.Element {
|
|||||||
const value: IAppContext = useMemo(
|
const value: IAppContext = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
user,
|
user,
|
||||||
licenses,
|
activeLicense,
|
||||||
activeLicenseV3,
|
|
||||||
featureFlags,
|
featureFlags,
|
||||||
trialInfo,
|
trialInfo,
|
||||||
orgPreferences,
|
orgPreferences,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
org,
|
org,
|
||||||
isFetchingUser,
|
isFetchingUser,
|
||||||
isFetchingLicenses,
|
isFetchingActiveLicense,
|
||||||
isFetchingActiveLicenseV3,
|
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
isFetchingOrgPreferences,
|
isFetchingOrgPreferences,
|
||||||
userFetchError,
|
userFetchError,
|
||||||
licensesFetchError,
|
activeLicenseFetchError,
|
||||||
activeLicenseV3FetchError,
|
|
||||||
featureFlagsFetchError,
|
featureFlagsFetchError,
|
||||||
orgPreferencesFetchError,
|
orgPreferencesFetchError,
|
||||||
licensesRefetch,
|
activeLicenseRefetch,
|
||||||
updateUser,
|
updateUser,
|
||||||
updateOrgPreferences,
|
updateOrgPreferences,
|
||||||
updateOrg,
|
updateOrg,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
trialInfo,
|
trialInfo,
|
||||||
activeLicenseV3,
|
activeLicense,
|
||||||
activeLicenseV3FetchError,
|
activeLicenseFetchError,
|
||||||
featureFlags,
|
featureFlags,
|
||||||
featureFlagsFetchError,
|
featureFlagsFetchError,
|
||||||
isFetchingActiveLicenseV3,
|
isFetchingActiveLicense,
|
||||||
isFetchingFeatureFlags,
|
isFetchingFeatureFlags,
|
||||||
isFetchingLicenses,
|
|
||||||
isFetchingOrgPreferences,
|
isFetchingOrgPreferences,
|
||||||
isFetchingUser,
|
isFetchingUser,
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
licenses,
|
|
||||||
licensesFetchError,
|
|
||||||
licensesRefetch,
|
|
||||||
org,
|
org,
|
||||||
orgPreferences,
|
orgPreferences,
|
||||||
orgPreferencesFetchError,
|
orgPreferencesFetchError,
|
||||||
|
activeLicenseRefetch,
|
||||||
updateOrg,
|
updateOrg,
|
||||||
user,
|
user,
|
||||||
userFetchError,
|
userFetchError,
|
||||||
|
@ -1,30 +1,27 @@
|
|||||||
|
import APIError from 'types/api/error';
|
||||||
import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeaturesFlags';
|
import { FeatureFlagProps as FeatureFlags } from 'types/api/features/getFeaturesFlags';
|
||||||
import { PayloadProps as LicensesResModel } from 'types/api/licenses/getAll';
|
import { LicenseResModel, TrialInfo } from 'types/api/licensesV3/getActive';
|
||||||
import { LicenseV3ResModel, TrialInfo } from 'types/api/licensesV3/getActive';
|
|
||||||
import { Organization } from 'types/api/user/getOrganization';
|
import { Organization } from 'types/api/user/getOrganization';
|
||||||
import { UserResponse as User } from 'types/api/user/getUser';
|
import { UserResponse as User } from 'types/api/user/getUser';
|
||||||
import { OrgPreference } from 'types/reducer/app';
|
import { OrgPreference } from 'types/reducer/app';
|
||||||
|
|
||||||
export interface IAppContext {
|
export interface IAppContext {
|
||||||
user: IUser;
|
user: IUser;
|
||||||
licenses: LicensesResModel | null;
|
activeLicense: LicenseResModel | null;
|
||||||
activeLicenseV3: LicenseV3ResModel | null;
|
|
||||||
trialInfo: TrialInfo | null;
|
trialInfo: TrialInfo | null;
|
||||||
featureFlags: FeatureFlags[] | null;
|
featureFlags: FeatureFlags[] | null;
|
||||||
orgPreferences: OrgPreference[] | null;
|
orgPreferences: OrgPreference[] | null;
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
org: Organization[] | null;
|
org: Organization[] | null;
|
||||||
isFetchingUser: boolean;
|
isFetchingUser: boolean;
|
||||||
isFetchingLicenses: boolean;
|
isFetchingActiveLicense: boolean;
|
||||||
isFetchingActiveLicenseV3: boolean;
|
|
||||||
isFetchingFeatureFlags: boolean;
|
isFetchingFeatureFlags: boolean;
|
||||||
isFetchingOrgPreferences: boolean;
|
isFetchingOrgPreferences: boolean;
|
||||||
userFetchError: unknown;
|
userFetchError: unknown;
|
||||||
licensesFetchError: unknown;
|
activeLicenseFetchError: APIError | null;
|
||||||
activeLicenseV3FetchError: unknown;
|
|
||||||
featureFlagsFetchError: unknown;
|
featureFlagsFetchError: unknown;
|
||||||
orgPreferencesFetchError: unknown;
|
orgPreferencesFetchError: unknown;
|
||||||
licensesRefetch: () => void;
|
activeLicenseRefetch: () => void;
|
||||||
updateUser: (user: IUser) => void;
|
updateUser: (user: IUser) => void;
|
||||||
updateOrgPreferences: (orgPreferences: OrgPreference[]) => void;
|
updateOrgPreferences: (orgPreferences: OrgPreference[]) => void;
|
||||||
updateOrg(orgId: string, updatedOrgName: string): void;
|
updateOrg(orgId: string, updatedOrgName: string): void;
|
||||||
|
@ -105,7 +105,8 @@ export function getAppContextMock(
|
|||||||
appContextOverrides?: Partial<IAppContext>,
|
appContextOverrides?: Partial<IAppContext>,
|
||||||
): IAppContext {
|
): IAppContext {
|
||||||
return {
|
return {
|
||||||
activeLicenseV3: {
|
activeLicense: {
|
||||||
|
key: 'test-key',
|
||||||
event_queue: {
|
event_queue: {
|
||||||
created_at: '0',
|
created_at: '0',
|
||||||
event: LicenseEvent.NO_EVENT,
|
event: LicenseEvent.NO_EVENT,
|
||||||
@ -138,8 +139,8 @@ export function getAppContextMock(
|
|||||||
trialConvertedToSubscription: false,
|
trialConvertedToSubscription: false,
|
||||||
gracePeriodEnd: -1,
|
gracePeriodEnd: -1,
|
||||||
},
|
},
|
||||||
isFetchingActiveLicenseV3: false,
|
isFetchingActiveLicense: false,
|
||||||
activeLicenseV3FetchError: null,
|
activeLicenseFetchError: null,
|
||||||
user: {
|
user: {
|
||||||
accessJwt: 'some-token',
|
accessJwt: 'some-token',
|
||||||
refreshJwt: 'some-refresh-token',
|
refreshJwt: 'some-refresh-token',
|
||||||
@ -160,20 +161,6 @@ export function getAppContextMock(
|
|||||||
],
|
],
|
||||||
isFetchingUser: false,
|
isFetchingUser: false,
|
||||||
userFetchError: null,
|
userFetchError: null,
|
||||||
licenses: {
|
|
||||||
licenses: [
|
|
||||||
{
|
|
||||||
key: 'does-not-matter',
|
|
||||||
isCurrent: true,
|
|
||||||
planKey: 'ENTERPRISE_PLAN',
|
|
||||||
ValidFrom: new Date(),
|
|
||||||
ValidUntil: new Date(),
|
|
||||||
status: 'VALID',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
isFetchingLicenses: false,
|
|
||||||
licensesFetchError: null,
|
|
||||||
featureFlags: [
|
featureFlags: [
|
||||||
{
|
{
|
||||||
name: FeatureKeys.SSO,
|
name: FeatureKeys.SSO,
|
||||||
@ -246,7 +233,7 @@ export function getAppContextMock(
|
|||||||
updateUser: jest.fn(),
|
updateUser: jest.fn(),
|
||||||
updateOrg: jest.fn(),
|
updateOrg: jest.fn(),
|
||||||
updateOrgPreferences: jest.fn(),
|
updateOrgPreferences: jest.fn(),
|
||||||
licensesRefetch: jest.fn(),
|
activeLicenseRefetch: jest.fn(),
|
||||||
...appContextOverrides,
|
...appContextOverrides,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -5,3 +5,8 @@ export interface CheckoutSuccessPayloadProps {
|
|||||||
export interface CheckoutRequestPayloadProps {
|
export interface CheckoutRequestPayloadProps {
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: CheckoutSuccessPayloadProps;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
import { License } from './def';
|
|
||||||
|
|
||||||
export type PayloadProps = {
|
|
||||||
licenses: License[];
|
|
||||||
};
|
|
@ -30,7 +30,7 @@ export const LicensePlanKey = {
|
|||||||
BASIC: 'BASIC',
|
BASIC: 'BASIC',
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LicenseV3EventQueueResModel = {
|
export type LicenseEventQueueResModel = {
|
||||||
event: LicenseEvent;
|
event: LicenseEvent;
|
||||||
status: string;
|
status: string;
|
||||||
scheduled_at: string;
|
scheduled_at: string;
|
||||||
@ -38,10 +38,11 @@ export type LicenseV3EventQueueResModel = {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LicenseV3ResModel = {
|
export type LicenseResModel = {
|
||||||
|
key: string;
|
||||||
status: LicenseStatus;
|
status: LicenseStatus;
|
||||||
state: LicenseState;
|
state: LicenseState;
|
||||||
event_queue: LicenseV3EventQueueResModel;
|
event_queue: LicenseEventQueueResModel;
|
||||||
platform: LicensePlatform;
|
platform: LicensePlatform;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
plan: {
|
plan: {
|
||||||
@ -67,3 +68,8 @@ export type TrialInfo = {
|
|||||||
trialConvertedToSubscription: boolean;
|
trialConvertedToSubscription: boolean;
|
||||||
gracePeriodEnd: number;
|
gracePeriodEnd: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface PayloadProps {
|
||||||
|
data: LicenseEventQueueResModel;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
18
pkg/licensing/config.go
Normal file
18
pkg/licensing/config.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package licensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ factory.Config = (*Config)(nil)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
PollInterval time.Duration `mapstructure:"poll_interval"`
|
||||||
|
FailureThreshold int `mapstructure:"failure_threshold"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Config) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
55
pkg/licensing/licensing.go
Normal file
55
pkg/licensing/licensing.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package licensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrCodeUnsupported = errors.MustNewCode("licensing_unsupported")
|
||||||
|
ErrCodeFeatureUnavailable = errors.MustNewCode("feature_unavailable")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Licensing interface {
|
||||||
|
factory.Service
|
||||||
|
|
||||||
|
// Validate validates the license with the upstream server
|
||||||
|
Validate(ctx context.Context) error
|
||||||
|
// Activate validates and enables the license
|
||||||
|
Activate(ctx context.Context, organizationID valuer.UUID, key string) error
|
||||||
|
// GetActive fetches the current active license in org
|
||||||
|
GetActive(ctx context.Context, organizationID valuer.UUID) (*licensetypes.License, error)
|
||||||
|
// Refresh refreshes the license state from upstream server
|
||||||
|
Refresh(ctx context.Context, organizationID valuer.UUID) error
|
||||||
|
// Checkout creates a checkout session via upstream server and returns the redirection link
|
||||||
|
Checkout(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
||||||
|
// Portal creates a portal session via upstream server and return the redirection link
|
||||||
|
Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error)
|
||||||
|
|
||||||
|
// feature surrogate
|
||||||
|
// CheckFeature checks if the feature is active or not
|
||||||
|
CheckFeature(ctx context.Context, key string) error
|
||||||
|
// GetFeatureFlags fetches all the defined feature flags
|
||||||
|
GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error)
|
||||||
|
// GetFeatureFlags fetches all the defined feature flags
|
||||||
|
GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error)
|
||||||
|
// InitFeatures initialises the feature flags
|
||||||
|
InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error
|
||||||
|
// UpdateFeatureFlag updates the feature flag
|
||||||
|
UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
Activate(http.ResponseWriter, *http.Request)
|
||||||
|
Refresh(http.ResponseWriter, *http.Request)
|
||||||
|
GetActive(http.ResponseWriter, *http.Request)
|
||||||
|
|
||||||
|
Checkout(http.ResponseWriter, *http.Request)
|
||||||
|
Portal(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
35
pkg/licensing/nooplicensing/api.go
Normal file
35
pkg/licensing/nooplicensing/api.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package nooplicensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noopLicensingAPI struct{}
|
||||||
|
|
||||||
|
func NewLicenseAPI() licensing.API {
|
||||||
|
return &noopLicensingAPI{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *noopLicensingAPI) Activate(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Error(rw, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *noopLicensingAPI) GetActive(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Error(rw, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *noopLicensingAPI) Refresh(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Error(rw, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *noopLicensingAPI) Checkout(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Error(rw, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "not implemented"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *noopLicensingAPI) Portal(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
render.Error(rw, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "not implemented"))
|
||||||
|
}
|
99
pkg/licensing/nooplicensing/provider.go
Normal file
99
pkg/licensing/nooplicensing/provider.go
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
package nooplicensing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/licensetypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type noopLicensing struct {
|
||||||
|
stopChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFactory() factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("noop"), func(ctx context.Context, providerSettings factory.ProviderSettings, config licensing.Config) (licensing.Licensing, error) {
|
||||||
|
return New(ctx, providerSettings, config)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(_ context.Context, _ factory.ProviderSettings, _ licensing.Config) (licensing.Licensing, error) {
|
||||||
|
return &noopLicensing{stopChan: make(chan struct{})}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Start(context.Context) error {
|
||||||
|
<-provider.stopChan
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Stop(context.Context) error {
|
||||||
|
close(provider.stopChan)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Activate(ctx context.Context, organizationID valuer.UUID, key string) error {
|
||||||
|
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "fetching license is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Validate(ctx context.Context) error {
|
||||||
|
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "validating license is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Refresh(ctx context.Context, organizationID valuer.UUID) error {
|
||||||
|
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "refreshing license is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Checkout(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
|
||||||
|
return nil, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "checkout session is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) Portal(ctx context.Context, organizationID valuer.UUID, postableSubscription *licensetypes.PostableSubscription) (*licensetypes.GettableSubscription, error) {
|
||||||
|
return nil, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "portal session is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) GetActive(ctx context.Context, organizationID valuer.UUID) (*licensetypes.License, error) {
|
||||||
|
return nil, errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "fetching active license is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) CheckFeature(ctx context.Context, key string) error {
|
||||||
|
feature, err := provider.GetFeatureFlag(ctx, key)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if feature.Active {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Newf(errors.TypeNotFound, licensing.ErrCodeFeatureUnavailable, "feature unavailable: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) GetFeatureFlag(ctx context.Context, key string) (*featuretypes.GettableFeature, error) {
|
||||||
|
features, err := provider.GetFeatureFlags(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, feature := range features {
|
||||||
|
if feature.Name == key {
|
||||||
|
return feature, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "no feature available with given key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) GetFeatureFlags(ctx context.Context) ([]*featuretypes.GettableFeature, error) {
|
||||||
|
return licensetypes.DefaultFeatureSet, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) InitFeatures(ctx context.Context, features []*featuretypes.GettableFeature) error {
|
||||||
|
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "init features is not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider *noopLicensing) UpdateFeatureFlag(ctx context.Context, feature *featuretypes.GettableFeature) error {
|
||||||
|
return errors.New(errors.TypeUnsupported, licensing.ErrCodeUnsupported, "updating feature flag is not supported")
|
||||||
|
}
|
@ -92,7 +92,7 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = h.module.CreateBulkInvite(ctx, claims.OrgID, claims.UserID, &types.PostableBulkInviteRequest{
|
invites, err := h.module.CreateBulkInvite(ctx, claims.OrgID, claims.UserID, &types.PostableBulkInviteRequest{
|
||||||
Invites: []types.PostableInvite{req},
|
Invites: []types.PostableInvite{req},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -100,7 +100,7 @@ func (h *handler) CreateInvite(rw http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
render.Success(rw, http.StatusCreated, nil)
|
render.Success(rw, http.StatusCreated, invites[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
|
func (h *handler) CreateBulkInvite(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
errorsV2 "github.com/SigNoz/signoz/pkg/errors"
|
||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
"github.com/SigNoz/signoz/pkg/http/render"
|
"github.com/SigNoz/signoz/pkg/http/render"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations/services"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
@ -58,6 +59,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
"github.com/SigNoz/signoz/pkg/query-service/postprocess"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
"github.com/SigNoz/signoz/pkg/types/pipelinetypes"
|
||||||
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
ruletypes "github.com/SigNoz/signoz/pkg/types/ruletypes"
|
||||||
|
|
||||||
@ -89,7 +91,6 @@ func NewRouter() *mux.Router {
|
|||||||
type APIHandler struct {
|
type APIHandler struct {
|
||||||
reader interfaces.Reader
|
reader interfaces.Reader
|
||||||
ruleManager *rules.Manager
|
ruleManager *rules.Manager
|
||||||
featureFlags interfaces.FeatureLookup
|
|
||||||
querier interfaces.Querier
|
querier interfaces.Querier
|
||||||
querierV2 interfaces.Querier
|
querierV2 interfaces.Querier
|
||||||
queryBuilder *queryBuilder.QueryBuilder
|
queryBuilder *queryBuilder.QueryBuilder
|
||||||
@ -136,6 +137,8 @@ type APIHandler struct {
|
|||||||
|
|
||||||
AlertmanagerAPI *alertmanager.API
|
AlertmanagerAPI *alertmanager.API
|
||||||
|
|
||||||
|
LicensingAPI licensing.API
|
||||||
|
|
||||||
FieldsAPI *fields.API
|
FieldsAPI *fields.API
|
||||||
|
|
||||||
Signoz *signoz.SigNoz
|
Signoz *signoz.SigNoz
|
||||||
@ -155,9 +158,6 @@ type APIHandlerOpts struct {
|
|||||||
// rule manager handles rule crud operations
|
// rule manager handles rule crud operations
|
||||||
RuleManager *rules.Manager
|
RuleManager *rules.Manager
|
||||||
|
|
||||||
// feature flags querier
|
|
||||||
FeatureFlags interfaces.FeatureLookup
|
|
||||||
|
|
||||||
// Integrations
|
// Integrations
|
||||||
IntegrationsController *integrations.Controller
|
IntegrationsController *integrations.Controller
|
||||||
|
|
||||||
@ -177,6 +177,8 @@ type APIHandlerOpts struct {
|
|||||||
|
|
||||||
AlertmanagerAPI *alertmanager.API
|
AlertmanagerAPI *alertmanager.API
|
||||||
|
|
||||||
|
LicensingAPI licensing.API
|
||||||
|
|
||||||
FieldsAPI *fields.API
|
FieldsAPI *fields.API
|
||||||
|
|
||||||
Signoz *signoz.SigNoz
|
Signoz *signoz.SigNoz
|
||||||
@ -224,7 +226,6 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
|||||||
preferSpanMetrics: opts.PreferSpanMetrics,
|
preferSpanMetrics: opts.PreferSpanMetrics,
|
||||||
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
temporalityMap: make(map[string]map[v3.Temporality]bool),
|
||||||
ruleManager: opts.RuleManager,
|
ruleManager: opts.RuleManager,
|
||||||
featureFlags: opts.FeatureFlags,
|
|
||||||
IntegrationsController: opts.IntegrationsController,
|
IntegrationsController: opts.IntegrationsController,
|
||||||
CloudIntegrationsController: opts.CloudIntegrationsController,
|
CloudIntegrationsController: opts.CloudIntegrationsController,
|
||||||
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
LogsParsingPipelineController: opts.LogsParsingPipelineController,
|
||||||
@ -244,6 +245,7 @@ func NewAPIHandler(opts APIHandlerOpts) (*APIHandler, error) {
|
|||||||
JWT: opts.JWT,
|
JWT: opts.JWT,
|
||||||
SummaryService: summaryService,
|
SummaryService: summaryService,
|
||||||
AlertmanagerAPI: opts.AlertmanagerAPI,
|
AlertmanagerAPI: opts.AlertmanagerAPI,
|
||||||
|
LicensingAPI: opts.LicensingAPI,
|
||||||
Signoz: opts.Signoz,
|
Signoz: opts.Signoz,
|
||||||
FieldsAPI: opts.FieldsAPI,
|
FieldsAPI: opts.FieldsAPI,
|
||||||
QuickFilters: opts.QuickFilters,
|
QuickFilters: opts.QuickFilters,
|
||||||
@ -607,7 +609,7 @@ func (aH *APIHandler) RegisterRoutes(router *mux.Router, am *middleware.AuthZ) {
|
|||||||
render.Success(rw, http.StatusOK, []any{})
|
render.Success(rw, http.StatusOK, []any{})
|
||||||
})).Methods(http.MethodGet)
|
})).Methods(http.MethodGet)
|
||||||
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(func(rw http.ResponseWriter, req *http.Request) {
|
router.HandleFunc("/api/v3/licenses/active", am.ViewAccess(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
render.Error(rw, errorsV2.New(errorsV2.TypeUnsupported, errorsV2.CodeUnsupported, "not implemented"))
|
aH.LicensingAPI.Activate(rw, req)
|
||||||
})).Methods(http.MethodGet)
|
})).Methods(http.MethodGet)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1979,15 +1981,14 @@ func (aH *APIHandler) getVersion(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
||||||
featureSet, err := aH.FF().GetFeatureFlags()
|
featureSet, err := aH.Signoz.Licensing.GetFeatureFlags(r.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
aH.HandleError(w, err, http.StatusInternalServerError)
|
aH.HandleError(w, err, http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if aH.preferSpanMetrics {
|
if aH.preferSpanMetrics {
|
||||||
for idx := range featureSet {
|
for idx, feature := range featureSet {
|
||||||
feature := &featureSet[idx]
|
if feature.Name == featuretypes.UseSpanMetrics {
|
||||||
if feature.Name == model.UseSpanMetrics {
|
|
||||||
featureSet[idx].Active = true
|
featureSet[idx].Active = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1995,12 +1996,8 @@ func (aH *APIHandler) getFeatureFlags(w http.ResponseWriter, r *http.Request) {
|
|||||||
aH.Respond(w, featureSet)
|
aH.Respond(w, featureSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (aH *APIHandler) FF() interfaces.FeatureLookup {
|
func (aH *APIHandler) CheckFeature(ctx context.Context, key string) bool {
|
||||||
return aH.featureFlags
|
err := aH.Signoz.Licensing.CheckFeature(ctx, key)
|
||||||
}
|
|
||||||
|
|
||||||
func (aH *APIHandler) CheckFeature(f string) bool {
|
|
||||||
err := aH.FF().CheckFeature(f)
|
|
||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/cache"
|
"github.com/SigNoz/signoz/pkg/cache"
|
||||||
metricsV3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
|
metricsV3 "github.com/SigNoz/signoz/pkg/query-service/app/metrics/v3"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
|
||||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -47,7 +46,6 @@ type prepareMetricQueryFunc func(start, end int64, queryType v3.QueryType, panel
|
|||||||
|
|
||||||
type QueryBuilder struct {
|
type QueryBuilder struct {
|
||||||
options QueryBuilderOptions
|
options QueryBuilderOptions
|
||||||
featureFlags interfaces.FeatureLookup
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryBuilderOptions struct {
|
type QueryBuilderOptions struct {
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/alertmanager"
|
"github.com/SigNoz/signoz/pkg/alertmanager"
|
||||||
"github.com/SigNoz/signoz/pkg/apis/fields"
|
"github.com/SigNoz/signoz/pkg/apis/fields"
|
||||||
"github.com/SigNoz/signoz/pkg/http/middleware"
|
"github.com/SigNoz/signoz/pkg/http/middleware"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
"github.com/SigNoz/signoz/pkg/modules/quickfilter"
|
||||||
quickfilterscore "github.com/SigNoz/signoz/pkg/modules/quickfilter/core"
|
quickfilterscore "github.com/SigNoz/signoz/pkg/modules/quickfilter/core"
|
||||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||||
@ -34,7 +35,6 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/cache"
|
"github.com/SigNoz/signoz/pkg/cache"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
|
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/rules"
|
"github.com/SigNoz/signoz/pkg/query-service/rules"
|
||||||
@ -81,8 +81,6 @@ func (s Server) HealthCheckStatus() chan healthcheck.Status {
|
|||||||
|
|
||||||
// NewServer creates and initializes Server
|
// NewServer creates and initializes Server
|
||||||
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
||||||
// initiate feature manager
|
|
||||||
fm := featureManager.StartManager()
|
|
||||||
|
|
||||||
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
fluxIntervalForTraceDetail, err := time.ParseDuration(serverOptions.FluxIntervalForTraceDetail)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -146,13 +144,13 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
|
|||||||
Reader: reader,
|
Reader: reader,
|
||||||
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
|
PreferSpanMetrics: serverOptions.PreferSpanMetrics,
|
||||||
RuleManager: rm,
|
RuleManager: rm,
|
||||||
FeatureFlags: fm,
|
|
||||||
IntegrationsController: integrationsController,
|
IntegrationsController: integrationsController,
|
||||||
CloudIntegrationsController: cloudIntegrationsController,
|
CloudIntegrationsController: cloudIntegrationsController,
|
||||||
LogsParsingPipelineController: logParsingPipelineController,
|
LogsParsingPipelineController: logParsingPipelineController,
|
||||||
FluxInterval: fluxInterval,
|
FluxInterval: fluxInterval,
|
||||||
JWT: serverOptions.Jwt,
|
JWT: serverOptions.Jwt,
|
||||||
AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager),
|
AlertmanagerAPI: alertmanager.NewAPI(serverOptions.SigNoz.Alertmanager),
|
||||||
|
LicensingAPI: nooplicensing.NewLicenseAPI(),
|
||||||
FieldsAPI: fields.NewAPI(serverOptions.SigNoz.TelemetryStore),
|
FieldsAPI: fields.NewAPI(serverOptions.SigNoz.TelemetryStore),
|
||||||
Signoz: serverOptions.SigNoz,
|
Signoz: serverOptions.SigNoz,
|
||||||
QuickFilters: quickFilter,
|
QuickFilters: quickFilter,
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -65,9 +66,9 @@ func UseMetricsPreAggregation() bool {
|
|||||||
|
|
||||||
var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false")
|
var KafkaSpanEval = GetOrDefaultEnv("KAFKA_SPAN_EVAL", "false")
|
||||||
|
|
||||||
var DEFAULT_FEATURE_SET = model.FeatureSet{
|
var DEFAULT_FEATURE_SET = []*featuretypes.GettableFeature{
|
||||||
model.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: model.UseSpanMetrics,
|
Name: featuretypes.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
package featureManager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
"go.uber.org/zap"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FeatureManager struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartManager() *FeatureManager {
|
|
||||||
fM := &FeatureManager{}
|
|
||||||
return fM
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckFeature will be internally used by backend routines
|
|
||||||
// for feature gating
|
|
||||||
func (fm *FeatureManager) CheckFeature(featureKey string) error {
|
|
||||||
|
|
||||||
feature, err := fm.GetFeatureFlag(featureKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if feature.Active {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.ErrFeatureUnavailable{Key: featureKey}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFeatureFlags returns current features
|
|
||||||
func (fm *FeatureManager) GetFeatureFlags() (model.FeatureSet, error) {
|
|
||||||
features := constants.DEFAULT_FEATURE_SET
|
|
||||||
return features, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FeatureManager) InitFeatures(req model.FeatureSet) error {
|
|
||||||
zap.L().Error("InitFeatures not implemented in OSS")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FeatureManager) UpdateFeatureFlag(req model.Feature) error {
|
|
||||||
zap.L().Error("UpdateFeatureFlag not implemented in OSS")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fm *FeatureManager) GetFeatureFlag(key string) (model.Feature, error) {
|
|
||||||
features, err := fm.GetFeatureFlags()
|
|
||||||
if err != nil {
|
|
||||||
return model.Feature{}, err
|
|
||||||
}
|
|
||||||
for _, feature := range features {
|
|
||||||
if feature.Name == key {
|
|
||||||
return feature, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return model.Feature{}, model.ErrFeatureUnavailable{Key: key}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package interfaces
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
type FeatureLookup interface {
|
|
||||||
CheckFeature(f string) error
|
|
||||||
GetFeatureFlags() (model.FeatureSet, error)
|
|
||||||
GetFeatureFlag(f string) (model.Feature, error)
|
|
||||||
UpdateFeatureFlag(features model.Feature) error
|
|
||||||
InitFeatures(features model.FeatureSet) error
|
|
||||||
}
|
|
@ -11,6 +11,8 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
"github.com/SigNoz/signoz/pkg/config/fileprovider"
|
||||||
"github.com/SigNoz/signoz/pkg/emailing"
|
"github.com/SigNoz/signoz/pkg/emailing"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing/nooplicensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
@ -120,6 +122,10 @@ func main() {
|
|||||||
config,
|
config,
|
||||||
zeus.Config{},
|
zeus.Config{},
|
||||||
noopzeus.NewProviderFactory(),
|
noopzeus.NewProviderFactory(),
|
||||||
|
licensing.Config{},
|
||||||
|
func(_ sqlstore.SQLStore, _ zeus.Zeus) factory.ProviderFactory[licensing.Licensing, licensing.Config] {
|
||||||
|
return nooplicensing.NewFactory()
|
||||||
|
},
|
||||||
signoz.NewEmailingProviderFactories(),
|
signoz.NewEmailingProviderFactories(),
|
||||||
signoz.NewCacheProviderFactories(),
|
signoz.NewCacheProviderFactories(),
|
||||||
signoz.NewWebProviderFactories(),
|
signoz.NewWebProviderFactories(),
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package model
|
|
||||||
|
|
||||||
type FeatureSet []Feature
|
|
||||||
type Feature struct {
|
|
||||||
Name string `db:"name" json:"name"`
|
|
||||||
Active bool `db:"active" json:"active"`
|
|
||||||
Usage int64 `db:"usage" json:"usage"`
|
|
||||||
UsageLimit int64 `db:"usage_limit" json:"usage_limit"`
|
|
||||||
Route string `db:"route" json:"route"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const UseSpanMetrics = "USE_SPAN_METRICS"
|
|
||||||
const AnomalyDetection = "ANOMALY_DETECTION"
|
|
||||||
const TraceFunnels = "TRACE_FUNNELS"
|
|
||||||
|
|
||||||
var BasicPlan = FeatureSet{
|
|
||||||
Feature{
|
|
||||||
Name: UseSpanMetrics,
|
|
||||||
Active: false,
|
|
||||||
Usage: 0,
|
|
||||||
UsageLimit: -1,
|
|
||||||
Route: "",
|
|
||||||
},
|
|
||||||
Feature{
|
|
||||||
Name: AnomalyDetection,
|
|
||||||
Active: false,
|
|
||||||
Usage: 0,
|
|
||||||
UsageLimit: -1,
|
|
||||||
Route: "",
|
|
||||||
},
|
|
||||||
Feature{
|
|
||||||
Name: TraceFunnels,
|
|
||||||
Active: false,
|
|
||||||
Usage: 0,
|
|
||||||
UsageLimit: -1,
|
|
||||||
Route: "",
|
|
||||||
},
|
|
||||||
}
|
|
@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
"github.com/SigNoz/signoz/pkg/modules/user/impluser"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
"github.com/SigNoz/signoz/pkg/query-service/constants"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
|
||||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||||
"github.com/SigNoz/signoz/pkg/signoz"
|
"github.com/SigNoz/signoz/pkg/signoz"
|
||||||
@ -304,7 +303,6 @@ func (tb *FilterSuggestionsTestBed) GetQBFilterSuggestionsForLogs(
|
|||||||
func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
||||||
testDB := utils.NewQueryServiceDBForTests(t)
|
testDB := utils.NewQueryServiceDBForTests(t)
|
||||||
|
|
||||||
fm := featureManager.StartManager()
|
|
||||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
||||||
mockClickhouse.MatchExpectationsInOrder(false)
|
mockClickhouse.MatchExpectationsInOrder(false)
|
||||||
|
|
||||||
@ -318,7 +316,6 @@ func NewFilterSuggestionsTestBed(t *testing.T) *FilterSuggestionsTestBed {
|
|||||||
|
|
||||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
FeatureFlags: fm,
|
|
||||||
JWT: jwt,
|
JWT: jwt,
|
||||||
Signoz: &signoz.SigNoz{
|
Signoz: &signoz.SigNoz{
|
||||||
Modules: modules,
|
Modules: modules,
|
||||||
|
@ -24,7 +24,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
"github.com/SigNoz/signoz/pkg/instrumentation/instrumentationtest"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlstore"
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
"github.com/SigNoz/signoz/pkg/types"
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
@ -366,7 +365,6 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
|||||||
t.Fatalf("could not create cloud integrations controller: %v", err)
|
t.Fatalf("could not create cloud integrations controller: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := featureManager.StartManager()
|
|
||||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
||||||
mockClickhouse.MatchExpectationsInOrder(false)
|
mockClickhouse.MatchExpectationsInOrder(false)
|
||||||
|
|
||||||
@ -382,7 +380,6 @@ func NewCloudIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *CloudI
|
|||||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
CloudIntegrationsController: controller,
|
CloudIntegrationsController: controller,
|
||||||
FeatureFlags: fm,
|
|
||||||
JWT: jwt,
|
JWT: jwt,
|
||||||
Signoz: &signoz.SigNoz{
|
Signoz: &signoz.SigNoz{
|
||||||
Modules: modules,
|
Modules: modules,
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/query-service/app"
|
"github.com/SigNoz/signoz/pkg/query-service/app"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/cloudintegrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
"github.com/SigNoz/signoz/pkg/query-service/app/integrations"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/featureManager"
|
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/model"
|
"github.com/SigNoz/signoz/pkg/query-service/model"
|
||||||
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
v3 "github.com/SigNoz/signoz/pkg/query-service/model/v3"
|
||||||
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
"github.com/SigNoz/signoz/pkg/query-service/utils"
|
||||||
@ -567,7 +566,6 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
|||||||
t.Fatalf("could not create integrations controller: %v", err)
|
t.Fatalf("could not create integrations controller: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fm := featureManager.StartManager()
|
|
||||||
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
reader, mockClickhouse := NewMockClickhouseReader(t, testDB)
|
||||||
mockClickhouse.MatchExpectationsInOrder(false)
|
mockClickhouse.MatchExpectationsInOrder(false)
|
||||||
|
|
||||||
@ -589,7 +587,7 @@ func NewIntegrationsTestBed(t *testing.T, testDB sqlstore.SQLStore) *Integration
|
|||||||
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
apiHandler, err := app.NewAPIHandler(app.APIHandlerOpts{
|
||||||
Reader: reader,
|
Reader: reader,
|
||||||
IntegrationsController: controller,
|
IntegrationsController: controller,
|
||||||
FeatureFlags: fm,
|
|
||||||
JWT: jwt,
|
JWT: jwt,
|
||||||
CloudIntegrationsController: cloudIntegrationsController,
|
CloudIntegrationsController: cloudIntegrationsController,
|
||||||
Signoz: &signoz.SigNoz{
|
Signoz: &signoz.SigNoz{
|
||||||
|
@ -80,6 +80,7 @@ func NewSQLMigrationProviderFactories(sqlstore sqlstore.SQLStore) factory.NamedM
|
|||||||
sqlmigration.NewCreateQuickFiltersFactory(sqlstore),
|
sqlmigration.NewCreateQuickFiltersFactory(sqlstore),
|
||||||
sqlmigration.NewUpdateQuickFiltersFactory(sqlstore),
|
sqlmigration.NewUpdateQuickFiltersFactory(sqlstore),
|
||||||
sqlmigration.NewAuthRefactorFactory(sqlstore),
|
sqlmigration.NewAuthRefactorFactory(sqlstore),
|
||||||
|
sqlmigration.NewUpdateLicenseFactory(sqlstore),
|
||||||
sqlmigration.NewMigratePATToFactorAPIKey(sqlstore),
|
sqlmigration.NewMigratePATToFactorAPIKey(sqlstore),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/SigNoz/signoz/pkg/emailing"
|
"github.com/SigNoz/signoz/pkg/emailing"
|
||||||
"github.com/SigNoz/signoz/pkg/factory"
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
"github.com/SigNoz/signoz/pkg/instrumentation"
|
"github.com/SigNoz/signoz/pkg/instrumentation"
|
||||||
|
"github.com/SigNoz/signoz/pkg/licensing"
|
||||||
"github.com/SigNoz/signoz/pkg/modules/user"
|
"github.com/SigNoz/signoz/pkg/modules/user"
|
||||||
"github.com/SigNoz/signoz/pkg/prometheus"
|
"github.com/SigNoz/signoz/pkg/prometheus"
|
||||||
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
"github.com/SigNoz/signoz/pkg/sqlmigration"
|
||||||
@ -30,6 +31,7 @@ type SigNoz struct {
|
|||||||
Prometheus prometheus.Prometheus
|
Prometheus prometheus.Prometheus
|
||||||
Alertmanager alertmanager.Alertmanager
|
Alertmanager alertmanager.Alertmanager
|
||||||
Zeus zeus.Zeus
|
Zeus zeus.Zeus
|
||||||
|
Licensing licensing.Licensing
|
||||||
Emailing emailing.Emailing
|
Emailing emailing.Emailing
|
||||||
Modules Modules
|
Modules Modules
|
||||||
Handlers Handlers
|
Handlers Handlers
|
||||||
@ -40,6 +42,8 @@ func New(
|
|||||||
config Config,
|
config Config,
|
||||||
zeusConfig zeus.Config,
|
zeusConfig zeus.Config,
|
||||||
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
|
zeusProviderFactory factory.ProviderFactory[zeus.Zeus, zeus.Config],
|
||||||
|
licenseConfig licensing.Config,
|
||||||
|
licenseProviderFactoryCb func(sqlstore.SQLStore, zeus.Zeus) factory.ProviderFactory[licensing.Licensing, licensing.Config],
|
||||||
emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]],
|
emailingProviderFactories factory.NamedMap[factory.ProviderFactory[emailing.Emailing, emailing.Config]],
|
||||||
cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]],
|
cacheProviderFactories factory.NamedMap[factory.ProviderFactory[cache.Cache, cache.Config]],
|
||||||
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
|
webProviderFactories factory.NamedMap[factory.ProviderFactory[web.Web, web.Config]],
|
||||||
@ -171,6 +175,16 @@ func New(
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
licensingProviderFactory := licenseProviderFactoryCb(sqlstore, zeus)
|
||||||
|
licensing, err := licensingProviderFactory.New(
|
||||||
|
ctx,
|
||||||
|
providerSettings,
|
||||||
|
licenseConfig,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
userModule := userModuleFactory(sqlstore, emailing, providerSettings)
|
userModule := userModuleFactory(sqlstore, emailing, providerSettings)
|
||||||
userHandler := userHandlerFactory(userModule)
|
userHandler := userHandlerFactory(userModule)
|
||||||
|
|
||||||
@ -184,6 +198,7 @@ func New(
|
|||||||
instrumentation.Logger(),
|
instrumentation.Logger(),
|
||||||
factory.NewNamedService(factory.MustNewName("instrumentation"), instrumentation),
|
factory.NewNamedService(factory.MustNewName("instrumentation"), instrumentation),
|
||||||
factory.NewNamedService(factory.MustNewName("alertmanager"), alertmanager),
|
factory.NewNamedService(factory.MustNewName("alertmanager"), alertmanager),
|
||||||
|
factory.NewNamedService(factory.MustNewName("licensing"), licensing),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -199,6 +214,7 @@ func New(
|
|||||||
Prometheus: prometheus,
|
Prometheus: prometheus,
|
||||||
Alertmanager: alertmanager,
|
Alertmanager: alertmanager,
|
||||||
Zeus: zeus,
|
Zeus: zeus,
|
||||||
|
Licensing: licensing,
|
||||||
Emailing: emailing,
|
Emailing: emailing,
|
||||||
Modules: modules,
|
Modules: modules,
|
||||||
Handlers: handlers,
|
Handlers: handlers,
|
||||||
|
149
pkg/sqlmigration/034_update_license.go
Normal file
149
pkg/sqlmigration/034_update_license.go
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
package sqlmigration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/factory"
|
||||||
|
"github.com/SigNoz/signoz/pkg/sqlstore"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
"github.com/uptrace/bun/migrate"
|
||||||
|
)
|
||||||
|
|
||||||
|
type updateLicense struct {
|
||||||
|
store sqlstore.SQLStore
|
||||||
|
}
|
||||||
|
|
||||||
|
type existingLicense34 struct {
|
||||||
|
bun.BaseModel `bun:"table:licenses_v3"`
|
||||||
|
|
||||||
|
ID string `bun:"id,pk,type:text"`
|
||||||
|
Key string `bun:"key,type:text,notnull,unique"`
|
||||||
|
Data string `bun:"data,type:text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type newLicense34 struct {
|
||||||
|
bun.BaseModel `bun:"table:license"`
|
||||||
|
|
||||||
|
types.Identifiable
|
||||||
|
types.TimeAuditable
|
||||||
|
Key string `bun:"key,type:text,notnull,unique"`
|
||||||
|
Data map[string]any `bun:"data,type:text"`
|
||||||
|
LastValidatedAt time.Time `bun:"last_validated_at,notnull"`
|
||||||
|
OrgID string `bun:"org_id,type:text,notnull" json:"orgID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateLicenseFactory(store sqlstore.SQLStore) factory.ProviderFactory[SQLMigration, Config] {
|
||||||
|
return factory.NewProviderFactory(factory.MustNewName("update_license"), func(ctx context.Context, ps factory.ProviderSettings, c Config) (SQLMigration, error) {
|
||||||
|
return newUpdateLicense(ctx, ps, c, store)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUpdateLicense(_ context.Context, _ factory.ProviderSettings, _ Config, store sqlstore.SQLStore) (SQLMigration, error) {
|
||||||
|
return &updateLicense{store: store}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateLicense) Register(migrations *migrate.Migrations) error {
|
||||||
|
if err := migrations.Register(migration.Up, migration.Down); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateLicense) Up(ctx context.Context, db *bun.DB) error {
|
||||||
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
_ = tx.Rollback()
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = migration.store.Dialect().RenameTableAndModifyModel(ctx, tx, new(existingLicense34), new(newLicense34), []string{OrgReference}, func(ctx context.Context) error {
|
||||||
|
existingLicenses := make([]*existingLicense34, 0)
|
||||||
|
err = tx.NewSelect().Model(&existingLicenses).Scan(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil && len(existingLicenses) > 0 {
|
||||||
|
var orgID string
|
||||||
|
err := migration.
|
||||||
|
store.
|
||||||
|
BunDB().
|
||||||
|
NewSelect().
|
||||||
|
Model((*types.Organization)(nil)).
|
||||||
|
Column("id").
|
||||||
|
Scan(ctx, &orgID)
|
||||||
|
if err != nil {
|
||||||
|
if err != sql.ErrNoRows {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
newLicenses, err := migration.CopyExistingLicensesToNewLicenses(existingLicenses, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = tx.
|
||||||
|
NewInsert().
|
||||||
|
Model(&newLicenses).
|
||||||
|
Exec(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateLicense) Down(context.Context, *bun.DB) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (migration *updateLicense) CopyExistingLicensesToNewLicenses(existingLicenses []*existingLicense34, orgID string) ([]*newLicense34, error) {
|
||||||
|
newLicenses := make([]*newLicense34, len(existingLicenses))
|
||||||
|
for idx, existingLicense := range existingLicenses {
|
||||||
|
licenseID, err := valuer.NewUUID(existingLicense.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "license id is not a valid UUID: %s", existingLicense.ID)
|
||||||
|
}
|
||||||
|
licenseData := map[string]any{}
|
||||||
|
err = json.Unmarshal([]byte(existingLicense.Data), &licenseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "unable to unmarshal license data in map[string]any")
|
||||||
|
}
|
||||||
|
newLicenses[idx] = &newLicense34{
|
||||||
|
Identifiable: types.Identifiable{
|
||||||
|
ID: licenseID,
|
||||||
|
},
|
||||||
|
TimeAuditable: types.TimeAuditable{
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
},
|
||||||
|
Key: existingLicense.Key,
|
||||||
|
Data: licenseData,
|
||||||
|
LastValidatedAt: time.Now(),
|
||||||
|
OrgID: orgID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLicenses, nil
|
||||||
|
}
|
28
pkg/types/featuretypes/feature.go
Normal file
28
pkg/types/featuretypes/feature.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package featuretypes
|
||||||
|
|
||||||
|
import "github.com/uptrace/bun"
|
||||||
|
|
||||||
|
type FeatureSet []*GettableFeature
|
||||||
|
type GettableFeature struct {
|
||||||
|
Name string `db:"name" json:"name"`
|
||||||
|
Active bool `db:"active" json:"active"`
|
||||||
|
Usage int64 `db:"usage" json:"usage"`
|
||||||
|
UsageLimit int64 `db:"usage_limit" json:"usage_limit"`
|
||||||
|
Route string `db:"route" json:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StorableFeature struct {
|
||||||
|
bun.BaseModel `bun:"table:feature_status"`
|
||||||
|
|
||||||
|
Name string `bun:"name,pk,type:text" json:"name"`
|
||||||
|
Active bool `bun:"active" json:"active"`
|
||||||
|
Usage int `bun:"usage,default:0" json:"usage"`
|
||||||
|
UsageLimit int `bun:"usage_limit,default:0" json:"usage_limit"`
|
||||||
|
Route string `bun:"route,type:text" json:"route"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorableFeature() {}
|
||||||
|
|
||||||
|
const UseSpanMetrics = "USE_SPAN_METRICS"
|
||||||
|
const AnomalyDetection = "ANOMALY_DETECTION"
|
||||||
|
const TraceFunnels = "TRACE_FUNNELS"
|
@ -85,3 +85,7 @@ type PostableInvite struct {
|
|||||||
type PostableBulkInviteRequest struct {
|
type PostableBulkInviteRequest struct {
|
||||||
Invites []PostableInvite `json:"invites"`
|
Invites []PostableInvite `json:"invites"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GettableCreateInviteResponse struct {
|
||||||
|
InviteToken string `json:"token"`
|
||||||
|
}
|
||||||
|
@ -1,46 +0,0 @@
|
|||||||
package types
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/uptrace/bun"
|
|
||||||
)
|
|
||||||
|
|
||||||
type License struct {
|
|
||||||
bun.BaseModel `bun:"table:licenses"`
|
|
||||||
|
|
||||||
Key string `bun:"key,pk,type:text"`
|
|
||||||
CreatedAt time.Time `bun:"createdAt,default:current_timestamp"`
|
|
||||||
UpdatedAt time.Time `bun:"updatedAt,default:current_timestamp"`
|
|
||||||
PlanDetails string `bun:"planDetails,type:text"`
|
|
||||||
ActivationID string `bun:"activationId,type:text"`
|
|
||||||
ValidationMessage string `bun:"validationMessage,type:text"`
|
|
||||||
LastValidated time.Time `bun:"lastValidated,default:current_timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Site struct {
|
|
||||||
bun.BaseModel `bun:"table:sites"`
|
|
||||||
|
|
||||||
UUID string `bun:"uuid,pk,type:text"`
|
|
||||||
Alias string `bun:"alias,type:varchar(180),default:'PROD'"`
|
|
||||||
URL string `bun:"url,type:varchar(300)"`
|
|
||||||
CreatedAt time.Time `bun:"createdAt,default:current_timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type FeatureStatus struct {
|
|
||||||
bun.BaseModel `bun:"table:feature_status"`
|
|
||||||
|
|
||||||
Name string `bun:"name,pk,type:text" json:"name"`
|
|
||||||
Active bool `bun:"active" json:"active"`
|
|
||||||
Usage int `bun:"usage,default:0" json:"usage"`
|
|
||||||
UsageLimit int `bun:"usage_limit,default:0" json:"usage_limit"`
|
|
||||||
Route string `bun:"route,type:text" json:"route"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LicenseV3 struct {
|
|
||||||
bun.BaseModel `bun:"table:licenses_v3"`
|
|
||||||
|
|
||||||
ID string `bun:"id,pk,type:text"`
|
|
||||||
Key string `bun:"key,type:text,notnull,unique"`
|
|
||||||
Data string `bun:"data,type:text"`
|
|
||||||
}
|
|
389
pkg/types/licensetypes/license.go
Normal file
389
pkg/types/licensetypes/license.go
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
package licensetypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types"
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StorableLicense struct {
|
||||||
|
bun.BaseModel `bun:"table:license"`
|
||||||
|
|
||||||
|
types.Identifiable
|
||||||
|
types.TimeAuditable
|
||||||
|
Key string `bun:"key,type:text,notnull,unique"`
|
||||||
|
Data map[string]any `bun:"data,type:text"`
|
||||||
|
LastValidatedAt time.Time `bun:"last_validated_at,notnull"`
|
||||||
|
OrgID valuer.UUID `bun:"org_id,type:text,notnull" json:"orgID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// this data excludes ID and Key
|
||||||
|
type License struct {
|
||||||
|
ID valuer.UUID
|
||||||
|
Key string
|
||||||
|
Data map[string]interface{}
|
||||||
|
PlanName string
|
||||||
|
Features []*featuretypes.GettableFeature
|
||||||
|
Status string
|
||||||
|
ValidFrom int64
|
||||||
|
ValidUntil int64
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
LastValidatedAt time.Time
|
||||||
|
OrganizationID valuer.UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type GettableLicense map[string]any
|
||||||
|
|
||||||
|
type PostableLicense struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorableLicense(ID valuer.UUID, key string, data map[string]any, createdAt, updatedAt, lastValidatedAt time.Time, organizationID valuer.UUID) *StorableLicense {
|
||||||
|
return &StorableLicense{
|
||||||
|
Identifiable: types.Identifiable{
|
||||||
|
ID: ID,
|
||||||
|
},
|
||||||
|
TimeAuditable: types.TimeAuditable{
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
},
|
||||||
|
Key: key,
|
||||||
|
Data: data,
|
||||||
|
LastValidatedAt: lastValidatedAt,
|
||||||
|
OrgID: organizationID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStorableLicenseFromLicense(license *License) *StorableLicense {
|
||||||
|
return &StorableLicense{
|
||||||
|
Identifiable: types.Identifiable{
|
||||||
|
ID: license.ID,
|
||||||
|
},
|
||||||
|
TimeAuditable: types.TimeAuditable{
|
||||||
|
CreatedAt: license.CreatedAt,
|
||||||
|
UpdatedAt: license.UpdatedAt,
|
||||||
|
},
|
||||||
|
Key: license.Key,
|
||||||
|
Data: license.Data,
|
||||||
|
LastValidatedAt: license.LastValidatedAt,
|
||||||
|
OrgID: license.OrganizationID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetActiveLicenseFromStorableLicenses(storableLicenses []*StorableLicense, organizationID valuer.UUID) (*License, error) {
|
||||||
|
var activeLicense *License
|
||||||
|
for _, storableLicense := range storableLicenses {
|
||||||
|
license, err := NewLicenseFromStorableLicense(storableLicense)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if license.Status != "VALID" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if activeLicense == nil &&
|
||||||
|
(license.ValidFrom != 0) &&
|
||||||
|
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
||||||
|
activeLicense = license
|
||||||
|
}
|
||||||
|
if activeLicense != nil &&
|
||||||
|
license.ValidFrom > activeLicense.ValidFrom &&
|
||||||
|
(license.ValidUntil == -1 || license.ValidUntil > time.Now().Unix()) {
|
||||||
|
activeLicense = license
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if activeLicense == nil {
|
||||||
|
return nil, errors.Newf(errors.TypeNotFound, errors.CodeNotFound, "no active license found for the organization %s", organizationID.StringValue())
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeLicense, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractKeyFromMapStringInterface[T any](data map[string]interface{}, key string) (T, error) {
|
||||||
|
var zeroValue T
|
||||||
|
if val, ok := data[key]; ok {
|
||||||
|
if value, ok := val.(T); ok {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return zeroValue, fmt.Errorf("%s key is not a valid %s", key, reflect.TypeOf(zeroValue))
|
||||||
|
}
|
||||||
|
return zeroValue, fmt.Errorf("%s key is missing", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLicense(data []byte, organizationID valuer.UUID) (*License, error) {
|
||||||
|
licenseData := map[string]any{}
|
||||||
|
err := json.Unmarshal(data, &licenseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInternal, errors.CodeInternal, "failed to unmarshal license data")
|
||||||
|
}
|
||||||
|
|
||||||
|
var features []*featuretypes.GettableFeature
|
||||||
|
|
||||||
|
// extract id from data
|
||||||
|
licenseIDStr, err := extractKeyFromMapStringInterface[string](licenseData, "id")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
licenseID, err := valuer.NewUUID(licenseIDStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delete(licenseData, "id")
|
||||||
|
|
||||||
|
// extract key from data
|
||||||
|
licenseKey, err := extractKeyFromMapStringInterface[string](licenseData, "key")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
delete(licenseData, "key")
|
||||||
|
|
||||||
|
// extract status from data
|
||||||
|
status, err := extractKeyFromMapStringInterface[string](licenseData, "status")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
planMap, err := extractKeyFromMapStringInterface[map[string]any](licenseData, "plan")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// if license status is invalid then default it to basic
|
||||||
|
if status == LicenseStatusInvalid {
|
||||||
|
planName = PlanNameBasic
|
||||||
|
}
|
||||||
|
|
||||||
|
featuresFromZeus := make([]*featuretypes.GettableFeature, 0)
|
||||||
|
if _features, ok := licenseData["features"]; ok {
|
||||||
|
featuresData, err := json.Marshal(_features)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to marshal features data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to unmarshal features data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch planName {
|
||||||
|
case PlanNameEnterprise:
|
||||||
|
features = append(features, EnterprisePlan...)
|
||||||
|
case PlanNameBasic:
|
||||||
|
features = append(features, BasicPlan...)
|
||||||
|
default:
|
||||||
|
features = append(features, BasicPlan...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(featuresFromZeus) > 0 {
|
||||||
|
for _, feature := range featuresFromZeus {
|
||||||
|
exists := false
|
||||||
|
for i, existingFeature := range features {
|
||||||
|
if existingFeature.Name == feature.Name {
|
||||||
|
features[i] = feature // Replace existing feature
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
features = append(features, feature) // Append if it doesn't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
licenseData["features"] = features
|
||||||
|
|
||||||
|
_validFrom, err := extractKeyFromMapStringInterface[float64](licenseData, "valid_from")
|
||||||
|
if err != nil {
|
||||||
|
_validFrom = 0
|
||||||
|
}
|
||||||
|
validFrom := int64(_validFrom)
|
||||||
|
|
||||||
|
_validUntil, err := extractKeyFromMapStringInterface[float64](licenseData, "valid_until")
|
||||||
|
if err != nil {
|
||||||
|
_validUntil = 0
|
||||||
|
}
|
||||||
|
validUntil := int64(_validUntil)
|
||||||
|
|
||||||
|
return &License{
|
||||||
|
ID: licenseID,
|
||||||
|
Key: licenseKey,
|
||||||
|
Data: licenseData,
|
||||||
|
PlanName: planName,
|
||||||
|
Features: features,
|
||||||
|
ValidFrom: validFrom,
|
||||||
|
ValidUntil: validUntil,
|
||||||
|
Status: status,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
UpdatedAt: time.Now(),
|
||||||
|
LastValidatedAt: time.Now(),
|
||||||
|
OrganizationID: organizationID,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLicenseFromStorableLicense(storableLicense *StorableLicense) (*License, error) {
|
||||||
|
var features []*featuretypes.GettableFeature
|
||||||
|
// extract status from data
|
||||||
|
status, err := extractKeyFromMapStringInterface[string](storableLicense.Data, "status")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
planMap, err := extractKeyFromMapStringInterface[map[string]any](storableLicense.Data, "plan")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
planName, err := extractKeyFromMapStringInterface[string](planMap, "name")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// if license status is invalid then default it to basic
|
||||||
|
if status == LicenseStatusInvalid {
|
||||||
|
planName = PlanNameBasic
|
||||||
|
}
|
||||||
|
|
||||||
|
featuresFromZeus := make([]*featuretypes.GettableFeature, 0)
|
||||||
|
if _features, ok := storableLicense.Data["features"]; ok {
|
||||||
|
featuresData, err := json.Marshal(_features)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to marshal features data")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(featuresData, &featuresFromZeus); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to unmarshal features data")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch planName {
|
||||||
|
case PlanNameEnterprise:
|
||||||
|
features = append(features, EnterprisePlan...)
|
||||||
|
case PlanNameBasic:
|
||||||
|
features = append(features, BasicPlan...)
|
||||||
|
default:
|
||||||
|
features = append(features, BasicPlan...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(featuresFromZeus) > 0 {
|
||||||
|
for _, feature := range featuresFromZeus {
|
||||||
|
exists := false
|
||||||
|
for i, existingFeature := range features {
|
||||||
|
if existingFeature.Name == feature.Name {
|
||||||
|
features[i] = feature // Replace existing feature
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
features = append(features, feature) // Append if it doesn't exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
storableLicense.Data["features"] = features
|
||||||
|
|
||||||
|
_validFrom, err := extractKeyFromMapStringInterface[float64](storableLicense.Data, "valid_from")
|
||||||
|
if err != nil {
|
||||||
|
_validFrom = 0
|
||||||
|
}
|
||||||
|
validFrom := int64(_validFrom)
|
||||||
|
|
||||||
|
_validUntil, err := extractKeyFromMapStringInterface[float64](storableLicense.Data, "valid_until")
|
||||||
|
if err != nil {
|
||||||
|
_validUntil = 0
|
||||||
|
}
|
||||||
|
validUntil := int64(_validUntil)
|
||||||
|
|
||||||
|
return &License{
|
||||||
|
ID: storableLicense.ID,
|
||||||
|
Key: storableLicense.Key,
|
||||||
|
Data: storableLicense.Data,
|
||||||
|
PlanName: planName,
|
||||||
|
Features: features,
|
||||||
|
ValidFrom: validFrom,
|
||||||
|
ValidUntil: validUntil,
|
||||||
|
Status: status,
|
||||||
|
CreatedAt: storableLicense.CreatedAt,
|
||||||
|
UpdatedAt: storableLicense.UpdatedAt,
|
||||||
|
LastValidatedAt: storableLicense.LastValidatedAt,
|
||||||
|
OrganizationID: storableLicense.OrgID,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (license *License) Update(data []byte) error {
|
||||||
|
updatedLicense, err := NewLicense(data, license.OrganizationID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTime := time.Now()
|
||||||
|
license.Data = updatedLicense.Data
|
||||||
|
license.Features = updatedLicense.Features
|
||||||
|
license.ID = updatedLicense.ID
|
||||||
|
license.Key = updatedLicense.Key
|
||||||
|
license.PlanName = updatedLicense.PlanName
|
||||||
|
license.Status = updatedLicense.Status
|
||||||
|
license.ValidFrom = updatedLicense.ValidFrom
|
||||||
|
license.ValidUntil = updatedLicense.ValidUntil
|
||||||
|
license.UpdatedAt = currentTime
|
||||||
|
license.LastValidatedAt = currentTime
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGettableLicense(data map[string]any, key string) *GettableLicense {
|
||||||
|
gettableLicense := make(GettableLicense)
|
||||||
|
for k, v := range data {
|
||||||
|
gettableLicense[k] = v
|
||||||
|
}
|
||||||
|
gettableLicense["key"] = key
|
||||||
|
return &gettableLicense
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PostableLicense) UnmarshalJSON(data []byte) error {
|
||||||
|
var postableLicense struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &postableLicense)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to unmarshal payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if postableLicense.Key == "" {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "license key cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Key = postableLicense.Key
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
Create(context.Context, *StorableLicense) error
|
||||||
|
Get(context.Context, valuer.UUID, valuer.UUID) (*StorableLicense, error)
|
||||||
|
GetAll(context.Context, valuer.UUID) ([]*StorableLicense, error)
|
||||||
|
Update(context.Context, valuer.UUID, *StorableLicense) error
|
||||||
|
|
||||||
|
// feature surrogate
|
||||||
|
InitFeatures(context.Context, []*featuretypes.StorableFeature) error
|
||||||
|
CreateFeature(context.Context, *featuretypes.StorableFeature) error
|
||||||
|
GetFeature(context.Context, string) (*featuretypes.StorableFeature, error)
|
||||||
|
GetAllFeatures(context.Context) ([]*featuretypes.StorableFeature, error)
|
||||||
|
UpdateFeature(context.Context, *featuretypes.StorableFeature) error
|
||||||
|
|
||||||
|
// ListOrganizations returns the list of orgs
|
||||||
|
ListOrganizations(context.Context) ([]valuer.UUID, error)
|
||||||
|
}
|
175
pkg/types/licensetypes/license_test.go
Normal file
175
pkg/types/licensetypes/license_test.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package licensetypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
|
"github.com/SigNoz/signoz/pkg/valuer"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewLicenseV3(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
data []byte
|
||||||
|
pass bool
|
||||||
|
expected *License
|
||||||
|
error error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Error for missing license id",
|
||||||
|
data: []byte(`{}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("id key is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for license id not being a valid string",
|
||||||
|
data: []byte(`{"id": 10}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("id key is not a valid string"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for missing license key",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e"}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("key key is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for invalid string license key",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":10}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("key key is not a valid string"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for missing license status",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e", "key": "does-not-matter","category":"FREE"}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("status key is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for invalid string license status",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key": "does-not-matter", "category":"FREE", "status":10}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("status key is not a valid string"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for missing license plan",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE"}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("plan key is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for invalid json license plan",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":10}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("plan key is not a valid map[string]interface {}"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Error for invalid license plan",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{}}`),
|
||||||
|
pass: false,
|
||||||
|
error: errors.New("name key is missing"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Parse the entire license properly",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||||
|
pass: true,
|
||||||
|
expected: &License{
|
||||||
|
ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
Key: "does-not-matter-key",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"plan": map[string]interface{}{
|
||||||
|
"name": "ENTERPRISE",
|
||||||
|
},
|
||||||
|
"category": "FREE",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
"valid_from": float64(1730899309),
|
||||||
|
"valid_until": float64(-1),
|
||||||
|
},
|
||||||
|
PlanName: PlanNameEnterprise,
|
||||||
|
ValidFrom: 1730899309,
|
||||||
|
ValidUntil: -1,
|
||||||
|
Status: "ACTIVE",
|
||||||
|
Features: make([]*featuretypes.GettableFeature, 0),
|
||||||
|
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fallback to basic plan if license status is invalid",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"INVALID","plan":{"name":"ENTERPRISE"},"valid_from": 1730899309,"valid_until": -1}`),
|
||||||
|
pass: true,
|
||||||
|
expected: &License{
|
||||||
|
ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
Key: "does-not-matter-key",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"plan": map[string]interface{}{
|
||||||
|
"name": "ENTERPRISE",
|
||||||
|
},
|
||||||
|
"category": "FREE",
|
||||||
|
"status": "INVALID",
|
||||||
|
"valid_from": float64(1730899309),
|
||||||
|
"valid_until": float64(-1),
|
||||||
|
},
|
||||||
|
PlanName: PlanNameBasic,
|
||||||
|
ValidFrom: 1730899309,
|
||||||
|
ValidUntil: -1,
|
||||||
|
Status: "INVALID",
|
||||||
|
Features: make([]*featuretypes.GettableFeature, 0),
|
||||||
|
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fallback states for validFrom and validUntil",
|
||||||
|
data: []byte(`{"id":"0196f794-ff30-7bee-a5f4-ef5ad315715e","key":"does-not-matter-key","category":"FREE","status":"ACTIVE","plan":{"name":"ENTERPRISE"},"valid_from":1234.456,"valid_until":5678.567}`),
|
||||||
|
pass: true,
|
||||||
|
expected: &License{
|
||||||
|
ID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
Key: "does-not-matter-key",
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"plan": map[string]interface{}{
|
||||||
|
"name": "ENTERPRISE",
|
||||||
|
},
|
||||||
|
"valid_from": 1234.456,
|
||||||
|
"valid_until": 5678.567,
|
||||||
|
"category": "FREE",
|
||||||
|
"status": "ACTIVE",
|
||||||
|
},
|
||||||
|
PlanName: PlanNameEnterprise,
|
||||||
|
ValidFrom: 1234,
|
||||||
|
ValidUntil: 5678,
|
||||||
|
Status: "ACTIVE",
|
||||||
|
Features: make([]*featuretypes.GettableFeature, 0),
|
||||||
|
CreatedAt: time.Time{},
|
||||||
|
UpdatedAt: time.Time{},
|
||||||
|
LastValidatedAt: time.Time{},
|
||||||
|
OrganizationID: valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
license, err := NewLicense(tc.data, valuer.MustNewUUID("0196f794-ff30-7bee-a5f4-ef5ad315715e"))
|
||||||
|
if license != nil {
|
||||||
|
license.Features = make([]*featuretypes.GettableFeature, 0)
|
||||||
|
delete(license.Data, "features")
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.pass {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, license)
|
||||||
|
// as the new license will pick the time.Now() value. doesn't make sense to compare them
|
||||||
|
license.CreatedAt = time.Time{}
|
||||||
|
license.UpdatedAt = time.Time{}
|
||||||
|
license.LastValidatedAt = time.Time{}
|
||||||
|
assert.Equal(t, tc.expected, license)
|
||||||
|
} else {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.EqualError(t, err, tc.error.Error())
|
||||||
|
require.Nil(t, license)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package model
|
package licensetypes
|
||||||
|
|
||||||
import (
|
import "github.com/SigNoz/signoz/pkg/types/featuretypes"
|
||||||
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const SSO = "SSO"
|
const SSO = "SSO"
|
||||||
const Basic = "BASIC_PLAN"
|
const Basic = "BASIC_PLAN"
|
||||||
@ -26,44 +24,44 @@ const ChatSupport = "CHAT_SUPPORT"
|
|||||||
const Gateway = "GATEWAY"
|
const Gateway = "GATEWAY"
|
||||||
const PremiumSupport = "PREMIUM_SUPPORT"
|
const PremiumSupport = "PREMIUM_SUPPORT"
|
||||||
|
|
||||||
var BasicPlan = basemodel.FeatureSet{
|
var BasicPlan = featuretypes.FeatureSet{
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: SSO,
|
Name: SSO,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.UseSpanMetrics,
|
Name: featuretypes.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: Gateway,
|
Name: Gateway,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: PremiumSupport,
|
Name: PremiumSupport,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.AnomalyDetection,
|
Name: featuretypes.AnomalyDetection,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.TraceFunnels,
|
Name: featuretypes.TraceFunnels,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
@ -71,58 +69,68 @@ var BasicPlan = basemodel.FeatureSet{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var EnterprisePlan = basemodel.FeatureSet{
|
var EnterprisePlan = featuretypes.FeatureSet{
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: SSO,
|
Name: SSO,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.UseSpanMetrics,
|
Name: featuretypes.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: Onboarding,
|
Name: Onboarding,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: ChatSupport,
|
Name: ChatSupport,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: Gateway,
|
Name: Gateway,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: PremiumSupport,
|
Name: PremiumSupport,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.AnomalyDetection,
|
Name: featuretypes.AnomalyDetection,
|
||||||
Active: true,
|
Active: true,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
||||||
Route: "",
|
Route: "",
|
||||||
},
|
},
|
||||||
basemodel.Feature{
|
&featuretypes.GettableFeature{
|
||||||
Name: basemodel.TraceFunnels,
|
Name: featuretypes.TraceFunnels,
|
||||||
|
Active: false,
|
||||||
|
Usage: 0,
|
||||||
|
UsageLimit: -1,
|
||||||
|
Route: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var DefaultFeatureSet = featuretypes.FeatureSet{
|
||||||
|
&featuretypes.GettableFeature{
|
||||||
|
Name: featuretypes.UseSpanMetrics,
|
||||||
Active: false,
|
Active: false,
|
||||||
Usage: 0,
|
Usage: 0,
|
||||||
UsageLimit: -1,
|
UsageLimit: -1,
|
33
pkg/types/licensetypes/subscription.go
Normal file
33
pkg/types/licensetypes/subscription.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package licensetypes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/SigNoz/signoz/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GettableSubscription struct {
|
||||||
|
RedirectURL string `json:"redirectURL"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostableSubscription struct {
|
||||||
|
SuccessURL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PostableSubscription) UnmarshalJSON(data []byte) error {
|
||||||
|
var postableSubscription struct {
|
||||||
|
SuccessURL string `json:"url"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, &postableSubscription)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "failed to unmarshal payload")
|
||||||
|
}
|
||||||
|
|
||||||
|
if postableSubscription.SuccessURL == "" {
|
||||||
|
return errors.Newf(errors.TypeInvalidInput, errors.CodeInvalidInput, "success url cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SuccessURL = postableSubscription.SuccessURL
|
||||||
|
return nil
|
||||||
|
}
|
@ -6,6 +6,7 @@ from testcontainers.core.container import Network
|
|||||||
from wiremock.client import (
|
from wiremock.client import (
|
||||||
Mapping,
|
Mapping,
|
||||||
Mappings,
|
Mappings,
|
||||||
|
Requests,
|
||||||
)
|
)
|
||||||
from wiremock.constants import Config
|
from wiremock.constants import Config
|
||||||
from wiremock.testing.testcontainer import WireMockContainer
|
from wiremock.testing.testcontainer import WireMockContainer
|
||||||
@ -78,3 +79,4 @@ def make_http_mocks():
|
|||||||
yield _make_http_mocks
|
yield _make_http_mocks
|
||||||
|
|
||||||
Mappings.delete_all_mappings()
|
Mappings.delete_all_mappings()
|
||||||
|
Requests.reset_request_journal()
|
||||||
|
@ -69,7 +69,7 @@ def test_apply_license(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
|||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json()["count"] >= 1
|
assert response.json()["count"] == 1
|
||||||
|
|
||||||
|
|
||||||
def test_refresh_license(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
def test_refresh_license(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
||||||
@ -123,7 +123,7 @@ def test_refresh_license(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None
|
|||||||
|
|
||||||
cursor = signoz.sqlstore.conn.cursor()
|
cursor = signoz.sqlstore.conn.cursor()
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT data FROM licenses_v3 WHERE id='0196360e-90cd-7a74-8313-1aa815ce2a67'"
|
"SELECT data FROM license WHERE id='0196360e-90cd-7a74-8313-1aa815ce2a67'"
|
||||||
)
|
)
|
||||||
record = cursor.fetchone()[0]
|
record = cursor.fetchone()[0]
|
||||||
assert json.loads(record)["valid_from"] == 1732146922
|
assert json.loads(record)["valid_from"] == 1732146922
|
||||||
@ -134,7 +134,7 @@ def test_refresh_license(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None
|
|||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.json()["count"] >= 1
|
assert response.json()["count"] == 1
|
||||||
|
|
||||||
|
|
||||||
def test_license_checkout(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
def test_license_checkout(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
||||||
@ -172,7 +172,7 @@ def test_license_checkout(signoz: SigNoz, make_http_mocks, get_jwt_token) -> Non
|
|||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == http.HTTPStatus.OK
|
assert response.status_code == http.HTTPStatus.CREATED
|
||||||
assert response.json()["data"]["redirectURL"] == "https://signoz.checkout.com"
|
assert response.json()["data"]["redirectURL"] == "https://signoz.checkout.com"
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
@ -219,7 +219,7 @@ def test_license_portal(signoz: SigNoz, make_http_mocks, get_jwt_token) -> None:
|
|||||||
timeout=5,
|
timeout=5,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert response.status_code == http.HTTPStatus.OK
|
assert response.status_code == http.HTTPStatus.CREATED
|
||||||
assert response.json()["data"]["redirectURL"] == "https://signoz.portal.com"
|
assert response.json()["data"]["redirectURL"] == "https://signoz.portal.com"
|
||||||
|
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user