mirror of
https://github.com/SigNoz/signoz.git
synced 2026-04-20 18:50:29 +01:00
Compare commits
12 Commits
chore/use-
...
issue_4361
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50b42aca8c | ||
|
|
dbad6cff96 | ||
|
|
73a44cee77 | ||
|
|
b9b7c10af9 | ||
|
|
3d71707f0b | ||
|
|
f59f611ea2 | ||
|
|
07131f10a9 | ||
|
|
d930e77870 | ||
|
|
8a9f6bee98 | ||
|
|
4b06b4546f | ||
|
|
526b08a179 | ||
|
|
c7ab610bd8 |
@@ -4268,6 +4268,183 @@ components:
|
||||
type: object
|
||||
Sigv4SigV4Config:
|
||||
type: object
|
||||
SpanattributemappingtypesCondition:
|
||||
properties:
|
||||
attributes:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
resource:
|
||||
items:
|
||||
type: string
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
SpanattributemappingtypesFieldContext:
|
||||
enum:
|
||||
- attribute
|
||||
- resource
|
||||
type: string
|
||||
SpanattributemappingtypesGroup:
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
condition:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesCondition'
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
ordId:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- ordId
|
||||
- name
|
||||
- category
|
||||
- condition
|
||||
- enabled
|
||||
type: object
|
||||
SpanattributemappingtypesListGroupsResponse:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesGroup'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
SpanattributemappingtypesListMappersResponse:
|
||||
properties:
|
||||
items:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapper'
|
||||
nullable: true
|
||||
type: array
|
||||
required:
|
||||
- items
|
||||
type: object
|
||||
SpanattributemappingtypesMapper:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapperConfig'
|
||||
createdAt:
|
||||
format: date-time
|
||||
type: string
|
||||
createdBy:
|
||||
type: string
|
||||
enabled:
|
||||
type: boolean
|
||||
field_context:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesFieldContext'
|
||||
group_id:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
updatedAt:
|
||||
format: date-time
|
||||
type: string
|
||||
updatedBy:
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
- group_id
|
||||
- name
|
||||
- field_context
|
||||
- config
|
||||
- enabled
|
||||
type: object
|
||||
SpanattributemappingtypesMapperConfig:
|
||||
properties:
|
||||
sources:
|
||||
items:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapperSource'
|
||||
nullable: true
|
||||
type: array
|
||||
type: object
|
||||
SpanattributemappingtypesMapperOperation:
|
||||
enum:
|
||||
- move
|
||||
- copy
|
||||
type: string
|
||||
SpanattributemappingtypesMapperSource:
|
||||
properties:
|
||||
context:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesFieldContext'
|
||||
key:
|
||||
type: string
|
||||
operation:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapperOperation'
|
||||
priority:
|
||||
type: integer
|
||||
type: object
|
||||
SpanattributemappingtypesPostableGroup:
|
||||
properties:
|
||||
category:
|
||||
type: string
|
||||
condition:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesCondition'
|
||||
enabled:
|
||||
type: boolean
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- category
|
||||
- condition
|
||||
type: object
|
||||
SpanattributemappingtypesPostableMapper:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapperConfig'
|
||||
enabled:
|
||||
type: boolean
|
||||
field_context:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesFieldContext'
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- field_context
|
||||
- config
|
||||
type: object
|
||||
SpanattributemappingtypesUpdatableGroup:
|
||||
properties:
|
||||
condition:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesCondition'
|
||||
enabled:
|
||||
nullable: true
|
||||
type: boolean
|
||||
name:
|
||||
nullable: true
|
||||
type: string
|
||||
type: object
|
||||
SpanattributemappingtypesUpdatableMapper:
|
||||
properties:
|
||||
config:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapperConfig'
|
||||
enabled:
|
||||
nullable: true
|
||||
type: boolean
|
||||
field_context:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesFieldContext'
|
||||
type: object
|
||||
TelemetrytypesFieldContext:
|
||||
enum:
|
||||
- metric
|
||||
@@ -8876,6 +9053,510 @@ paths:
|
||||
summary: Updates my service account
|
||||
tags:
|
||||
- serviceaccount
|
||||
/api/v1/span_attribute_mapping_groups:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns all span attribute mapping groups for the authenticated
|
||||
org.
|
||||
operationId: ListSpanAttributeMappingGroups
|
||||
parameters:
|
||||
- in: query
|
||||
name: category
|
||||
schema:
|
||||
nullable: true
|
||||
type: string
|
||||
- in: query
|
||||
name: enabled
|
||||
schema:
|
||||
nullable: true
|
||||
type: boolean
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesListGroupsResponse'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: List span attribute mapping groups
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
post:
|
||||
deprecated: false
|
||||
description: Creates a new span attribute mapping group for the org.
|
||||
operationId: CreateMappingGroup
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesPostableGroup'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesGroup'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"409":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Conflict
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Create a span attribute mapping group
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
/api/v1/span_attribute_mapping_groups/{groupId}/mappers/{mapperId}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: Hard-deletes a mapper from a mapping group.
|
||||
operationId: DeleteMapper
|
||||
parameters:
|
||||
- in: path
|
||||
name: groupId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- in: path
|
||||
name: mapperId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Delete a span attribute mapper
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
patch:
|
||||
deprecated: false
|
||||
description: Partially updates an existing mapper's field context, config, or
|
||||
enabled state.
|
||||
operationId: UpdateMapper
|
||||
parameters:
|
||||
- in: path
|
||||
name: groupId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- in: path
|
||||
name: mapperId
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesUpdatableMapper'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapper'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Update a span attribute mapper
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
/api/v1/span_attribute_mapping_groups/{id}:
|
||||
delete:
|
||||
deprecated: false
|
||||
description: Hard-deletes a mapping group and cascades to all its mappers.
|
||||
operationId: DeleteMappingGroup
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: No Content
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Delete a span attribute mapping group
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
patch:
|
||||
deprecated: false
|
||||
description: Partially updates an existing mapping group's name, condition,
|
||||
or enabled state.
|
||||
operationId: UpdateMappingGroup
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesUpdatableGroup'
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesGroup'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Update a span attribute mapping group
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
/api/v1/span_attribute_mapping_groups/{id}/mappers:
|
||||
get:
|
||||
deprecated: false
|
||||
description: Returns all attribute mappers belonging to a mapping group.
|
||||
operationId: ListMappers
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"200":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesListMappersResponse'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: OK
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- VIEWER
|
||||
- tokenizer:
|
||||
- VIEWER
|
||||
summary: List span attribute mappers for a group
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
post:
|
||||
deprecated: false
|
||||
description: Adds a new attribute mapper to the specified mapping group.
|
||||
operationId: CreateMapper
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesPostableMapper'
|
||||
responses:
|
||||
"201":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
properties:
|
||||
data:
|
||||
$ref: '#/components/schemas/SpanattributemappingtypesMapper'
|
||||
status:
|
||||
type: string
|
||||
required:
|
||||
- status
|
||||
- data
|
||||
type: object
|
||||
description: Created
|
||||
"400":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Bad Request
|
||||
"401":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Unauthorized
|
||||
"403":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Forbidden
|
||||
"404":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Not Found
|
||||
"409":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Conflict
|
||||
"500":
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RenderErrorResponse'
|
||||
description: Internal Server Error
|
||||
security:
|
||||
- api_key:
|
||||
- ADMIN
|
||||
- tokenizer:
|
||||
- ADMIN
|
||||
summary: Create a span attribute mapper
|
||||
tags:
|
||||
- span-attribute-mapping
|
||||
/api/v1/testChannel:
|
||||
post:
|
||||
deprecated: true
|
||||
|
||||
@@ -19,11 +19,11 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
|
||||
}
|
||||
|
||||
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
|
||||
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
|
||||
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.Aws.DeploymentRegion)
|
||||
u, _ := url.Parse(baseURL)
|
||||
|
||||
q := u.Query()
|
||||
q.Set("region", req.Config.AWS.DeploymentRegion)
|
||||
q.Set("region", req.Config.Aws.DeploymentRegion)
|
||||
u.Fragment = "/stacks/quickcreate"
|
||||
|
||||
u.RawQuery = q.Encode()
|
||||
@@ -39,7 +39,9 @@ func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, acc
|
||||
q.Set("param_IngestionKey", req.Credentials.IngestionKey)
|
||||
|
||||
return &cloudintegrationtypes.ConnectionArtifact{
|
||||
AWS: cloudintegrationtypes.NewAWSConnectionArtifact(u.String() + "?&" + q.Encode()), // this format is required by AWS
|
||||
Aws: &cloudintegrationtypes.AWSConnectionArtifact{
|
||||
ConnectionURL: u.String() + "?&" + q.Encode(), // this format is required by AWS
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -122,6 +124,9 @@ func (provider *awscloudprovider) BuildIntegrationConfig(
|
||||
}
|
||||
|
||||
return &cloudintegrationtypes.ProviderIntegrationConfig{
|
||||
AWS: cloudintegrationtypes.NewAWSIntegrationConfig(account.Config.AWS.Regions, collectionStrategy),
|
||||
AWS: &cloudintegrationtypes.AWSIntegrationConfig{
|
||||
EnabledRegions: account.Config.AWS.Regions,
|
||||
TelemetryCollectionStrategy: collectionStrategy,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const config: Config.InitialOptions = {
|
||||
'^.+\\.(js|jsx)$': 'babel-jest',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
|
||||
],
|
||||
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
|
||||
testPathIgnorePatterns: ['/node_modules/', '/public/'],
|
||||
|
||||
@@ -24,10 +24,6 @@ window.matchMedia =
|
||||
};
|
||||
};
|
||||
|
||||
if (!HTMLElement.prototype.scrollIntoView) {
|
||||
HTMLElement.prototype.scrollIntoView = function (): void {};
|
||||
}
|
||||
|
||||
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
|
||||
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
|
||||
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
|
||||
|
||||
@@ -48,9 +48,23 @@
|
||||
"@radix-ui/react-tooltip": "1.0.7",
|
||||
"@sentry/react": "8.41.0",
|
||||
"@sentry/vite-plugin": "2.22.6",
|
||||
"@signozhq/button": "0.0.5",
|
||||
"@signozhq/calendar": "0.1.1",
|
||||
"@signozhq/callout": "0.0.4",
|
||||
"@signozhq/checkbox": "0.0.4",
|
||||
"@signozhq/combobox": "0.0.4",
|
||||
"@signozhq/command": "0.0.2",
|
||||
"@signozhq/design-tokens": "2.1.4",
|
||||
"@signozhq/dialog": "0.0.4",
|
||||
"@signozhq/drawer": "0.0.6",
|
||||
"@signozhq/icons": "0.1.0",
|
||||
"@signozhq/ui": "0.0.9",
|
||||
"@signozhq/input": "0.0.4",
|
||||
"@signozhq/popover": "0.1.2",
|
||||
"@signozhq/radio-group": "0.0.4",
|
||||
"@signozhq/resizable": "0.0.2",
|
||||
"@signozhq/table": "0.3.7",
|
||||
"@signozhq/toggle-group": "0.0.3",
|
||||
"@signozhq/ui": "0.0.5",
|
||||
"@tanstack/react-table": "8.21.3",
|
||||
"@tanstack/react-virtual": "3.13.22",
|
||||
"@uiw/codemirror-theme-copilot": "4.23.11",
|
||||
|
||||
@@ -244,18 +244,12 @@ export const ShortcutsPage = Loadable(
|
||||
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
|
||||
);
|
||||
|
||||
export const Integrations = Loadable(
|
||||
export const InstalledIntegrations = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
|
||||
),
|
||||
);
|
||||
export const IntegrationsDetailsPage = Loadable(
|
||||
() =>
|
||||
import(
|
||||
/* webpackChunkName: "IntegrationsDetailsPage" */ 'pages/IntegrationsDetailsPage'
|
||||
),
|
||||
);
|
||||
|
||||
export const MessagingQueuesMainPage = Loadable(
|
||||
() =>
|
||||
|
||||
@@ -18,8 +18,7 @@ import {
|
||||
ForgotPassword,
|
||||
Home,
|
||||
InfrastructureMonitoring,
|
||||
Integrations,
|
||||
IntegrationsDetailsPage,
|
||||
InstalledIntegrations,
|
||||
LicensePage,
|
||||
ListAllALertsPage,
|
||||
LiveLogs,
|
||||
@@ -390,17 +389,10 @@ const routes: AppRoutes[] = [
|
||||
isPrivate: true,
|
||||
key: 'WORKSPACE_ACCESS_RESTRICTED',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS_DETAIL,
|
||||
exact: true,
|
||||
component: IntegrationsDetailsPage,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS_DETAIL',
|
||||
},
|
||||
{
|
||||
path: ROUTES.INTEGRATIONS,
|
||||
exact: true,
|
||||
component: Integrations,
|
||||
component: InstalledIntegrations,
|
||||
isPrivate: true,
|
||||
key: 'INTEGRATIONS',
|
||||
},
|
||||
|
||||
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
19
frontend/src/api/Integrations/removeAwsIntegrationAccount.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import axios from 'api';
|
||||
import { ErrorResponse, SuccessResponse } from 'types/api';
|
||||
|
||||
const removeAwsIntegrationAccount = async (
|
||||
accountId: string,
|
||||
): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
|
||||
const response = await axios.post(
|
||||
`/cloud-integrations/aws/accounts/${accountId}/disconnect`,
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
message: response.data.status,
|
||||
payload: response.data.data,
|
||||
};
|
||||
};
|
||||
|
||||
export default removeAwsIntegrationAccount;
|
||||
@@ -5243,6 +5243,198 @@ export interface Sigv4SigV4ConfigDTO {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesConditionDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
attributes?: string[] | null;
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
resource?: string[] | null;
|
||||
}
|
||||
|
||||
export enum SpanattributemappingtypesFieldContextDTO {
|
||||
attribute = 'attribute',
|
||||
resource = 'resource',
|
||||
}
|
||||
export interface SpanattributemappingtypesGroupDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
category: string;
|
||||
condition: SpanattributemappingtypesConditionDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
ordId: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesListGroupsResponseDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
items: SpanattributemappingtypesGroupDTO[] | null;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesListMappersResponseDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
items: SpanattributemappingtypesMapperDTO[] | null;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesMapperDTO {
|
||||
config: SpanattributemappingtypesMapperConfigDTO;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
createdAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
createdBy?: string;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled: boolean;
|
||||
field_context: SpanattributemappingtypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
group_id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* @type string
|
||||
* @format date-time
|
||||
*/
|
||||
updatedAt?: Date;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
updatedBy?: string;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesMapperConfigDTO {
|
||||
/**
|
||||
* @type array
|
||||
* @nullable true
|
||||
*/
|
||||
sources?: SpanattributemappingtypesMapperSourceDTO[] | null;
|
||||
}
|
||||
|
||||
export enum SpanattributemappingtypesMapperOperationDTO {
|
||||
move = 'move',
|
||||
copy = 'copy',
|
||||
}
|
||||
export interface SpanattributemappingtypesMapperSourceDTO {
|
||||
context?: SpanattributemappingtypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
key?: string;
|
||||
operation?: SpanattributemappingtypesMapperOperationDTO;
|
||||
/**
|
||||
* @type integer
|
||||
*/
|
||||
priority?: number;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesPostableGroupDTO {
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
category: string;
|
||||
condition: SpanattributemappingtypesConditionDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled?: boolean;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesPostableMapperDTO {
|
||||
config: SpanattributemappingtypesMapperConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
*/
|
||||
enabled?: boolean;
|
||||
field_context: SpanattributemappingtypesFieldContextDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesUpdatableGroupDTO {
|
||||
condition?: SpanattributemappingtypesConditionDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
/**
|
||||
* @type string
|
||||
* @nullable true
|
||||
*/
|
||||
name?: string | null;
|
||||
}
|
||||
|
||||
export interface SpanattributemappingtypesUpdatableMapperDTO {
|
||||
config?: SpanattributemappingtypesMapperConfigDTO;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
field_context?: SpanattributemappingtypesFieldContextDTO;
|
||||
}
|
||||
|
||||
export enum TelemetrytypesFieldContextDTO {
|
||||
metric = 'metric',
|
||||
log = 'log',
|
||||
@@ -6462,6 +6654,89 @@ export type GetMyServiceAccount200 = {
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListSpanAttributeMappingGroupsParams = {
|
||||
/**
|
||||
* @type string
|
||||
* @nullable true
|
||||
* @description undefined
|
||||
*/
|
||||
category?: string | null;
|
||||
/**
|
||||
* @type boolean
|
||||
* @nullable true
|
||||
* @description undefined
|
||||
*/
|
||||
enabled?: boolean | null;
|
||||
};
|
||||
|
||||
export type ListSpanAttributeMappingGroups200 = {
|
||||
data: SpanattributemappingtypesListGroupsResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateMappingGroup201 = {
|
||||
data: SpanattributemappingtypesGroupDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteMapperPathParameters = {
|
||||
groupId: string;
|
||||
mapperId: string;
|
||||
};
|
||||
export type UpdateMapperPathParameters = {
|
||||
groupId: string;
|
||||
mapperId: string;
|
||||
};
|
||||
export type UpdateMapper200 = {
|
||||
data: SpanattributemappingtypesMapperDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type DeleteMappingGroupPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateMappingGroupPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type UpdateMappingGroup200 = {
|
||||
data: SpanattributemappingtypesGroupDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListMappersPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type ListMappers200 = {
|
||||
data: SpanattributemappingtypesListMappersResponseDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type CreateMapperPathParameters = {
|
||||
id: string;
|
||||
};
|
||||
export type CreateMapper201 = {
|
||||
data: SpanattributemappingtypesMapperDTO;
|
||||
/**
|
||||
* @type string
|
||||
*/
|
||||
status: string;
|
||||
};
|
||||
|
||||
export type ListUsersDeprecated200 = {
|
||||
/**
|
||||
* @type array
|
||||
|
||||
@@ -0,0 +1,784 @@
|
||||
/**
|
||||
* ! Do not edit manually
|
||||
* * The file has been auto-generated using Orval for SigNoz
|
||||
* * regenerate with 'yarn generate:api'
|
||||
* SigNoz
|
||||
*/
|
||||
import type {
|
||||
InvalidateOptions,
|
||||
MutationFunction,
|
||||
QueryClient,
|
||||
QueryFunction,
|
||||
QueryKey,
|
||||
UseMutationOptions,
|
||||
UseMutationResult,
|
||||
UseQueryOptions,
|
||||
UseQueryResult,
|
||||
} from 'react-query';
|
||||
import { useMutation, useQuery } from 'react-query';
|
||||
|
||||
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
|
||||
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
|
||||
import type {
|
||||
CreateMapper201,
|
||||
CreateMapperPathParameters,
|
||||
CreateMappingGroup201,
|
||||
DeleteMapperPathParameters,
|
||||
DeleteMappingGroupPathParameters,
|
||||
ListMappers200,
|
||||
ListMappersPathParameters,
|
||||
ListSpanAttributeMappingGroups200,
|
||||
ListSpanAttributeMappingGroupsParams,
|
||||
RenderErrorResponseDTO,
|
||||
SpanattributemappingtypesPostableGroupDTO,
|
||||
SpanattributemappingtypesPostableMapperDTO,
|
||||
SpanattributemappingtypesUpdatableGroupDTO,
|
||||
SpanattributemappingtypesUpdatableMapperDTO,
|
||||
UpdateMapper200,
|
||||
UpdateMapperPathParameters,
|
||||
UpdateMappingGroup200,
|
||||
UpdateMappingGroupPathParameters,
|
||||
} from '../sigNoz.schemas';
|
||||
|
||||
/**
|
||||
* Returns all span attribute mapping groups for the authenticated org.
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
export const listSpanAttributeMappingGroups = (
|
||||
params?: ListSpanAttributeMappingGroupsParams,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListSpanAttributeMappingGroups200>({
|
||||
url: `/api/v1/span_attribute_mapping_groups`,
|
||||
method: 'GET',
|
||||
params,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListSpanAttributeMappingGroupsQueryKey = (
|
||||
params?: ListSpanAttributeMappingGroupsParams,
|
||||
) => {
|
||||
return [
|
||||
`/api/v1/span_attribute_mapping_groups`,
|
||||
...(params ? [params] : []),
|
||||
] as const;
|
||||
};
|
||||
|
||||
export const getListSpanAttributeMappingGroupsQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
params?: ListSpanAttributeMappingGroupsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey =
|
||||
queryOptions?.queryKey ?? getListSpanAttributeMappingGroupsQueryKey(params);
|
||||
|
||||
const queryFn: QueryFunction<
|
||||
Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>
|
||||
> = ({ signal }) => listSpanAttributeMappingGroups(params, signal);
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListSpanAttributeMappingGroupsQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>
|
||||
>;
|
||||
export type ListSpanAttributeMappingGroupsQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
|
||||
export function useListSpanAttributeMappingGroups<
|
||||
TData = Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
params?: ListSpanAttributeMappingGroupsParams,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listSpanAttributeMappingGroups>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListSpanAttributeMappingGroupsQueryOptions(
|
||||
params,
|
||||
options,
|
||||
);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List span attribute mapping groups
|
||||
*/
|
||||
export const invalidateListSpanAttributeMappingGroups = async (
|
||||
queryClient: QueryClient,
|
||||
params?: ListSpanAttributeMappingGroupsParams,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListSpanAttributeMappingGroupsQueryKey(params) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new span attribute mapping group for the org.
|
||||
* @summary Create a span attribute mapping group
|
||||
*/
|
||||
export const createMappingGroup = (
|
||||
spanattributemappingtypesPostableGroupDTO: BodyType<SpanattributemappingtypesPostableGroupDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateMappingGroup201>({
|
||||
url: `/api/v1/span_attribute_mapping_groups`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spanattributemappingtypesPostableGroupDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateMappingGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpanattributemappingtypesPostableGroupDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpanattributemappingtypesPostableGroupDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createMappingGroup'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>,
|
||||
{ data: BodyType<SpanattributemappingtypesPostableGroupDTO> }
|
||||
> = (props) => {
|
||||
const { data } = props ?? {};
|
||||
|
||||
return createMappingGroup(data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateMappingGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>
|
||||
>;
|
||||
export type CreateMappingGroupMutationBody = BodyType<SpanattributemappingtypesPostableGroupDTO>;
|
||||
export type CreateMappingGroupMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create a span attribute mapping group
|
||||
*/
|
||||
export const useCreateMappingGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpanattributemappingtypesPostableGroupDTO> },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createMappingGroup>>,
|
||||
TError,
|
||||
{ data: BodyType<SpanattributemappingtypesPostableGroupDTO> },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateMappingGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Hard-deletes a mapper from a mapping group.
|
||||
* @summary Delete a span attribute mapper
|
||||
*/
|
||||
export const deleteMapper = ({
|
||||
groupId,
|
||||
mapperId,
|
||||
}: DeleteMapperPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${groupId}/mappers/${mapperId}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMapperPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMapperPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteMapper'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteMapper>>,
|
||||
{ pathParams: DeleteMapperPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteMapper(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteMapper>>
|
||||
>;
|
||||
|
||||
export type DeleteMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a span attribute mapper
|
||||
*/
|
||||
export const useDeleteMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMapperPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteMapper>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMapperPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Partially updates an existing mapper's field context, config, or enabled state.
|
||||
* @summary Update a span attribute mapper
|
||||
*/
|
||||
export const updateMapper = (
|
||||
{ groupId, mapperId }: UpdateMapperPathParameters,
|
||||
spanattributemappingtypesUpdatableMapperDTO: BodyType<SpanattributemappingtypesUpdatableMapperDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateMapper200>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${groupId}/mappers/${mapperId}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spanattributemappingtypesUpdatableMapperDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateMapper'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateMapper>>,
|
||||
{
|
||||
pathParams: UpdateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateMapper(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateMapper>>
|
||||
>;
|
||||
export type UpdateMapperMutationBody = BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
export type UpdateMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update a span attribute mapper
|
||||
*/
|
||||
export const useUpdateMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Hard-deletes a mapping group and cascades to all its mappers.
|
||||
* @summary Delete a span attribute mapping group
|
||||
*/
|
||||
export const deleteMappingGroup = ({
|
||||
id,
|
||||
}: DeleteMappingGroupPathParameters) => {
|
||||
return GeneratedAPIInstance<void>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${id}`,
|
||||
method: 'DELETE',
|
||||
});
|
||||
};
|
||||
|
||||
export const getDeleteMappingGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMappingGroupPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMappingGroupPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['deleteMappingGroup'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>,
|
||||
{ pathParams: DeleteMappingGroupPathParameters }
|
||||
> = (props) => {
|
||||
const { pathParams } = props ?? {};
|
||||
|
||||
return deleteMappingGroup(pathParams);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type DeleteMappingGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>
|
||||
>;
|
||||
|
||||
export type DeleteMappingGroupMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Delete a span attribute mapping group
|
||||
*/
|
||||
export const useDeleteMappingGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMappingGroupPathParameters },
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof deleteMappingGroup>>,
|
||||
TError,
|
||||
{ pathParams: DeleteMappingGroupPathParameters },
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getDeleteMappingGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Partially updates an existing mapping group's name, condition, or enabled state.
|
||||
* @summary Update a span attribute mapping group
|
||||
*/
|
||||
export const updateMappingGroup = (
|
||||
{ id }: UpdateMappingGroupPathParameters,
|
||||
spanattributemappingtypesUpdatableGroupDTO: BodyType<SpanattributemappingtypesUpdatableGroupDTO>,
|
||||
) => {
|
||||
return GeneratedAPIInstance<UpdateMappingGroup200>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${id}`,
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spanattributemappingtypesUpdatableGroupDTO,
|
||||
});
|
||||
};
|
||||
|
||||
export const getUpdateMappingGroupMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMappingGroupPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMappingGroupPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['updateMappingGroup'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>,
|
||||
{
|
||||
pathParams: UpdateMappingGroupPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return updateMappingGroup(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type UpdateMappingGroupMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>
|
||||
>;
|
||||
export type UpdateMappingGroupMutationBody = BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
export type UpdateMappingGroupMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Update a span attribute mapping group
|
||||
*/
|
||||
export const useUpdateMappingGroup = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMappingGroupPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof updateMappingGroup>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: UpdateMappingGroupPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesUpdatableGroupDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getUpdateMappingGroupMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
/**
|
||||
* Returns all attribute mappers belonging to a mapping group.
|
||||
* @summary List span attribute mappers for a group
|
||||
*/
|
||||
export const listMappers = (
|
||||
{ id }: ListMappersPathParameters,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<ListMappers200>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${id}/mappers`,
|
||||
method: 'GET',
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getListMappersQueryKey = ({ id }: ListMappersPathParameters) => {
|
||||
return [`/api/v1/span_attribute_mapping_groups/${id}/mappers`] as const;
|
||||
};
|
||||
|
||||
export const getListMappersQueryOptions = <
|
||||
TData = Awaited<ReturnType<typeof listMappers>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: ListMappersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listMappers>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
) => {
|
||||
const { query: queryOptions } = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListMappersQueryKey({ id });
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listMappers>>> = ({
|
||||
signal,
|
||||
}) => listMappers({ id }, signal);
|
||||
|
||||
return {
|
||||
queryKey,
|
||||
queryFn,
|
||||
enabled: !!id,
|
||||
...queryOptions,
|
||||
} as UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listMappers>>,
|
||||
TError,
|
||||
TData
|
||||
> & { queryKey: QueryKey };
|
||||
};
|
||||
|
||||
export type ListMappersQueryResult = NonNullable<
|
||||
Awaited<ReturnType<typeof listMappers>>
|
||||
>;
|
||||
export type ListMappersQueryError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary List span attribute mappers for a group
|
||||
*/
|
||||
|
||||
export function useListMappers<
|
||||
TData = Awaited<ReturnType<typeof listMappers>>,
|
||||
TError = ErrorType<RenderErrorResponseDTO>
|
||||
>(
|
||||
{ id }: ListMappersPathParameters,
|
||||
options?: {
|
||||
query?: UseQueryOptions<
|
||||
Awaited<ReturnType<typeof listMappers>>,
|
||||
TError,
|
||||
TData
|
||||
>;
|
||||
},
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
const queryOptions = getListMappersQueryOptions({ id }, options);
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
|
||||
queryKey: QueryKey;
|
||||
};
|
||||
|
||||
query.queryKey = queryOptions.queryKey;
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary List span attribute mappers for a group
|
||||
*/
|
||||
export const invalidateListMappers = async (
|
||||
queryClient: QueryClient,
|
||||
{ id }: ListMappersPathParameters,
|
||||
options?: InvalidateOptions,
|
||||
): Promise<QueryClient> => {
|
||||
await queryClient.invalidateQueries(
|
||||
{ queryKey: getListMappersQueryKey({ id }) },
|
||||
options,
|
||||
);
|
||||
|
||||
return queryClient;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a new attribute mapper to the specified mapping group.
|
||||
* @summary Create a span attribute mapper
|
||||
*/
|
||||
export const createMapper = (
|
||||
{ id }: CreateMapperPathParameters,
|
||||
spanattributemappingtypesPostableMapperDTO: BodyType<SpanattributemappingtypesPostableMapperDTO>,
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
return GeneratedAPIInstance<CreateMapper201>({
|
||||
url: `/api/v1/span_attribute_mapping_groups/${id}/mappers`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: spanattributemappingtypesPostableMapperDTO,
|
||||
signal,
|
||||
});
|
||||
};
|
||||
|
||||
export const getCreateMapperMutationOptions = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationKey = ['createMapper'];
|
||||
const { mutation: mutationOptions } = options
|
||||
? options.mutation &&
|
||||
'mutationKey' in options.mutation &&
|
||||
options.mutation.mutationKey
|
||||
? options
|
||||
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||
: { mutation: { mutationKey } };
|
||||
|
||||
const mutationFn: MutationFunction<
|
||||
Awaited<ReturnType<typeof createMapper>>,
|
||||
{
|
||||
pathParams: CreateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
}
|
||||
> = (props) => {
|
||||
const { pathParams, data } = props ?? {};
|
||||
|
||||
return createMapper(pathParams, data);
|
||||
};
|
||||
|
||||
return { mutationFn, ...mutationOptions };
|
||||
};
|
||||
|
||||
export type CreateMapperMutationResult = NonNullable<
|
||||
Awaited<ReturnType<typeof createMapper>>
|
||||
>;
|
||||
export type CreateMapperMutationBody = BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
export type CreateMapperMutationError = ErrorType<RenderErrorResponseDTO>;
|
||||
|
||||
/**
|
||||
* @summary Create a span attribute mapper
|
||||
*/
|
||||
export const useCreateMapper = <
|
||||
TError = ErrorType<RenderErrorResponseDTO>,
|
||||
TContext = unknown
|
||||
>(options?: {
|
||||
mutation?: UseMutationOptions<
|
||||
Awaited<ReturnType<typeof createMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
>;
|
||||
}): UseMutationResult<
|
||||
Awaited<ReturnType<typeof createMapper>>,
|
||||
TError,
|
||||
{
|
||||
pathParams: CreateMapperPathParameters;
|
||||
data: BodyType<SpanattributemappingtypesPostableMapperDTO>;
|
||||
},
|
||||
TContext
|
||||
> => {
|
||||
const mutationOptions = getCreateMapperMutationOptions(options);
|
||||
|
||||
return useMutation(mutationOptions);
|
||||
};
|
||||
88
frontend/src/api/integration/aws/index.ts
Normal file
88
frontend/src/api/integration/aws/index.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import axios from 'api';
|
||||
import {
|
||||
CloudAccount,
|
||||
Service,
|
||||
ServiceData,
|
||||
UpdateServiceConfigPayload,
|
||||
UpdateServiceConfigResponse,
|
||||
} from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import {
|
||||
AccountConfigPayload,
|
||||
AccountConfigResponse,
|
||||
ConnectionParams,
|
||||
ConnectionUrlResponse,
|
||||
} from 'types/api/integrations/aws';
|
||||
|
||||
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
|
||||
const response = await axios.get('/cloud-integrations/aws/accounts');
|
||||
|
||||
return response.data.data.accounts;
|
||||
};
|
||||
|
||||
export const getAwsServices = async (
|
||||
cloudAccountId?: string,
|
||||
): Promise<Service[]> => {
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
const response = await axios.get('/cloud-integrations/aws/services', {
|
||||
params,
|
||||
});
|
||||
|
||||
return response.data.data.services;
|
||||
};
|
||||
|
||||
export const getServiceDetails = async (
|
||||
serviceId: string,
|
||||
cloudAccountId?: string,
|
||||
): Promise<ServiceData> => {
|
||||
const params = cloudAccountId
|
||||
? { cloud_account_id: cloudAccountId }
|
||||
: undefined;
|
||||
const response = await axios.get(
|
||||
`/cloud-integrations/aws/services/${serviceId}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const generateConnectionUrl = async (params: {
|
||||
agent_config: { region: string };
|
||||
account_config: { regions: string[] };
|
||||
account_id?: string;
|
||||
}): Promise<ConnectionUrlResponse> => {
|
||||
const response = await axios.post(
|
||||
'/cloud-integrations/aws/accounts/generate-connection-url',
|
||||
params,
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
|
||||
export const updateAccountConfig = async (
|
||||
accountId: string,
|
||||
payload: AccountConfigPayload,
|
||||
): Promise<AccountConfigResponse> => {
|
||||
const response = await axios.post<AccountConfigResponse>(
|
||||
`/cloud-integrations/aws/accounts/${accountId}/config`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateServiceConfig = async (
|
||||
serviceId: string,
|
||||
payload: UpdateServiceConfigPayload,
|
||||
): Promise<UpdateServiceConfigResponse> => {
|
||||
const response = await axios.post<UpdateServiceConfigResponse>(
|
||||
`/cloud-integrations/aws/services/${serviceId}/config`,
|
||||
payload,
|
||||
);
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const getConnectionParams = async (): Promise<ConnectionParams> => {
|
||||
const response = await axios.get(
|
||||
'/cloud-integrations/aws/accounts/generate-connection-params',
|
||||
);
|
||||
return response.data.data;
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
<svg width="929" height="8" viewBox="0 0 929 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dotted-double-line-pattern" x="0" y="0" width="6" height="8" patternUnits="userSpaceOnUse">
|
||||
<rect width="2" height="2" rx="1" fill="#242834" />
|
||||
<rect y="6" width="2" height="2" rx="1" fill="#242834" />
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="929" height="8" fill="url(#dotted-double-line-pattern)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 442 B |
14
frontend/src/auto-import-registry.d.ts
vendored
14
frontend/src/auto-import-registry.d.ts
vendored
@@ -10,6 +10,20 @@
|
||||
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
import '@signozhq/button';
|
||||
import '@signozhq/calendar';
|
||||
import '@signozhq/callout';
|
||||
import '@signozhq/checkbox';
|
||||
import '@signozhq/combobox';
|
||||
import '@signozhq/command';
|
||||
import '@signozhq/design-tokens';
|
||||
import '@signozhq/dialog';
|
||||
import '@signozhq/drawer';
|
||||
import '@signozhq/icons';
|
||||
import '@signozhq/input';
|
||||
import '@signozhq/popover';
|
||||
import '@signozhq/radio-group';
|
||||
import '@signozhq/resizable';
|
||||
import '@signozhq/table';
|
||||
import '@signozhq/toggle-group';
|
||||
import '@signozhq/ui';
|
||||
|
||||
@@ -80,12 +80,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback } from 'react';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { LifeBuoy } from 'lucide-react';
|
||||
|
||||
import signozBrandLogoUrl from '@/assets/Logos/signoz-brand-logo.svg';
|
||||
@@ -23,10 +23,8 @@ function AuthHeader(): JSX.Element {
|
||||
</div>
|
||||
<Button
|
||||
className="auth-header-help-button"
|
||||
prefix={<LifeBuoy size={12} />}
|
||||
prefixIcon={<LifeBuoy size={12} />}
|
||||
onClick={handleGetHelp}
|
||||
variant="solid"
|
||||
color="none"
|
||||
>
|
||||
Get Help
|
||||
</Button>
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
.masked-dots {
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0%,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0%,
|
||||
transparent 56.77%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
.cloud-service-data-collected {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
.cloud-service-data-collected-table {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.cloud-service-data-collected-table-heading {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
color: var(--l2-foreground);
|
||||
|
||||
/* Bifrost (Ancient)/Content/sm */
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
.cloud-service-data-collected-table-logs {
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--l3-background);
|
||||
background: var(--l1-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,10 @@
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { X } from '@signozhq/icons';
|
||||
import {
|
||||
Button,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccounts,
|
||||
@@ -140,7 +137,6 @@ function CreateServiceAccountModal(): JSX.Element {
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form="create-sa-form"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import CreateServiceAccountModal from '../CreateServiceAccountModal';
|
||||
|
||||
@@ -127,12 +121,12 @@ describe('CreateServiceAccountModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
const dialog = await screen.findByRole('dialog', {
|
||||
name: /New Service Account/i,
|
||||
});
|
||||
await screen.findByRole('dialog', { name: /New Service Account/i });
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /New Service Account/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows "Name is required" after clearing the name field', async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Calendar } from '@signozhq/ui';
|
||||
import { Calendar } from '@signozhq/calendar';
|
||||
import { Button } from 'antd';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Input, InputRef, Popover, Tooltip } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
@@ -661,9 +661,7 @@ function CustomTimePicker({
|
||||
onClick={handleZoomOut}
|
||||
disabled={zoomOutDisabled}
|
||||
data-testid="zoom-out-btn"
|
||||
prefix={<ZoomOut size={14} />}
|
||||
variant="solid"
|
||||
color="none"
|
||||
prefixIcon={<ZoomOut size={14} />}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button, DialogFooter, DialogWrapper } from '@signozhq/ui';
|
||||
import { MemberRow } from 'components/MembersTable/MembersTable';
|
||||
|
||||
interface DeleteMemberDialogProps {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
.edit-member-drawer {
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
padding: var(--padding-5) var(--padding-4);
|
||||
}
|
||||
|
||||
&__field {
|
||||
@@ -113,6 +120,11 @@
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
padding: 0 var(--padding-4);
|
||||
border-top: 1px solid var(--l1-border);
|
||||
flex-shrink: 0;
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
&__footer-left {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
|
||||
import { Badge, Button, DrawerWrapper, Input, toast } from '@signozhq/ui';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Badge, toast } from '@signozhq/ui';
|
||||
import { Skeleton, Tooltip } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -593,69 +596,65 @@ function EditMemberDrawer({
|
||||
const drawerContent = (
|
||||
<div className="edit-member-drawer__layout">
|
||||
<div className="edit-member-drawer__body">{drawerBody}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="edit-member-drawer__footer">
|
||||
<div className="edit-member-drawer__footer-left">
|
||||
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
variant="solid"
|
||||
color="none"
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
{!isDeleted && (
|
||||
<div className="edit-member-drawer__footer">
|
||||
<div className="edit-member-drawer__footer-left">
|
||||
<Tooltip title={getDeleteTooltip(isRootUser, isSelf)}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--danger"
|
||||
onClick={(): void => setShowDeleteConfirm(true)}
|
||||
disabled={isRootUser || isSelf}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
{isInvited ? 'Revoke Invite' : 'Delete Member'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<div className="edit-member-drawer__footer-divider" />
|
||||
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="edit-member-drawer__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
|
||||
<div className="edit-member-drawer__footer-divider" />
|
||||
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
|
||||
<span className="edit-member-drawer__tooltip-wrapper">
|
||||
<Button
|
||||
className="edit-member-drawer__footer-btn edit-member-drawer__footer-btn--warning"
|
||||
onClick={handleGenerateResetLink}
|
||||
disabled={isGeneratingLink || isRootUser || isLoadingTokenStatus}
|
||||
variant="solid"
|
||||
color="none"
|
||||
color="primary"
|
||||
size="sm"
|
||||
disabled={!isDirty || isSaving || isRootUser}
|
||||
onClick={handleSave}
|
||||
>
|
||||
<RefreshCw size={12} />
|
||||
{isGeneratingLink
|
||||
? 'Generating...'
|
||||
: isInvited
|
||||
? getInviteButtonLabel(
|
||||
isLoadingTokenStatus,
|
||||
existingToken,
|
||||
isTokenExpired,
|
||||
tokenNotFound,
|
||||
)
|
||||
: 'Generate Password Reset Link'}
|
||||
{isSaving ? 'Saving...' : 'Save Member Details'}
|
||||
</Button>
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<div className="edit-member-drawer__footer-right">
|
||||
<Button variant="solid" color="secondary" size="sm" onClick={handleClose}>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
disabled={!isDirty || isSaving || isRootUser}
|
||||
onClick={handleSave}
|
||||
>
|
||||
{isSaving ? 'Saving...' : 'Save Member Details'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -669,14 +668,14 @@ function EditMemberDrawer({
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
title="Member Details"
|
||||
footer={footer}
|
||||
width="wide"
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
allowOutsideClick
|
||||
header={{ title: 'Member Details' }}
|
||||
content={drawerContent}
|
||||
className="edit-member-drawer"
|
||||
/>
|
||||
|
||||
<ResetLinkDialog
|
||||
open={showResetLinkDialog}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Button, DialogWrapper } from '@signozhq/ui';
|
||||
|
||||
interface ResetLinkDialogProps {
|
||||
open: boolean;
|
||||
@@ -48,7 +49,7 @@ function ResetLinkDialog({
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={onCopy}
|
||||
prefix={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
prefixIcon={hasCopied ? <Check size={12} /> : <Copy size={12} />}
|
||||
className="reset-link-dialog__copy-btn"
|
||||
>
|
||||
{hasCopied ? 'Copied!' : 'Copy'}
|
||||
|
||||
@@ -20,29 +20,17 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import EditMemberDrawer, { EditMemberDrawerProps } from '../EditMemberDrawer';
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useSetRoleByUserID: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
convertToApiError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
jest.mock('@signozhq/drawer', () => ({
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
content?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null => (open ? <div>{children}</div> : null),
|
||||
}): JSX.Element | null => (open ? <div>{content}</div> : null),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/dialog', () => ({
|
||||
DialogWrapper: ({
|
||||
children,
|
||||
open,
|
||||
@@ -60,6 +48,24 @@ jest.mock('@signozhq/ui', () => ({
|
||||
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
|
||||
<div>{children}</div>
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('api/generated/services/users', () => ({
|
||||
useDeleteUser: jest.fn(),
|
||||
useGetUser: jest.fn(),
|
||||
useUpdateUser: jest.fn(),
|
||||
useUpdateMyUserV2: jest.fn(),
|
||||
useSetRoleByUserID: jest.fn(),
|
||||
useGetResetPasswordToken: jest.fn(),
|
||||
useCreateResetPasswordToken: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
|
||||
convertToApiError: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: {
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
&__wrap {
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
color-mix(in srgb, var(--l1-background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
|
||||
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
|
||||
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
|
||||
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
|
||||
@@ -40,17 +40,15 @@
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 0;
|
||||
background: var(--l2-background);
|
||||
overflow: hidden;
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
background: none !important;
|
||||
|
||||
.ant-modal-title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -82,7 +80,6 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
padding: 3px 7px;
|
||||
background: var(--l2-background);
|
||||
@@ -93,15 +90,15 @@
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin: 0 !important;
|
||||
height: 6px;
|
||||
background: var(--bg-sakura-500);
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 !important;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useLocation } from 'react-router-dom';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
|
||||
const [activeTab, setActiveTab] = useState('feedback');
|
||||
|
||||
@@ -4,8 +4,8 @@ import { toast } from '@signozhq/ui';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { handleContactSupport } from 'container/Integrations/utils';
|
||||
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
|
||||
import { handleContactSupport } from 'pages/Integrations/utils';
|
||||
|
||||
import FeedbackModal from '../FeedbackModal';
|
||||
|
||||
@@ -31,7 +31,7 @@ jest.mock('hooks/useGetTenantLicense', () => ({
|
||||
useGetTenantLicense: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('container/Integrations/utils', () => ({
|
||||
jest.mock('pages/Integrations/utils', () => ({
|
||||
handleContactSupport: jest.fn(),
|
||||
}));
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
|
||||
.label {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
@@ -20,9 +21,8 @@
|
||||
padding: 0px 8px;
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
color: var(--l2-foreground);
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
@@ -37,7 +37,6 @@
|
||||
|
||||
border-radius: 2px 0px 0px 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
|
||||
border-right: none;
|
||||
border-left: none;
|
||||
@@ -47,7 +46,6 @@
|
||||
border-bottom-left-radius: 0px;
|
||||
font-size: 12px !important;
|
||||
line-height: 27px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--l2-foreground) !important;
|
||||
font-size: 12px !important;
|
||||
@@ -63,8 +61,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
}
|
||||
@@ -73,7 +71,7 @@
|
||||
.input {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
background: var(--l3-background);
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Style } from '@signozhq/design-tokens';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { ChevronDown, CircleAlert, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import {
|
||||
Button,
|
||||
Callout,
|
||||
DialogFooter,
|
||||
DialogWrapper,
|
||||
Input,
|
||||
toast,
|
||||
} from '@signozhq/ui';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Select } from 'antd';
|
||||
import inviteUsers from 'api/v1/invite/bulk/create';
|
||||
import sendInvite from 'api/v1/invite/create';
|
||||
@@ -298,9 +295,8 @@ function InviteMembersModal({
|
||||
showIcon
|
||||
icon={<CircleAlert size={12} />}
|
||||
className="invite-team-members-error-callout"
|
||||
>
|
||||
{getValidationErrorMessage()}
|
||||
</Callout>
|
||||
description={getValidationErrorMessage()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -310,7 +306,7 @@ function InviteMembersModal({
|
||||
color="secondary"
|
||||
size="sm"
|
||||
className="add-another-member-button"
|
||||
prefix={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
prefixIcon={<Plus size={12} color={Style.L1_FOREGROUND} />}
|
||||
onClick={addRow}
|
||||
>
|
||||
Add another
|
||||
|
||||
@@ -100,19 +100,16 @@ function MarkdownRenderer({
|
||||
variables,
|
||||
trackCopyAction,
|
||||
elementDetails,
|
||||
className,
|
||||
}: {
|
||||
markdownContent: any;
|
||||
variables: any;
|
||||
trackCopyAction?: boolean;
|
||||
elementDetails?: Record<string, unknown>;
|
||||
className?: string;
|
||||
}): JSX.Element {
|
||||
const interpolatedMarkdown = interpolateMarkdown(markdownContent, variables);
|
||||
|
||||
return (
|
||||
<ReactMarkdown
|
||||
className={className}
|
||||
rehypePlugins={[rehypeRaw as any]}
|
||||
components={{
|
||||
// @ts-ignore
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.ant-table-thead {
|
||||
> tr > th,
|
||||
> tr > td {
|
||||
background: var(--l1-background);
|
||||
background: var(--background);
|
||||
font-size: var(--paragraph-small-600-font-size);
|
||||
font-weight: var(--paragraph-small-600-font-weight);
|
||||
line-height: var(--paragraph-small-600-line-height);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-builder-v2 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -276,7 +274,7 @@
|
||||
.ant-input-group-addon {
|
||||
border-top-left-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background: var(--l2-background);
|
||||
background: var(--l3-background);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 300;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-add-ons {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -104,7 +102,7 @@
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
padding: 0px !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
background-color: var(--card) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border);
|
||||
@@ -213,7 +211,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
background-color: var(--card) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -251,8 +249,8 @@
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l2-border);
|
||||
background: var(--l2-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
height: 38px;
|
||||
width: 38px;
|
||||
|
||||
@@ -286,3 +284,108 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.add-ons-list {
|
||||
.add-ons-tabs {
|
||||
.add-on-tab-title {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.tab {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:first-child {
|
||||
border-left: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.selected-view {
|
||||
color: var(--primary-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.selected-view::before {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.compass-button {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.having-filter-container {
|
||||
.having-filter-select-container {
|
||||
.having-filter-select-editor {
|
||||
.cm-editor {
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
|
||||
.cm-content {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
|
||||
ul {
|
||||
li {
|
||||
color: var(--l1-foreground) !important;
|
||||
&:hover {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
&[aria-selected='true'] {
|
||||
background: var(--l3-background) !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.chip-decorator {
|
||||
background: var(--l3-background) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.query-aggregation-container {
|
||||
display: block;
|
||||
|
||||
@@ -142,7 +140,7 @@
|
||||
.cm-line {
|
||||
line-height: 36px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -198,7 +196,6 @@
|
||||
min-width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
border-radius: 0px 2px 2px 0px;
|
||||
border: 1px solid var(--l1-border);
|
||||
@@ -273,7 +270,7 @@
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--l2-background) !important;
|
||||
background: var(--l1-background) !important;
|
||||
opacity: 0.5 !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// TODO: Improve the styling of the query aggregation container and its components. - @YounixM , @H4ad
|
||||
|
||||
.code-mirror-where-clause {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
@@ -158,7 +156,7 @@
|
||||
.cm-line {
|
||||
line-height: 34px !important;
|
||||
font-family: 'Space Mono', monospace !important;
|
||||
background-color: var(--l2-background) !important;
|
||||
background-color: var(--l1-background) !important;
|
||||
|
||||
::-moz-selection {
|
||||
background: var(--l3-background) !important;
|
||||
@@ -456,3 +454,30 @@
|
||||
margin-top: -6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.code-mirror-where-clause {
|
||||
.cm-editor {
|
||||
.cm-tooltip-autocomplete {
|
||||
background: var(--l1-background) !important;
|
||||
border: 1px solid var(--l1-border);
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1) !important;
|
||||
}
|
||||
|
||||
.cm-line {
|
||||
::-moz-selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cm-selectionBackground {
|
||||
background: var(--bg-robin-200) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +158,12 @@
|
||||
|
||||
mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
-webkit-mask-image: radial-gradient(
|
||||
circle at 50% 0,
|
||||
color-mix(in srgb, var(--l1-background) 10%, transparent) 0,
|
||||
color-mix(in srgb, var(--background) 10%, transparent) 0,
|
||||
transparent 100%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
ComboboxItem,
|
||||
ComboboxList,
|
||||
ComboboxTrigger,
|
||||
} from '@signozhq/ui';
|
||||
} from '@signozhq/combobox';
|
||||
import { Skeleton, Switch, Tooltip, Typography } from 'antd';
|
||||
import getLocalStorageKey from 'api/browser/localstorage/get';
|
||||
import setLocalStorageKey from 'api/browser/localstorage/set';
|
||||
@@ -200,6 +200,7 @@ export default function QuickFilters(props: IQuickFiltersProps): JSX.Element {
|
||||
setOpen(false);
|
||||
}}
|
||||
isSelected={validQueryIndex === option.value}
|
||||
showCheck={false}
|
||||
>
|
||||
{option.label}
|
||||
</ComboboxItem>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Callout } from '@signozhq/callout';
|
||||
import { Check, Copy } from '@signozhq/icons';
|
||||
import { Badge, Button, Callout } from '@signozhq/ui';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyWithKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
|
||||
export interface KeyCreatedPhaseProps {
|
||||
@@ -41,7 +43,7 @@ function KeyCreatedPhase({
|
||||
<Callout
|
||||
type="info"
|
||||
showIcon
|
||||
title="Store the key securely. This is the only time it will be displayed."
|
||||
message="Store the key securely. This is the only time it will be displayed."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button, Input, ToggleGroup, ToggleGroupItem } from '@signozhq/ui';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { DatePicker } from 'antd';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
@@ -54,7 +56,7 @@ function KeyFormPhase({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onChange={(val): void => {
|
||||
onValueChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
@@ -110,7 +112,6 @@ function KeyFormPhase({
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -2,7 +2,8 @@ import { useCallback, useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { useCopyToClipboard } from 'react-use';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button, DialogFooter, DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getGetServiceAccountQueryKey,
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import type { Control, UseFormRegister } from 'react-hook-form';
|
||||
import { Controller } from 'react-hook-form';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { LockKeyhole, Trash2, X } from '@signozhq/icons';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Input,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@signozhq/ui';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import { DatePicker } from 'antd';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
@@ -75,7 +72,7 @@ function EditKeyForm({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={field.value}
|
||||
onChange={(val): void => {
|
||||
onValueChange={(val): void => {
|
||||
if (val) {
|
||||
field.onChange(val);
|
||||
}
|
||||
@@ -150,7 +147,6 @@ function EditKeyForm({
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
// @ts-expect-error -- form prop not in @signozhq/ui Button type - TODO: Fix this - @SagarRajput
|
||||
form={FORM_ID}
|
||||
variant="solid"
|
||||
color="primary"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { DialogWrapper } from '@signozhq/dialog';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
invalidateListServiceAccountKeys,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { KeyRound, X } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Skeleton, Table, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table/interface';
|
||||
import type { ServiceaccounttypesGettableFactorAPIKeyDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
@@ -96,7 +96,7 @@ function buildColumns({
|
||||
<Tooltip title={isDisabled ? 'Service account disabled' : 'Revoke Key'}>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
size="xs"
|
||||
color="destructive"
|
||||
disabled={isDisabled}
|
||||
onClick={(e): void => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback } from 'react';
|
||||
import { LockKeyhole } from '@signozhq/icons';
|
||||
import { Badge, Input } from '@signozhq/ui';
|
||||
import { Input } from '@signozhq/input';
|
||||
import { Badge } from '@signozhq/ui';
|
||||
import type { AuthtypesRoleDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import RolesSelect from 'components/RolesSelect';
|
||||
import { DATE_TIME_FORMATS } from 'constants/dateTimeFormats';
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
|
||||
import { Trash2, X } from '@signozhq/icons';
|
||||
import { Button, DialogFooter, DialogWrapper, toast } from '@signozhq/ui';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
getListServiceAccountKeysQueryKey,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { ChevronDown, ChevronUp, CircleAlert, RotateCw } from '@signozhq/icons';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
|
||||
import APIError from 'types/api/error';
|
||||
|
||||
@@ -42,7 +42,7 @@ function SaveErrorItem({
|
||||
<Button
|
||||
type="button"
|
||||
aria-label="Retry"
|
||||
size="sm"
|
||||
size="xs"
|
||||
onClick={async (e): Promise<void> => {
|
||||
e.stopPropagation();
|
||||
setIsRetrying(true);
|
||||
|
||||
@@ -5,21 +5,31 @@
|
||||
margin-left: var(--margin-2);
|
||||
}
|
||||
|
||||
&__layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: var(--padding-3) var(--padding-4) var(--padding-2) var(--padding-4);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__tab-group {
|
||||
[data-slot='toggle-group'] {
|
||||
height: 32px;
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l2-background);
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
[data-slot='toggle-group-item'] {
|
||||
height: 32px;
|
||||
border-radius: 0;
|
||||
border-left: 1px solid var(--l1-border);
|
||||
background: transparent;
|
||||
@@ -30,7 +40,6 @@
|
||||
padding: 0 var(--padding-7);
|
||||
gap: var(--spacing-3);
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
|
||||
&:first-child {
|
||||
border-left: none;
|
||||
@@ -79,7 +88,7 @@
|
||||
&__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-top: var(--padding-5);
|
||||
padding: var(--padding-5) var(--padding-4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-8);
|
||||
@@ -103,11 +112,14 @@
|
||||
}
|
||||
|
||||
&__footer {
|
||||
height: 56px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0 var(--padding-4);
|
||||
border-top: 1px solid var(--secondary);
|
||||
background: var(--card);
|
||||
}
|
||||
|
||||
&__keys-pagination {
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Button } from '@signozhq/button';
|
||||
import { DrawerWrapper } from '@signozhq/drawer';
|
||||
import { Key, LayoutGrid, Plus, Trash2, X } from '@signozhq/icons';
|
||||
import {
|
||||
Button,
|
||||
DrawerWrapper,
|
||||
toast,
|
||||
ToggleGroup,
|
||||
ToggleGroupItem,
|
||||
} from '@signozhq/ui';
|
||||
import { ToggleGroup, ToggleGroupItem } from '@signozhq/toggle-group';
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { Pagination, Skeleton } from 'antd';
|
||||
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
|
||||
import {
|
||||
@@ -382,7 +379,7 @@ function ServiceAccountDrawer({
|
||||
<ToggleGroup
|
||||
type="single"
|
||||
value={activeTab}
|
||||
onChange={(val): void => {
|
||||
onValueChange={(val): void => {
|
||||
if (val) {
|
||||
setActiveTab(val as ServiceAccountDrawerTab);
|
||||
if (val !== ServiceAccountDrawerTab.Keys) {
|
||||
@@ -474,71 +471,69 @@ function ServiceAccountDrawer({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const footer = (
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!isDeleted && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
className="sa-drawer__footer-btn"
|
||||
onClick={(): void => {
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
</Button>
|
||||
)}
|
||||
{!isDeleted && (
|
||||
<div className="sa-drawer__footer-right">
|
||||
<div className="sa-drawer__footer">
|
||||
{activeTab === ServiceAccountDrawerTab.Keys ? (
|
||||
<Pagination
|
||||
current={keysPage}
|
||||
pageSize={PAGE_SIZE}
|
||||
total={keys.length}
|
||||
showTotal={(total: number, range: number[]): JSX.Element => (
|
||||
<>
|
||||
<span className="sa-drawer__pagination-range">
|
||||
{range[0]} — {range[1]}
|
||||
</span>
|
||||
<span className="sa-drawer__pagination-total"> of {total}</span>
|
||||
</>
|
||||
)}
|
||||
showSizeChanger={false}
|
||||
hideOnSinglePage
|
||||
onChange={(page): void => {
|
||||
void setKeysPage(page);
|
||||
}}
|
||||
className="sa-drawer__keys-pagination"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{!isDeleted && (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClose}
|
||||
variant="ghost"
|
||||
color="destructive"
|
||||
className="sa-drawer__footer-btn"
|
||||
onClick={(): void => {
|
||||
setIsDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
<Trash2 size={12} />
|
||||
Delete Service Account
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
)}
|
||||
{!isDeleted && (
|
||||
<div className="sa-drawer__footer-right">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="secondary"
|
||||
size="sm"
|
||||
onClick={handleClose}
|
||||
>
|
||||
<X size={14} />
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
size="sm"
|
||||
loading={isSaving}
|
||||
disabled={!isDirty}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -552,15 +547,14 @@ function ServiceAccountDrawer({
|
||||
}
|
||||
}}
|
||||
direction="right"
|
||||
type="panel"
|
||||
showCloseButton
|
||||
showOverlay={false}
|
||||
title="Service Account Details"
|
||||
allowOutsideClick
|
||||
header={{ title: 'Service Account Details' }}
|
||||
content={drawerContent}
|
||||
className="sa-drawer"
|
||||
width="wide"
|
||||
footer={footer}
|
||||
>
|
||||
{drawerContent}
|
||||
</DrawerWrapper>
|
||||
/>
|
||||
|
||||
<DeleteAccountModal />
|
||||
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { toast } from '@signozhq/ui';
|
||||
import { rest, server } from 'mocks-server/server';
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
|
||||
import {
|
||||
render,
|
||||
screen,
|
||||
userEvent,
|
||||
waitFor,
|
||||
waitForElementToBeRemoved,
|
||||
} from 'tests/test-utils';
|
||||
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import AddKeyModal from '../AddKeyModal';
|
||||
|
||||
@@ -134,9 +128,11 @@ describe('AddKeyModal', () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
renderModal();
|
||||
|
||||
const dialog = await screen.findByRole('dialog', { name: /Add a New Key/i });
|
||||
await screen.findByRole('dialog', { name: /Add a New Key/i });
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
await waitForElementToBeRemoved(dialog);
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Add a New Key/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,14 +29,9 @@ function renderModal(
|
||||
account: 'sa-1',
|
||||
'edit-key': 'key-1',
|
||||
},
|
||||
onUrlUpdate?: jest.Mock,
|
||||
): ReturnType<typeof render> {
|
||||
return render(
|
||||
<NuqsTestingAdapter
|
||||
searchParams={searchParams}
|
||||
hasMemory
|
||||
onUrlUpdate={onUrlUpdate}
|
||||
>
|
||||
<NuqsTestingAdapter searchParams={searchParams} hasMemory>
|
||||
<EditKeyModal keyItem={keyItem} />
|
||||
</NuqsTestingAdapter>,
|
||||
);
|
||||
@@ -102,31 +97,14 @@ describe('EditKeyModal (URL-controlled)', () => {
|
||||
|
||||
it('cancel clears edit-key param and closes modal', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
const onUrlUpdate = jest.fn();
|
||||
renderModal(mockKey, undefined, onUrlUpdate);
|
||||
renderModal();
|
||||
|
||||
await screen.findByDisplayValue('Original Key Name');
|
||||
await user.click(screen.getByRole('button', { name: /Cancel/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onUrlUpdate).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const latestUrlUpdate =
|
||||
onUrlUpdate.mock.calls[onUrlUpdate.mock.calls.length - 1]?.[0];
|
||||
expect(latestUrlUpdate).toEqual(
|
||||
expect.objectContaining({
|
||||
queryString: expect.any(String),
|
||||
}),
|
||||
);
|
||||
expect(latestUrlUpdate.queryString).toContain('account=sa-1');
|
||||
expect(latestUrlUpdate.queryString).not.toContain('edit-key=');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
expect(
|
||||
screen.queryByRole('dialog', { name: /Edit Key Details/i }),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('revoke flow: clicking Revoke Key shows confirmation inside same dialog', async () => {
|
||||
|
||||
@@ -6,15 +6,18 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
|
||||
|
||||
import ServiceAccountDrawer from '../ServiceAccountDrawer';
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
jest.mock('@signozhq/drawer', () => ({
|
||||
DrawerWrapper: ({
|
||||
children,
|
||||
content,
|
||||
open,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
content?: ReactNode;
|
||||
open: boolean;
|
||||
}): JSX.Element | null => (open ? <div>{children}</div> : null),
|
||||
}): JSX.Element | null => (open ? <div>{content}</div> : null),
|
||||
}));
|
||||
|
||||
jest.mock('@signozhq/ui', () => ({
|
||||
...jest.requireActual('@signozhq/ui'),
|
||||
toast: { success: jest.fn(), error: jest.fn() },
|
||||
}));
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
.ant-table-thead {
|
||||
> tr > th,
|
||||
> tr > td {
|
||||
background: var(--l1-background);
|
||||
background: var(--background);
|
||||
font-size: var(--paragraph-small-600-font-size);
|
||||
font-weight: var(--paragraph-small-600-font-weight);
|
||||
line-height: var(--paragraph-small-600-line-height);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
gap: 20px;
|
||||
padding: 8px 12px;
|
||||
|
||||
background: var(--l1-background);
|
||||
background: var(--background);
|
||||
color: var(--l2-foreground);
|
||||
|
||||
border-radius: 8px;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
.warning-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
// === SECTION: Summary (Top)
|
||||
&__summary-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&__summary {
|
||||
|
||||
@@ -161,11 +161,10 @@ describe('CmdKPalette', () => {
|
||||
});
|
||||
|
||||
test('clicking a navigation item calls history.push with correct route', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const homeItem = screen.getByText(HOME_LABEL);
|
||||
await user.click(homeItem);
|
||||
await userEvent.click(homeItem);
|
||||
|
||||
expect(history.push).toHaveBeenCalledWith(ROUTES.HOME);
|
||||
});
|
||||
@@ -195,11 +194,10 @@ describe('CmdKPalette', () => {
|
||||
});
|
||||
|
||||
test('closing the palette via handleInvoke sets open to false', async () => {
|
||||
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
||||
render(<CmdKPalette userRole="ADMIN" />);
|
||||
|
||||
const dashItem = screen.getByText('Go to Dashboards');
|
||||
await user.click(dashItem);
|
||||
await userEvent.click(dashItem);
|
||||
|
||||
// last call from handleInvoke should set open to false
|
||||
expect(mockSetOpen).toHaveBeenCalledWith(false);
|
||||
|
||||
@@ -7,14 +7,6 @@
|
||||
[data-slot='dialog-content'] {
|
||||
position: fixed;
|
||||
z-index: 60;
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
/* Override the background and color of the dialog content from the theme */
|
||||
> div {
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.cmdk-section-heading [cmdk-group-heading] {
|
||||
@@ -41,7 +33,7 @@
|
||||
|
||||
.cmdk-item-light:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--l1-background) !important;
|
||||
background-color: var(--background) !important;
|
||||
}
|
||||
|
||||
.cmdk-item-light[data-selected='true'] {
|
||||
@@ -51,22 +43,6 @@
|
||||
|
||||
.cmdk-item {
|
||||
cursor: pointer;
|
||||
color: var(--l1-foreground);
|
||||
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
|
||||
&:hover {
|
||||
background: var(--l1-background-hover);
|
||||
}
|
||||
|
||||
&[data-selected='true'] {
|
||||
background: var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
[cmdk-item] svg {
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandShortcut,
|
||||
} from '@signozhq/ui';
|
||||
} from '@signozhq/command';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useThemeMode } from 'hooks/useDarkMode';
|
||||
import history from 'lib/history';
|
||||
|
||||
@@ -65,7 +65,6 @@ const ROUTES = {
|
||||
WORKSPACE_SUSPENDED: '/workspace-suspended',
|
||||
SHORTCUTS: '/settings/shortcuts',
|
||||
INTEGRATIONS: '/integrations',
|
||||
INTEGRATIONS_DETAIL: '/integrations/:integrationId',
|
||||
MESSAGING_QUEUES_BASE: '/messaging-queues',
|
||||
MESSAGING_QUEUES_KAFKA: '/messaging-queues/kafka',
|
||||
MESSAGING_QUEUES_KAFKA_DETAIL: '/messaging-queues/kafka/detail',
|
||||
|
||||
@@ -50,7 +50,6 @@
|
||||
.app-content {
|
||||
width: calc(100% - 54px); // width of the sidebar
|
||||
z-index: 0;
|
||||
background: var(--l1-background);
|
||||
|
||||
margin: 0 auto;
|
||||
|
||||
@@ -140,6 +139,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
.isDarkMode {
|
||||
.app-layout {
|
||||
.app-content {
|
||||
background: var(--background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isLightMode {
|
||||
.app-layout {
|
||||
.app-content {
|
||||
background: var(--card);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trial-expiry-banner,
|
||||
.slow-api-warning-banner,
|
||||
.workspace-restricted-banner {
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
IntegrationType,
|
||||
RequestIntegrationBtn,
|
||||
} from 'pages/Integrations/RequestIntegrationBtn';
|
||||
|
||||
import Header from './Header/Header';
|
||||
import HeroSection from './HeroSection/HeroSection';
|
||||
import ServicesTabs from './ServicesSection/ServicesTabs';
|
||||
|
||||
function CloudIntegrationPage(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
<Header />
|
||||
<HeroSection />
|
||||
<RequestIntegrationBtn
|
||||
type={IntegrationType.AWS_SERVICES}
|
||||
message="Can't find the AWS service you're looking for? Request more integrations"
|
||||
/>
|
||||
<ServicesTabs />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloudIntegrationPage;
|
||||
@@ -3,7 +3,7 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
|
||||
&__navigation {
|
||||
display: flex;
|
||||
@@ -18,7 +18,7 @@
|
||||
}
|
||||
|
||||
&__breadcrumb-title {
|
||||
color: var(--l1-foreground);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 14px;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
@@ -30,8 +30,8 @@
|
||||
justify-content: center;
|
||||
padding: 6px;
|
||||
gap: 6px;
|
||||
border: 1px solid var(--border);
|
||||
background: var(--card);
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l3-background);
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
line-height: 10px;
|
||||
@@ -39,11 +39,9 @@
|
||||
width: 113px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&,
|
||||
&:hover {
|
||||
border-color: var(--l2-border);
|
||||
color: var(--l1-foreground);
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import Breadcrumb from 'antd/es/breadcrumb';
|
||||
import { Breadcrumb } from 'antd';
|
||||
import ROUTES from 'constants/routes';
|
||||
import { IntegrationType } from 'container/Integrations/types';
|
||||
import { Blocks, LifeBuoy } from 'lucide-react';
|
||||
|
||||
import './Header.styles.scss';
|
||||
|
||||
function Header({ title }: { title: IntegrationType }): JSX.Element {
|
||||
function Header(): JSX.Element {
|
||||
return (
|
||||
<div className="cloud-header">
|
||||
<div className="cloud-header__navigation">
|
||||
@@ -18,33 +16,32 @@ function Header({ title }: { title: IntegrationType }): JSX.Element {
|
||||
title: (
|
||||
<Link to={ROUTES.INTEGRATIONS}>
|
||||
<span className="cloud-header__breadcrumb-link">
|
||||
<Blocks size={16} color="var(--l2-foreground)" />
|
||||
<Blocks size={16} color="var(--bg-vanilla-400)" />
|
||||
<span className="cloud-header__breadcrumb-title">Integrations</span>
|
||||
</span>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: <div className="cloud-header__breadcrumb-title">{title}</div>,
|
||||
title: (
|
||||
<div className="cloud-header__breadcrumb-title">
|
||||
Amazon Web Services
|
||||
</div>
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div className="cloud-header__actions">
|
||||
<Button
|
||||
variant="solid"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
onClick={(): void => {
|
||||
window.open(
|
||||
'https://signoz.io/blog/native-aws-integrations-with-autodiscovery/',
|
||||
'_blank',
|
||||
);
|
||||
}}
|
||||
prefix={<LifeBuoy size={12} />}
|
||||
<a
|
||||
href="https://signoz.io/blog/native-aws-integrations-with-autodiscovery/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="cloud-header__help"
|
||||
>
|
||||
<LifeBuoy size={12} />
|
||||
Get Help
|
||||
</Button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -1,10 +1,8 @@
|
||||
.hero-section {
|
||||
padding: 16px;
|
||||
|
||||
height: 308px;
|
||||
padding: 26px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
|
||||
gap: 24px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-position: right;
|
||||
@@ -32,36 +30,7 @@
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
&__icon {
|
||||
height: fit-content;
|
||||
background-color: var(--l1-background);
|
||||
padding: 12px;
|
||||
border: 1px solid var(--l2-background);
|
||||
border-radius: 6px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.12px;
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--l2-foreground);
|
||||
}
|
||||
|
||||
&-title {
|
||||
.title {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
@@ -69,7 +38,7 @@
|
||||
letter-spacing: -0.12px;
|
||||
}
|
||||
|
||||
&-description {
|
||||
.description {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
@@ -77,3 +46,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.integrations-select {
|
||||
height: 44px;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { useIsDarkMode } from 'hooks/useDarkMode';
|
||||
|
||||
import integrationsHeroBgUrl from '@/assets/Images/integrations-hero-bg.png';
|
||||
import awsDarkUrl from '@/assets/Logos/aws-dark.svg';
|
||||
|
||||
import AccountActions from './components/AccountActions';
|
||||
|
||||
import './HeroSection.style.scss';
|
||||
|
||||
function HeroSection(): JSX.Element {
|
||||
const isDarkMode = useIsDarkMode();
|
||||
return (
|
||||
<div
|
||||
className="hero-section"
|
||||
style={
|
||||
isDarkMode
|
||||
? {
|
||||
backgroundImage: `url('${integrationsHeroBgUrl}')`,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
>
|
||||
<div className="hero-section__icon">
|
||||
<img src={awsDarkUrl} alt="aws-logo" />
|
||||
</div>
|
||||
<div className="hero-section__details">
|
||||
<div className="title">Amazon Web Services</div>
|
||||
<div className="description">
|
||||
One-click setup for AWS monitoring with SigNoz
|
||||
</div>
|
||||
<AccountActions />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeroSection;
|
||||
@@ -4,57 +4,14 @@
|
||||
|
||||
&-with-account {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--l3-background);
|
||||
background: var(--l1-background);
|
||||
|
||||
.selected-cloud-integration-account-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid var(--l3-background);
|
||||
border-radius: none;
|
||||
width: 32px;
|
||||
}
|
||||
|
||||
&-selector-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.account-selector-label {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.account-selector {
|
||||
.ant-select {
|
||||
background-color: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
|
||||
.ant-select-selector {
|
||||
background: transparent !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__input-skeleton {
|
||||
width: 300px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
&__new-account-button-skeleton {
|
||||
@@ -65,13 +22,11 @@
|
||||
&__account-settings-button-skeleton {
|
||||
width: 140px;
|
||||
}
|
||||
|
||||
&__action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
&__action-button {
|
||||
font-family: 'Inter';
|
||||
border-radius: 2px;
|
||||
@@ -90,16 +45,11 @@
|
||||
&.secondary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 2px;
|
||||
background: var(--l1-background);
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--l3-background);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
border-radius: 2px;
|
||||
background: var(--l1-border);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,30 +57,25 @@
|
||||
.cloud-account-selector {
|
||||
border-radius: 2px;
|
||||
border: 1px solid var(--l3-background);
|
||||
background: var(--l1-background);
|
||||
background: linear-gradient(
|
||||
139deg,
|
||||
color-mix(in srgb, var(--card) 80%, transparent) 0%,
|
||||
color-mix(in srgb, var(--card) 90%, transparent) 98.68%
|
||||
);
|
||||
box-shadow: 4px 10px 16px 2px rgba(0, 0, 0, 0.2);
|
||||
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
backdrop-filter: blur(20px);
|
||||
.ant-select-selector {
|
||||
border-color: var(--l1-border) !important;
|
||||
background: var(--l1-background) !important;
|
||||
padding: 6px 8px !important;
|
||||
min-width: 140px !important;
|
||||
}
|
||||
|
||||
.ant-select-item-option-active {
|
||||
background: var(--l3-background) !important;
|
||||
padding: 6px 8px !important;
|
||||
}
|
||||
.ant-select-selection-item {
|
||||
color: var(--l1-foreground);
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 16px;
|
||||
}
|
||||
&:hover {
|
||||
.ant-select-selector {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.account-option-item {
|
||||
display: flex;
|
||||
@@ -142,8 +87,60 @@
|
||||
justify-content: center;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background-color: color-mix(in srgb, var(--border) 20%, transparent);
|
||||
background-color: color-mix(
|
||||
in srgb,
|
||||
var(--border) 20%,
|
||||
transparent
|
||||
); /* #C0C1C3 with 0.2 opacity */
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.lightMode {
|
||||
.hero-section__action-button {
|
||||
&.primary {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
background: var(--l1-background);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-account-selector {
|
||||
background: var(--l1-background);
|
||||
.ant-select-selector {
|
||||
background: var(--l1-background) !important;
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
.ant-select-item-option-active {
|
||||
background: var(--l3-background) !important;
|
||||
}
|
||||
|
||||
.ant-select-selection-item {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.ant-select-selector {
|
||||
border-color: var(--l1-border) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-option-item {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&__selected {
|
||||
background: var(--primary-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,58 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button } from '@signozhq/ui';
|
||||
import { Select, Skeleton } from 'antd';
|
||||
import { SelectProps } from 'antd/lib';
|
||||
import { Button, Select, Skeleton } from 'antd';
|
||||
import type { SelectProps } from 'antd/lib';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import { useListAccounts } from 'api/generated/services/cloudintegration';
|
||||
import { getAccountById } from 'container/Integrations/CloudIntegration/utils';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { ChevronDown, Dot, PencilLine, Plug, Plus } from 'lucide-react';
|
||||
import { Check, ChevronDown } from 'lucide-react';
|
||||
|
||||
import { mapAccountDtoToAwsCloudAccount } from '../../mapAwsCloudAccountFromDto';
|
||||
import { CloudAccount } from '../../types';
|
||||
import { CloudAccount } from '../../ServicesSection/types';
|
||||
import AccountSettingsModal from './AccountSettingsModal';
|
||||
import CloudAccountSetupModal from './CloudAccountSetupModal';
|
||||
|
||||
import './AccountActions.style.scss';
|
||||
|
||||
interface AccountOptionItemProps {
|
||||
label: React.ReactNode;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
function AccountOptionItem({
|
||||
label,
|
||||
isSelected,
|
||||
}: AccountOptionItemProps): JSX.Element {
|
||||
return (
|
||||
<div className="account-option-item">
|
||||
{label}
|
||||
{isSelected && (
|
||||
<div className="account-option-item__selected">
|
||||
<Check size={12} color={Color.BG_VANILLA_100} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderOption(
|
||||
option: any,
|
||||
activeAccountId: string | undefined,
|
||||
): JSX.Element {
|
||||
return (
|
||||
<AccountOptionItem
|
||||
label={option.label}
|
||||
isSelected={option.value === activeAccountId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const getAccountById = (
|
||||
accounts: CloudAccount[],
|
||||
accountId: string,
|
||||
): CloudAccount | null =>
|
||||
accounts.find((account) => account.cloud_account_id === accountId) || null;
|
||||
|
||||
function AccountActionsRenderer({
|
||||
accounts,
|
||||
isLoading,
|
||||
@@ -38,52 +73,55 @@ function AccountActionsRenderer({
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="hero-section__actions-with-account">
|
||||
<Skeleton.Input active block className="hero-section__input-skeleton" />
|
||||
<Skeleton.Input
|
||||
active
|
||||
size="large"
|
||||
block
|
||||
className="hero-section__input-skeleton"
|
||||
/>
|
||||
<div className="hero-section__action-buttons">
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="large"
|
||||
className="hero-section__new-account-button-skeleton"
|
||||
/>
|
||||
<Skeleton.Button
|
||||
active
|
||||
size="large"
|
||||
className="hero-section__account-settings-button-skeleton"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (accounts?.length) {
|
||||
return (
|
||||
<div className="hero-section__actions-with-account">
|
||||
<div className="hero-section__actions-with-account-selector-container">
|
||||
<div className="selected-cloud-integration-account-status">
|
||||
<Dot size={24} color={Color.BG_FOREST_500} />
|
||||
</div>
|
||||
|
||||
<div className="account-selector-label">Account:</div>
|
||||
|
||||
<span className="account-selector">
|
||||
<Select
|
||||
value={activeAccount?.providerAccountId}
|
||||
options={selectOptions}
|
||||
rootClassName="cloud-account-selector"
|
||||
popupMatchSelectWidth={false}
|
||||
placeholder="Select AWS Account"
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
onChange={onAccountChange}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<Select
|
||||
value={`Account: ${activeAccount?.cloud_account_id}`}
|
||||
options={selectOptions}
|
||||
rootClassName="cloud-account-selector"
|
||||
placeholder="Select AWS Account"
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
optionRender={(option): JSX.Element =>
|
||||
renderOption(option, activeAccount?.cloud_account_id)
|
||||
}
|
||||
onChange={onAccountChange}
|
||||
/>
|
||||
<div className="hero-section__action-buttons">
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
prefix={<PencilLine size={14} />}
|
||||
type="primary"
|
||||
className="hero-section__action-button primary"
|
||||
onClick={onIntegrationModalOpen}
|
||||
>
|
||||
Add New AWS Account
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
className="hero-section__action-button secondary"
|
||||
onClick={onAccountSettingsModalOpen}
|
||||
>
|
||||
Edit Account
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
color="secondary"
|
||||
onClick={onIntegrationModalOpen}
|
||||
prefix={<Plus size={14} />}
|
||||
>
|
||||
Add New Account
|
||||
Account Settings
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,11 +129,8 @@ function AccountActionsRenderer({
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
prefix={<Plug size={14} />}
|
||||
className="hero-section__action-button primary"
|
||||
onClick={onIntegrationModalOpen}
|
||||
size="sm"
|
||||
>
|
||||
Integrate Now
|
||||
</Button>
|
||||
@@ -105,18 +140,7 @@ function AccountActionsRenderer({
|
||||
function AccountActions(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const navigate = useNavigate();
|
||||
const { data: listAccountsResponse, isLoading } = useListAccounts({
|
||||
cloudProvider: INTEGRATION_TYPES.AWS,
|
||||
});
|
||||
const accounts = useMemo((): CloudAccount[] | undefined => {
|
||||
const raw = listAccountsResponse?.data?.accounts;
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
return raw
|
||||
.map(mapAccountDtoToAwsCloudAccount)
|
||||
.filter((account): account is CloudAccount => account !== null);
|
||||
}, [listAccountsResponse]);
|
||||
const { data: accounts, isLoading } = useAwsAccounts();
|
||||
|
||||
const initialAccount = useMemo(
|
||||
() =>
|
||||
@@ -138,13 +162,7 @@ function AccountActions(): JSX.Element {
|
||||
const latestUrlQuery = new URLSearchParams(window.location.search);
|
||||
latestUrlQuery.set('cloudAccountId', initialAccount.cloud_account_id);
|
||||
navigate({ search: latestUrlQuery.toString() });
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveAccount(null);
|
||||
const latestUrlQuery = new URLSearchParams(window.location.search);
|
||||
latestUrlQuery.delete('cloudAccountId');
|
||||
navigate({ search: latestUrlQuery.toString() });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [initialAccount]);
|
||||
|
||||
@@ -180,7 +198,7 @@ function AccountActions(): JSX.Element {
|
||||
accounts?.length
|
||||
? accounts.map((account) => ({
|
||||
value: account.cloud_account_id,
|
||||
label: account.providerAccountId,
|
||||
label: account.cloud_account_id,
|
||||
}))
|
||||
: [],
|
||||
[accounts],
|
||||
@@ -210,10 +228,10 @@ function AccountActions(): JSX.Element {
|
||||
/>
|
||||
)}
|
||||
|
||||
{isAccountSettingsModalOpen && activeAccount && (
|
||||
{isAccountSettingsModalOpen && (
|
||||
<AccountSettingsModal
|
||||
onClose={(): void => setIsAccountSettingsModalOpen(false)}
|
||||
account={activeAccount}
|
||||
account={activeAccount as CloudAccount}
|
||||
setActiveAccount={setActiveAccount}
|
||||
/>
|
||||
)}
|
||||
@@ -12,13 +12,10 @@
|
||||
flex-direction: column;
|
||||
gap: 17px;
|
||||
border-radius: 3px;
|
||||
|
||||
border: 1px solid var(--l1-border);
|
||||
padding: 14px;
|
||||
&-account-info {
|
||||
&-connected-account-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&-title {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
@@ -41,12 +38,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-region-selector {
|
||||
&-regions-switch {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
|
||||
gap: 10px;
|
||||
&-title {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
@@ -54,14 +49,6 @@
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&-description {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
|
||||
&-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -79,17 +66,15 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
&-regions-select {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
&-close-button,
|
||||
&-save-button {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -98,31 +83,18 @@
|
||||
}
|
||||
&-close-button {
|
||||
border-radius: 2px;
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
background: var(--l1-border);
|
||||
border: none;
|
||||
}
|
||||
&-save-button {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
border: none;
|
||||
border-radius: 2px;
|
||||
margin: 0 !important;
|
||||
|
||||
&:disabled {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
opacity: 0.6;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--primary-background-hover);
|
||||
}
|
||||
border-radius: 2px;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
.ant-modal-body {
|
||||
@@ -137,3 +109,81 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.account-settings-modal {
|
||||
&__title-account-id {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__body {
|
||||
border-color: var(--l1-border);
|
||||
|
||||
&-account-info {
|
||||
&-connected-account-details {
|
||||
&-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&-account-id {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&-account-id {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-regions-switch {
|
||||
&-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&-switch {
|
||||
&-label {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
&-close-button,
|
||||
&-save-button {
|
||||
color: var(--l1-background);
|
||||
}
|
||||
|
||||
&-close-button {
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&-save-button {
|
||||
// Keep primary button same as dark mode
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
|
||||
&:disabled {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
&:not(:disabled):hover {
|
||||
background: var(--bg-robin-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import { Dispatch, SetStateAction, useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Form, Select, Switch } from 'antd';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
getRegionPreviewText,
|
||||
useAccountSettingsModal,
|
||||
} from 'hooks/integration/aws/useAccountSettingsModal';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import history from 'lib/history';
|
||||
|
||||
import logEvent from '../../../../api/common/logEvent';
|
||||
import { CloudAccount } from '../../ServicesSection/types';
|
||||
import { RegionSelector } from './RegionSelector';
|
||||
import RemoveIntegrationAccount from './RemoveIntegrationAccount';
|
||||
|
||||
import './AccountSettingsModal.style.scss';
|
||||
|
||||
interface AccountSettingsModalProps {
|
||||
onClose: () => void;
|
||||
account: CloudAccount;
|
||||
setActiveAccount: Dispatch<SetStateAction<CloudAccount | null>>;
|
||||
}
|
||||
|
||||
function AccountSettingsModal({
|
||||
onClose,
|
||||
account,
|
||||
setActiveAccount,
|
||||
}: AccountSettingsModalProps): JSX.Element {
|
||||
const {
|
||||
form,
|
||||
isLoading,
|
||||
selectedRegions,
|
||||
includeAllRegions,
|
||||
isRegionSelectOpen,
|
||||
isSaveDisabled,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
setIsRegionSelectOpen,
|
||||
handleIncludeAllRegionsChange,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
} = useAccountSettingsModal({ onClose, account, setActiveAccount });
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
const urlQuery = useUrlQuery();
|
||||
|
||||
const handleRemoveIntegrationAccountSuccess = (): void => {
|
||||
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
|
||||
urlQuery.delete('cloudAccountId');
|
||||
handleClose();
|
||||
history.replace({ search: urlQuery.toString() });
|
||||
|
||||
logEvent('AWS Integration: Account removed', {
|
||||
id: account?.id,
|
||||
cloudAccountId: account?.cloud_account_id,
|
||||
});
|
||||
};
|
||||
|
||||
const handleRegionDeselect = useCallback(
|
||||
(item: string): void => {
|
||||
if (selectedRegions.includes(item)) {
|
||||
setSelectedRegions(selectedRegions.filter((region) => region !== item));
|
||||
if (includeAllRegions) {
|
||||
setIncludeAllRegions(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
selectedRegions,
|
||||
includeAllRegions,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
],
|
||||
);
|
||||
|
||||
const renderRegionSelector = useCallback(() => {
|
||||
if (isRegionSelectOpen) {
|
||||
return (
|
||||
<RegionSelector
|
||||
selectedRegions={selectedRegions}
|
||||
setSelectedRegions={setSelectedRegions}
|
||||
setIncludeAllRegions={setIncludeAllRegions}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="account-settings-modal__body-regions-switch-switch ">
|
||||
<Switch
|
||||
checked={includeAllRegions}
|
||||
onChange={handleIncludeAllRegionsChange}
|
||||
/>
|
||||
<button
|
||||
className="account-settings-modal__body-regions-switch-switch-label"
|
||||
type="button"
|
||||
onClick={(): void => handleIncludeAllRegionsChange(!includeAllRegions)}
|
||||
>
|
||||
Include all regions
|
||||
</button>
|
||||
</div>
|
||||
<Select
|
||||
suffixIcon={null}
|
||||
placeholder="Select Region(s)"
|
||||
className="cloud-account-setup-form__select account-settings-modal__body-regions-select integrations-select"
|
||||
onClick={(): void => setIsRegionSelectOpen(true)}
|
||||
mode="multiple"
|
||||
maxTagCount={3}
|
||||
value={getRegionPreviewText(selectedRegions)}
|
||||
open={false}
|
||||
onDeselect={handleRegionDeselect}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
isRegionSelectOpen,
|
||||
includeAllRegions,
|
||||
handleIncludeAllRegionsChange,
|
||||
selectedRegions,
|
||||
handleRegionDeselect,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
setIsRegionSelectOpen,
|
||||
]);
|
||||
|
||||
const renderAccountDetails = useCallback(
|
||||
() => (
|
||||
<div className="account-settings-modal__body-account-info">
|
||||
<div className="account-settings-modal__body-account-info-connected-account-details">
|
||||
<div className="account-settings-modal__body-account-info-connected-account-details-title">
|
||||
Connected Account details
|
||||
</div>
|
||||
<div className="account-settings-modal__body-account-info-connected-account-details-account-id">
|
||||
AWS Account:{' '}
|
||||
<span className="account-settings-modal__body-account-info-connected-account-details-account-id-account-id">
|
||||
{account?.id}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
[account?.id],
|
||||
);
|
||||
|
||||
const modalTitle = (
|
||||
<div className="account-settings-modal__title">
|
||||
Account settings for{' '}
|
||||
<span className="account-settings-modal__title-account-id">
|
||||
{account?.id}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<SignozModal
|
||||
open
|
||||
title={modalTitle}
|
||||
onCancel={handleClose}
|
||||
onOk={handleSubmit}
|
||||
okText="Save"
|
||||
okButtonProps={{
|
||||
disabled: isSaveDisabled,
|
||||
className: 'account-settings-modal__footer-save-button',
|
||||
loading: isLoading,
|
||||
}}
|
||||
cancelButtonProps={{
|
||||
className: 'account-settings-modal__footer-close-button',
|
||||
}}
|
||||
width={672}
|
||||
rootClassName="account-settings-modal"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
selectedRegions,
|
||||
includeAllRegions,
|
||||
}}
|
||||
>
|
||||
<div className="account-settings-modal__body">
|
||||
{renderAccountDetails()}
|
||||
|
||||
<Form.Item
|
||||
name="selectedRegions"
|
||||
rules={[
|
||||
{
|
||||
validator: async (): Promise<void> => {
|
||||
if (selectedRegions.length === 0) {
|
||||
throw new Error('Please select at least one region to monitor');
|
||||
}
|
||||
},
|
||||
message: 'Please select at least one region to monitor',
|
||||
},
|
||||
]}
|
||||
>
|
||||
{renderRegionSelector()}
|
||||
</Form.Item>
|
||||
|
||||
<div className="integration-detail-content">
|
||||
<RemoveIntegrationAccount
|
||||
accountId={account?.id}
|
||||
onRemoveIntegrationAccountSuccess={handleRemoveIntegrationAccountSuccess}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</SignozModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default AccountSettingsModal;
|
||||
@@ -1,28 +1,4 @@
|
||||
.cloud-account-setup-modal {
|
||||
background: var(--l1-background);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
[data-slot='drawer-title'] {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
> div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
min-height: 0;
|
||||
scrollbar-width: thin;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.account-setup-modal-footer {
|
||||
&__confirm-button {
|
||||
background: var(--primary-background);
|
||||
@@ -34,18 +10,12 @@
|
||||
font-family: 'Geist Mono';
|
||||
}
|
||||
&__close-button {
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
background: var(--l1-border);
|
||||
border-radius: 2px;
|
||||
color: var(--l1-foreground);
|
||||
font-family: 'Inter';
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,14 +23,12 @@
|
||||
.disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
&,
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
gap: 38px;
|
||||
}
|
||||
|
||||
&__alert {
|
||||
&.ant-alert {
|
||||
padding: 12px;
|
||||
@@ -88,8 +56,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--l1-foreground);
|
||||
|
||||
.retry-time {
|
||||
font-family: 'Geist Mono';
|
||||
font-size: 14px;
|
||||
@@ -150,7 +116,7 @@
|
||||
}
|
||||
&__note {
|
||||
padding: 12px;
|
||||
color: var(--callout-primary-description);
|
||||
color: var(--bg-robin-400);
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
letter-spacing: -0.06px;
|
||||
@@ -178,3 +144,87 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.cloud-account-setup-modal {
|
||||
.account-setup-modal-footer {
|
||||
&__confirm-button {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
&__close-button {
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-account-setup-form {
|
||||
&__title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__select {
|
||||
.ant-select-selection-item {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&__include-all-regions-switch {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&-label {
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__note {
|
||||
color: var(--primary-foreground);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--primary-background) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--primary-background) 10%, transparent);
|
||||
}
|
||||
|
||||
&__submit-button {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
&__alert {
|
||||
&.ant-alert-error {
|
||||
color: var(--danger-foreground);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--danger-background) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--danger-background) 10%, transparent);
|
||||
}
|
||||
|
||||
&.ant-alert-warning {
|
||||
color: var(--warning-foreground);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--warning-background) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--warning-background) 10%, transparent);
|
||||
}
|
||||
|
||||
&-message {
|
||||
.retry-time {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Button, DrawerWrapper } from '@signozhq/ui';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import { useIntegrationModal } from 'hooks/integration/aws/useIntegrationModal';
|
||||
import { SquareArrowOutUpRight } from 'lucide-react';
|
||||
|
||||
@@ -10,15 +12,19 @@ import {
|
||||
ModalStateEnum,
|
||||
} from '../types';
|
||||
import { RegionForm } from './RegionForm';
|
||||
import { RegionSelector } from './RegionSelector';
|
||||
import { SuccessView } from './SuccessView';
|
||||
|
||||
import './CloudAccountSetupModal.style.scss';
|
||||
|
||||
function CloudAccountSetupModal({
|
||||
onClose,
|
||||
}: IntegrationModalProps): JSX.Element {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
form,
|
||||
modalState,
|
||||
setModalState,
|
||||
isLoading,
|
||||
activeView,
|
||||
selectedRegions,
|
||||
@@ -26,86 +32,97 @@ function CloudAccountSetupModal({
|
||||
isGeneratingUrl,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
handleIncludeAllRegionsChange,
|
||||
handleRegionSelect,
|
||||
handleSubmit,
|
||||
handleClose,
|
||||
setActiveView,
|
||||
allRegions,
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
handleConnectionSuccess,
|
||||
handleConnectionTimeout,
|
||||
handleConnectionError,
|
||||
} = useIntegrationModal({ onClose });
|
||||
|
||||
const renderContent = useCallback(() => {
|
||||
return (
|
||||
<div className="cloud-account-setup-modal__content">
|
||||
<RegionForm
|
||||
form={form}
|
||||
modalState={modalState}
|
||||
if (modalState === ModalStateEnum.SUCCESS) {
|
||||
return <SuccessView />;
|
||||
}
|
||||
|
||||
if (activeView === ActiveViewEnum.SELECT_REGIONS) {
|
||||
return (
|
||||
<RegionSelector
|
||||
selectedRegions={selectedRegions}
|
||||
includeAllRegions={includeAllRegions}
|
||||
onRegionSelect={handleRegionSelect}
|
||||
onSubmit={handleSubmit}
|
||||
accountId={accountId}
|
||||
handleRegionChange={handleRegionChange}
|
||||
connectionParams={connectionParams}
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
setSelectedRegions={setSelectedRegions}
|
||||
setIncludeAllRegions={setIncludeAllRegions}
|
||||
onConnectionSuccess={handleConnectionSuccess}
|
||||
onConnectionTimeout={handleConnectionTimeout}
|
||||
onConnectionError={handleConnectionError}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<RegionForm
|
||||
form={form}
|
||||
modalState={modalState}
|
||||
setModalState={setModalState}
|
||||
selectedRegions={selectedRegions}
|
||||
includeAllRegions={includeAllRegions}
|
||||
onIncludeAllRegionsChange={handleIncludeAllRegionsChange}
|
||||
onRegionSelect={handleRegionSelect}
|
||||
onSubmit={handleSubmit}
|
||||
accountId={accountId}
|
||||
selectedDeploymentRegion={selectedDeploymentRegion}
|
||||
handleRegionChange={handleRegionChange}
|
||||
connectionParams={connectionParams}
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
modalState,
|
||||
activeView,
|
||||
form,
|
||||
setModalState,
|
||||
selectedRegions,
|
||||
includeAllRegions,
|
||||
handleIncludeAllRegionsChange,
|
||||
handleRegionSelect,
|
||||
handleSubmit,
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
isLoading,
|
||||
isGeneratingUrl,
|
||||
handleConnectionSuccess,
|
||||
handleConnectionTimeout,
|
||||
handleConnectionError,
|
||||
]);
|
||||
|
||||
const footer = (
|
||||
<div className="cloud-account-setup-modal__footer">
|
||||
<Button
|
||||
variant="solid"
|
||||
color="primary"
|
||||
prefix={<SquareArrowOutUpRight size={17} color={Color.BG_VANILLA_100} />}
|
||||
onClick={handleSubmit}
|
||||
disabled={
|
||||
selectedRegions.length === 0 ||
|
||||
isLoading ||
|
||||
isGeneratingUrl ||
|
||||
modalState === ModalStateEnum.WAITING
|
||||
}
|
||||
>
|
||||
Launch Cloud Formation Template
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
|
||||
const getSelectedRegionsCount = useCallback(
|
||||
(): number => selectedRegions.length,
|
||||
[selectedRegions],
|
||||
(): number =>
|
||||
selectedRegions.includes('all') ? allRegions.length : selectedRegions.length,
|
||||
[selectedRegions, allRegions],
|
||||
);
|
||||
|
||||
const getModalConfig = useCallback(() => {
|
||||
// Handle success state first
|
||||
if (modalState === ModalStateEnum.SUCCESS) {
|
||||
return {
|
||||
title: 'AWS Integration',
|
||||
okText: (
|
||||
<div className="cloud-account-setup-success-view__footer-button">
|
||||
Continue
|
||||
</div>
|
||||
),
|
||||
block: true,
|
||||
onOk: (): void => {
|
||||
queryClient.invalidateQueries([REACT_QUERY_KEY.AWS_ACCOUNTS]);
|
||||
handleClose();
|
||||
},
|
||||
cancelButtonProps: { style: { display: 'none' } },
|
||||
disabled: false,
|
||||
};
|
||||
}
|
||||
|
||||
// Handle other views
|
||||
const viewConfigs = {
|
||||
[ActiveViewEnum.FORM]: {
|
||||
title: 'Add AWS Account',
|
||||
@@ -138,30 +155,35 @@ function CloudAccountSetupModal({
|
||||
isLoading,
|
||||
isGeneratingUrl,
|
||||
activeView,
|
||||
handleClose,
|
||||
setActiveView,
|
||||
queryClient,
|
||||
]);
|
||||
|
||||
const modalConfig = getModalConfig();
|
||||
|
||||
const handleDrawerOpenChange = (open: boolean): void => {
|
||||
if (!open) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DrawerWrapper
|
||||
open={true}
|
||||
<SignozModal
|
||||
open
|
||||
className="cloud-account-setup-modal"
|
||||
onOpenChange={handleDrawerOpenChange}
|
||||
direction="right"
|
||||
showCloseButton
|
||||
title={modalConfig.title}
|
||||
width="wide"
|
||||
footer={footer}
|
||||
onCancel={handleClose}
|
||||
onOk={modalConfig.onOk}
|
||||
okText={modalConfig.okText}
|
||||
okButtonProps={{
|
||||
loading: isLoading,
|
||||
disabled: selectedRegions.length === 0 || modalConfig.disabled,
|
||||
className:
|
||||
activeView === ActiveViewEnum.FORM
|
||||
? 'cloud-account-setup-form__submit-button'
|
||||
: 'account-setup-modal-footer__confirm-button',
|
||||
block: activeView === ActiveViewEnum.FORM,
|
||||
}}
|
||||
cancelButtonProps={modalConfig.cancelButtonProps}
|
||||
width={672}
|
||||
>
|
||||
{renderContent()}
|
||||
</DrawerWrapper>
|
||||
</SignozModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import { Form, Select } from 'antd';
|
||||
import { Form, Select, Switch } from 'antd';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { Region } from 'utils/regions';
|
||||
import { popupContainer } from 'utils/selectPopupContainer';
|
||||
|
||||
import { RegionSelector } from './RegionSelector';
|
||||
|
||||
// Form section components
|
||||
function RegionDeploymentSection({
|
||||
regions,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
isFormDisabled,
|
||||
}: {
|
||||
regions: Region[];
|
||||
selectedDeploymentRegion: string | undefined;
|
||||
handleRegionChange: (value: string) => void;
|
||||
isFormDisabled: boolean;
|
||||
}): JSX.Element {
|
||||
@@ -35,8 +33,8 @@ function RegionDeploymentSection({
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
className="cloud-account-setup-form__select integrations-select"
|
||||
onChange={handleRegionChange}
|
||||
value={selectedDeploymentRegion}
|
||||
disabled={isFormDisabled}
|
||||
getPopupContainer={popupContainer}
|
||||
>
|
||||
{regions.flatMap((region) =>
|
||||
region.subRegions.map((subRegion) => (
|
||||
@@ -52,13 +50,19 @@ function RegionDeploymentSection({
|
||||
}
|
||||
|
||||
function MonitoringRegionsSection({
|
||||
includeAllRegions,
|
||||
selectedRegions,
|
||||
setSelectedRegions,
|
||||
setIncludeAllRegions,
|
||||
onIncludeAllRegionsChange,
|
||||
getRegionPreviewText,
|
||||
onRegionSelect,
|
||||
isFormDisabled,
|
||||
}: {
|
||||
includeAllRegions: boolean;
|
||||
selectedRegions: string[];
|
||||
setSelectedRegions: Dispatch<SetStateAction<string[]>>;
|
||||
setIncludeAllRegions: Dispatch<SetStateAction<boolean>>;
|
||||
onIncludeAllRegionsChange: (checked: boolean) => void;
|
||||
getRegionPreviewText: (regions: string[]) => string[];
|
||||
onRegionSelect: () => void;
|
||||
isFormDisabled: boolean;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<div className="cloud-account-setup-form__form-group">
|
||||
@@ -69,12 +73,51 @@ function MonitoringRegionsSection({
|
||||
Choose only the regions you want SigNoz to monitor. You can enable all at
|
||||
once, or pick specific ones:
|
||||
</div>
|
||||
|
||||
<RegionSelector
|
||||
selectedRegions={selectedRegions}
|
||||
setSelectedRegions={setSelectedRegions}
|
||||
setIncludeAllRegions={setIncludeAllRegions}
|
||||
/>
|
||||
<Form.Item
|
||||
name="monitorRegions"
|
||||
rules={[
|
||||
{
|
||||
validator: async (): Promise<void> => {
|
||||
if (selectedRegions.length === 0) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
message: 'Please select at least one region to monitor',
|
||||
},
|
||||
]}
|
||||
className="cloud-account-setup-form__form-item"
|
||||
>
|
||||
<div className="cloud-account-setup-form__include-all-regions-switch">
|
||||
<Switch
|
||||
size="small"
|
||||
checked={includeAllRegions}
|
||||
onChange={onIncludeAllRegionsChange}
|
||||
disabled={isFormDisabled}
|
||||
/>
|
||||
<button
|
||||
className="cloud-account-setup-form__include-all-regions-switch-label"
|
||||
type="button"
|
||||
onClick={(): void =>
|
||||
!isFormDisabled
|
||||
? onIncludeAllRegionsChange(!includeAllRegions)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Include all regions
|
||||
</button>
|
||||
</div>
|
||||
<Select
|
||||
suffixIcon={null}
|
||||
placeholder="Select Region(s)"
|
||||
className="cloud-account-setup-form__select integrations-select"
|
||||
onClick={!isFormDisabled ? onRegionSelect : undefined}
|
||||
mode="multiple"
|
||||
maxTagCount={3}
|
||||
value={getRegionPreviewText(selectedRegions)}
|
||||
open={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useRef } from 'react';
|
||||
import { Form } from 'antd';
|
||||
import cx from 'classnames';
|
||||
import { useAccountStatus } from 'hooks/integration/aws/useAccountStatus';
|
||||
import { AccountStatusResponse } from 'types/api/integrations/aws';
|
||||
import { regions } from 'utils/regions';
|
||||
|
||||
import logEvent from '../../../../api/common/logEvent';
|
||||
import { ModalStateEnum, RegionFormProps } from '../types';
|
||||
import AlertMessage from './AlertMessage';
|
||||
import {
|
||||
ComplianceNote,
|
||||
MonitoringRegionsSection,
|
||||
RegionDeploymentSection,
|
||||
} from './IntegrateNowFormSections';
|
||||
import RenderConnectionFields from './RenderConnectionParams';
|
||||
|
||||
const allRegions = (): string[] =>
|
||||
regions.flatMap((r) => r.subRegions.map((sr) => sr.name));
|
||||
|
||||
const getRegionPreviewText = (regions: string[]): string[] => {
|
||||
if (regions.includes('all')) {
|
||||
return allRegions();
|
||||
}
|
||||
return regions;
|
||||
};
|
||||
|
||||
export function RegionForm({
|
||||
form,
|
||||
modalState,
|
||||
setModalState,
|
||||
selectedRegions,
|
||||
includeAllRegions,
|
||||
onIncludeAllRegionsChange,
|
||||
onRegionSelect,
|
||||
onSubmit,
|
||||
accountId,
|
||||
selectedDeploymentRegion,
|
||||
handleRegionChange,
|
||||
connectionParams,
|
||||
isConnectionParamsLoading,
|
||||
}: RegionFormProps): JSX.Element {
|
||||
const startTimeRef = useRef(Date.now());
|
||||
const refetchInterval = 10 * 1000;
|
||||
const errorTimeout = 10 * 60 * 1000;
|
||||
|
||||
const { isLoading: isAccountStatusLoading } = useAccountStatus(accountId, {
|
||||
refetchInterval,
|
||||
enabled: !!accountId && modalState === ModalStateEnum.WAITING,
|
||||
onSuccess: (data: AccountStatusResponse) => {
|
||||
if (data.data.status.integration.last_heartbeat_ts_ms !== null) {
|
||||
setModalState(ModalStateEnum.SUCCESS);
|
||||
logEvent('AWS Integration: Account connected', {
|
||||
cloudAccountId: data?.data?.cloud_account_id,
|
||||
status: data?.data?.status,
|
||||
});
|
||||
} else if (Date.now() - startTimeRef.current >= errorTimeout) {
|
||||
setModalState(ModalStateEnum.ERROR);
|
||||
logEvent('AWS Integration: Account connection attempt timed out', {
|
||||
id: accountId,
|
||||
});
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
setModalState(ModalStateEnum.ERROR);
|
||||
},
|
||||
});
|
||||
|
||||
const isFormDisabled =
|
||||
modalState === ModalStateEnum.WAITING || isAccountStatusLoading;
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
className="cloud-account-setup-form"
|
||||
layout="vertical"
|
||||
onFinish={onSubmit}
|
||||
>
|
||||
<AlertMessage modalState={modalState} />
|
||||
|
||||
<div
|
||||
className={cx(`cloud-account-setup-form__content`, {
|
||||
disabled: isFormDisabled,
|
||||
})}
|
||||
>
|
||||
<RegionDeploymentSection
|
||||
regions={regions}
|
||||
handleRegionChange={handleRegionChange}
|
||||
isFormDisabled={isFormDisabled}
|
||||
selectedDeploymentRegion={selectedDeploymentRegion}
|
||||
/>
|
||||
<MonitoringRegionsSection
|
||||
includeAllRegions={includeAllRegions}
|
||||
selectedRegions={selectedRegions}
|
||||
onIncludeAllRegionsChange={onIncludeAllRegionsChange}
|
||||
getRegionPreviewText={getRegionPreviewText}
|
||||
onRegionSelect={onRegionSelect}
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
<ComplianceNote />
|
||||
<RenderConnectionFields
|
||||
isConnectionParamsLoading={isConnectionParamsLoading}
|
||||
connectionParams={connectionParams}
|
||||
isFormDisabled={isFormDisabled}
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
.select-all {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.regions-grid {
|
||||
@@ -20,11 +19,3 @@
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.region-selector-footer {
|
||||
margin-top: 36px;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
}
|
||||
@@ -28,12 +28,10 @@ export function RegionSelector({
|
||||
<div className="region-selector">
|
||||
<div className="select-all">
|
||||
<Checkbox
|
||||
checked={
|
||||
allRegionIds.length > 0 &&
|
||||
allRegionIds.every((regionId) => selectedRegions.includes(regionId))
|
||||
}
|
||||
checked={selectedRegions.includes('all')}
|
||||
indeterminate={
|
||||
selectedRegions.length > 0 && selectedRegions.length < allRegionIds.length
|
||||
selectedRegions.length > 20 &&
|
||||
selectedRegions.length < allRegionIds.length
|
||||
}
|
||||
onChange={(e): void => handleSelectAll(e.target.checked)}
|
||||
>
|
||||
@@ -48,7 +46,10 @@ export function RegionSelector({
|
||||
{region.subRegions.map((subRegion) => (
|
||||
<Checkbox
|
||||
key={subRegion.id}
|
||||
checked={selectedRegions.includes(subRegion.id)}
|
||||
checked={
|
||||
selectedRegions.includes('all') ||
|
||||
selectedRegions.includes(subRegion.id)
|
||||
}
|
||||
onChange={(): void => handleRegionSelect(subRegion.id)}
|
||||
>
|
||||
{subRegion.name}
|
||||
@@ -0,0 +1,47 @@
|
||||
.remove-integration-account {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 16px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid color-mix(in srgb, var(--bg-sakura-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-sakura-500) 6%, transparent);
|
||||
&__header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--bg-cherry-500);
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
color: var(--bg-cherry-300);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
|
||||
&__button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--bg-cherry-500);
|
||||
border: none;
|
||||
color: var(--l1-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
line-height: 13.3px; /* 110.833% */
|
||||
padding: 9px 13px;
|
||||
.ant-btn-icon {
|
||||
margin-inline-end: 4px !important;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&.ant-btn-default {
|
||||
color: var(--l2-foreground) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { useState } from 'react';
|
||||
import { useMutation } from 'react-query';
|
||||
import { Button, Modal } from 'antd';
|
||||
import logEvent from 'api/common/logEvent';
|
||||
import removeAwsIntegrationAccount from 'api/Integrations/removeAwsIntegrationAccount';
|
||||
import { SOMETHING_WENT_WRONG } from 'constants/api';
|
||||
import { useNotifications } from 'hooks/useNotifications';
|
||||
import { X } from 'lucide-react';
|
||||
import { INTEGRATION_TELEMETRY_EVENTS } from 'pages/Integrations/utils';
|
||||
|
||||
import './RemoveIntegrationAccount.scss';
|
||||
|
||||
function RemoveIntegrationAccount({
|
||||
accountId,
|
||||
onRemoveIntegrationAccountSuccess,
|
||||
}: {
|
||||
accountId: string;
|
||||
onRemoveIntegrationAccountSuccess: () => void;
|
||||
}): JSX.Element {
|
||||
const { notifications } = useNotifications();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
|
||||
const showModal = (): void => {
|
||||
setIsModalOpen(true);
|
||||
};
|
||||
|
||||
const {
|
||||
mutate: removeIntegration,
|
||||
isLoading: isRemoveIntegrationLoading,
|
||||
} = useMutation(removeAwsIntegrationAccount, {
|
||||
onSuccess: () => {
|
||||
onRemoveIntegrationAccountSuccess?.();
|
||||
setIsModalOpen(false);
|
||||
},
|
||||
onError: () => {
|
||||
notifications.error({
|
||||
message: SOMETHING_WENT_WRONG,
|
||||
});
|
||||
},
|
||||
});
|
||||
const handleOk = (): void => {
|
||||
logEvent(INTEGRATION_TELEMETRY_EVENTS.AWS_INTEGRATION_ACCOUNT_REMOVED, {
|
||||
accountId,
|
||||
});
|
||||
removeIntegration(accountId);
|
||||
};
|
||||
|
||||
const handleCancel = (): void => {
|
||||
setIsModalOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="remove-integration-account">
|
||||
<div className="remove-integration-account__header">
|
||||
<div className="remove-integration-account__title">Remove Integration</div>
|
||||
<div className="remove-integration-account__subtitle">
|
||||
Removing this integration won't delete any existing data but will stop
|
||||
collecting new data from AWS.
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="remove-integration-account__button"
|
||||
icon={<X size={14} />}
|
||||
onClick={(): void => showModal()}
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
<Modal
|
||||
className="remove-integration-modal"
|
||||
open={isModalOpen}
|
||||
title="Remove integration"
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
okText="Remove Integration"
|
||||
okButtonProps={{
|
||||
danger: true,
|
||||
disabled: isRemoveIntegrationLoading,
|
||||
}}
|
||||
>
|
||||
<div className="remove-integration-modal__text">
|
||||
Removing this account will remove all components created for sending
|
||||
telemetry to SigNoz in your AWS account within the next ~15 minutes
|
||||
(cloudformation stacks named signoz-integration-telemetry-collection in
|
||||
enabled regions). <br />
|
||||
<br />
|
||||
After that, you can delete the cloudformation stack that was created
|
||||
manually when connecting this account.
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RemoveIntegrationAccount;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Form, Input } from 'antd';
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||
|
||||
function RenderConnectionFields({
|
||||
isConnectionParamsLoading,
|
||||
@@ -7,51 +7,51 @@ function RenderConnectionFields({
|
||||
isFormDisabled,
|
||||
}: {
|
||||
isConnectionParamsLoading?: boolean;
|
||||
connectionParams?: CloudintegrationtypesCredentialsDTO | null;
|
||||
connectionParams?: ConnectionParams | null;
|
||||
isFormDisabled?: boolean;
|
||||
}): JSX.Element | null {
|
||||
if (
|
||||
isConnectionParamsLoading ||
|
||||
(!!connectionParams?.ingestionUrl &&
|
||||
!!connectionParams?.ingestionKey &&
|
||||
!!connectionParams?.sigNozApiUrl &&
|
||||
!!connectionParams?.sigNozApiKey)
|
||||
(!!connectionParams?.ingestion_url &&
|
||||
!!connectionParams?.ingestion_key &&
|
||||
!!connectionParams?.signoz_api_url &&
|
||||
!!connectionParams?.signoz_api_key)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Form.Item name="connectionParams">
|
||||
{!connectionParams?.ingestionUrl && (
|
||||
<Form.Item name="connection_params">
|
||||
{!connectionParams?.ingestion_url && (
|
||||
<Form.Item
|
||||
name="ingestionUrl"
|
||||
name="ingestion_url"
|
||||
label="Ingestion URL"
|
||||
rules={[{ required: true, message: 'Please enter ingestion URL' }]}
|
||||
>
|
||||
<Input placeholder="Enter ingestion URL" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{!connectionParams?.ingestionKey && (
|
||||
{!connectionParams?.ingestion_key && (
|
||||
<Form.Item
|
||||
name="ingestionKey"
|
||||
name="ingestion_key"
|
||||
label="Ingestion Key"
|
||||
rules={[{ required: true, message: 'Please enter ingestion key' }]}
|
||||
>
|
||||
<Input placeholder="Enter ingestion key" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{!connectionParams?.sigNozApiUrl && (
|
||||
{!connectionParams?.signoz_api_url && (
|
||||
<Form.Item
|
||||
name="sigNozApiUrl"
|
||||
name="signoz_api_url"
|
||||
label="SigNoz API URL"
|
||||
rules={[{ required: true, message: 'Please enter SigNoz API URL' }]}
|
||||
>
|
||||
<Input placeholder="Enter SigNoz API URL" disabled={isFormDisabled} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{!connectionParams?.sigNozApiKey && (
|
||||
{!connectionParams?.signoz_api_key && (
|
||||
<Form.Item
|
||||
name="sigNozApiKey"
|
||||
name="signoz_api_key"
|
||||
label="SigNoz API KEY"
|
||||
rules={[{ required: true, message: 'Please enter SigNoz API Key' }]}
|
||||
>
|
||||
@@ -0,0 +1,162 @@
|
||||
.cloud-account-setup-success-view {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 40px;
|
||||
text-align: center;
|
||||
padding-top: 34px;
|
||||
p,
|
||||
h3,
|
||||
h4 {
|
||||
margin: 0;
|
||||
}
|
||||
&__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
.cloud-account-setup-success-view {
|
||||
&__title {
|
||||
h3 {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
&__description {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
}
|
||||
}
|
||||
&__what-next {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
text-align: left;
|
||||
&-title {
|
||||
color: var(--muted-foreground);
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0.88px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.what-next-items-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
&__item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: baseline;
|
||||
|
||||
&.ant-alert {
|
||||
padding: 14px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.21px;
|
||||
}
|
||||
|
||||
&.ant-alert-info {
|
||||
border: 1px solid color-mix(in srgb, var(--bg-robin-600) 50%, transparent);
|
||||
background: color-mix(in srgb, var(--primary-background) 20%, transparent);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.what-next-item {
|
||||
color: var(--bg-robin-400);
|
||||
&-bullet-icon {
|
||||
font-size: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
&-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.21px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
&__footer {
|
||||
padding-top: 18px;
|
||||
.ant-btn {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
height: 36px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.lottie-container {
|
||||
position: absolute;
|
||||
width: 743.5px;
|
||||
height: 990.342px;
|
||||
top: -100px;
|
||||
left: -36px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.cloud-account-setup-success-view {
|
||||
&__content {
|
||||
.cloud-account-setup-success-view {
|
||||
&__title {
|
||||
h3 {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&__description {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__what-next {
|
||||
&-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.what-next-items-wrapper {
|
||||
&__item {
|
||||
&.ant-alert-info {
|
||||
border: 1px solid color-mix(in srgb, var(--bg-robin-600) 20%, transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--primary-background) 10%,
|
||||
transparent
|
||||
);
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
|
||||
.what-next-item {
|
||||
color: var(--primary-foreground);
|
||||
|
||||
&-text {
|
||||
color: var(--primary-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
.ant-btn {
|
||||
background: var(--primary-background);
|
||||
color: var(--primary-foreground);
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-background-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useState } from 'react';
|
||||
import Lottie from 'react-lottie';
|
||||
import { Alert } from 'antd';
|
||||
import integrationsSuccess from 'assets/Lotties/integrations-success.json';
|
||||
|
||||
import solidCheckCircleUrl from '@/assets/Icons/solid-check-circle.svg';
|
||||
|
||||
import './SuccessView.style.scss';
|
||||
|
||||
export function SuccessView(): JSX.Element {
|
||||
const [isAnimationComplete, setIsAnimationComplete] = useState(false);
|
||||
|
||||
const defaultOptions = {
|
||||
loop: false,
|
||||
autoplay: true,
|
||||
animationData: integrationsSuccess,
|
||||
rendererSettings: {
|
||||
preserveAspectRatio: 'xMidYMid slice',
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isAnimationComplete && (
|
||||
<div className="lottie-container">
|
||||
<Lottie
|
||||
options={defaultOptions}
|
||||
height={990.342}
|
||||
width={743.5}
|
||||
eventListeners={[
|
||||
{
|
||||
eventName: 'complete',
|
||||
callback: (): void => setIsAnimationComplete(true),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="cloud-account-setup-success-view">
|
||||
<div className="cloud-account-setup-success-view__icon">
|
||||
<img src={solidCheckCircleUrl} alt="Success" />
|
||||
</div>
|
||||
<div className="cloud-account-setup-success-view__content">
|
||||
<div className="cloud-account-setup-success-view__title">
|
||||
<h3>🎉 Success! </h3>
|
||||
<h3>Your AWS Web Service integration is all set.</h3>
|
||||
</div>
|
||||
<div className="cloud-account-setup-success-view__description">
|
||||
<p>Your observability journey is off to a great start. </p>
|
||||
<p>Now that your data is flowing, here’s what you can do next:</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="cloud-account-setup-success-view__what-next">
|
||||
<h4 className="cloud-account-setup-success-view__what-next-title">
|
||||
WHAT NEXT
|
||||
</h4>
|
||||
<div className="what-next-items-wrapper">
|
||||
<Alert
|
||||
message={
|
||||
<div className="what-next-items-wrapper__item">
|
||||
<div className="what-next-item-bullet-icon">•</div>
|
||||
<div className="what-next-item-text">
|
||||
Set up your AWS services effortlessly under your enabled account.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
type="info"
|
||||
className="what-next-items-wrapper__item"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Dispatch, SetStateAction } from 'react';
|
||||
import { FormInstance } from 'antd';
|
||||
import { CloudintegrationtypesCredentialsDTO } from 'api/generated/services/sigNoz.schemas';
|
||||
import { ConnectionParams } from 'types/api/integrations/aws';
|
||||
|
||||
export enum ActiveViewEnum {
|
||||
SELECT_REGIONS = 'select-regions',
|
||||
@@ -11,27 +11,23 @@ export enum ModalStateEnum {
|
||||
FORM = 'form',
|
||||
WAITING = 'waiting',
|
||||
ERROR = 'error',
|
||||
SUCCESS = 'success',
|
||||
}
|
||||
|
||||
export interface RegionFormProps {
|
||||
form: FormInstance;
|
||||
modalState: ModalStateEnum;
|
||||
setModalState: Dispatch<SetStateAction<ModalStateEnum>>;
|
||||
selectedRegions: string[];
|
||||
includeAllRegions: boolean;
|
||||
onIncludeAllRegionsChange: (checked: boolean) => void;
|
||||
onRegionSelect: () => void;
|
||||
onSubmit: () => Promise<void>;
|
||||
accountId?: string;
|
||||
selectedDeploymentRegion: string | undefined;
|
||||
handleRegionChange: (value: string) => void;
|
||||
connectionParams?: CloudintegrationtypesCredentialsDTO;
|
||||
connectionParams?: ConnectionParams;
|
||||
isConnectionParamsLoading?: boolean;
|
||||
setSelectedRegions: Dispatch<SetStateAction<string[]>>;
|
||||
setIncludeAllRegions: Dispatch<SetStateAction<boolean>>;
|
||||
onConnectionSuccess: (payload: {
|
||||
cloudAccountId: string;
|
||||
status?: unknown;
|
||||
}) => void;
|
||||
onConnectionTimeout: (payload: { id?: string }) => void;
|
||||
onConnectionError: () => void;
|
||||
}
|
||||
|
||||
export interface IntegrationModalProps {
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ServiceData } from './types';
|
||||
|
||||
function DashboardItem({
|
||||
dashboard,
|
||||
}: {
|
||||
dashboard: ServiceData['assets']['dashboards'][number];
|
||||
}): JSX.Element {
|
||||
const content = (
|
||||
<>
|
||||
<div className="cloud-service-dashboard-item__title">{dashboard.title}</div>
|
||||
<div className="cloud-service-dashboard-item__preview">
|
||||
<img
|
||||
src={dashboard.image}
|
||||
alt={dashboard.title}
|
||||
className="cloud-service-dashboard-item__preview-image"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="cloud-service-dashboard-item">
|
||||
{dashboard.url ? (
|
||||
<Link to={dashboard.url} className="cloud-service-dashboard-item__link">
|
||||
{content}
|
||||
</Link>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CloudServiceDashboards({
|
||||
service,
|
||||
}: {
|
||||
service: ServiceData;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
<>
|
||||
{service.assets.dashboards.map((dashboard) => (
|
||||
<DashboardItem key={dashboard.id} dashboard={dashboard} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CloudServiceDashboards;
|
||||
@@ -1,18 +1,13 @@
|
||||
import { Table } from 'antd';
|
||||
import {
|
||||
CloudintegrationtypesCollectedLogAttributeDTO,
|
||||
CloudintegrationtypesCollectedMetricDTO,
|
||||
} from 'api/generated/services/sigNoz.schemas';
|
||||
import { BarChart2, ScrollText } from 'lucide-react';
|
||||
|
||||
import './CloudServiceDataCollected.styles.scss';
|
||||
import { ServiceData } from './types';
|
||||
|
||||
function CloudServiceDataCollected({
|
||||
logsData,
|
||||
metricsData,
|
||||
}: {
|
||||
logsData: CloudintegrationtypesCollectedLogAttributeDTO[] | null | undefined;
|
||||
metricsData: CloudintegrationtypesCollectedMetricDTO[] | null | undefined;
|
||||
logsData: ServiceData['data_collected']['logs'];
|
||||
metricsData: ServiceData['data_collected']['metrics'];
|
||||
}): JSX.Element {
|
||||
const logsColumns = [
|
||||
{
|
||||
@@ -66,30 +61,24 @@ function CloudServiceDataCollected({
|
||||
return (
|
||||
<div className="cloud-service-data-collected">
|
||||
{logsData && logsData.length > 0 && (
|
||||
<div className="cloud-service-data-collected-table">
|
||||
<div className="cloud-service-data-collected-table-heading">
|
||||
<ScrollText size={14} />
|
||||
Logs
|
||||
</div>
|
||||
<div className="cloud-service-data-collected__table">
|
||||
<div className="cloud-service-data-collected__table-heading">Logs</div>
|
||||
<Table
|
||||
columns={logsColumns}
|
||||
dataSource={logsData}
|
||||
{...tableProps}
|
||||
className="cloud-service-data-collected-table-logs"
|
||||
className="cloud-service-data-collected__table-logs"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{metricsData && metricsData.length > 0 && (
|
||||
<div className="cloud-service-data-collected-table">
|
||||
<div className="cloud-service-data-collected-table-heading">
|
||||
<BarChart2 size={14} />
|
||||
Metrics
|
||||
</div>
|
||||
<div className="cloud-service-data-collected__table">
|
||||
<div className="cloud-service-data-collected__table-heading">Metrics</div>
|
||||
<Table
|
||||
columns={metricsColumns}
|
||||
dataSource={metricsData}
|
||||
{...tableProps}
|
||||
className="cloud-service-data-collected-table-metrics"
|
||||
className="cloud-service-data-collected__table-metrics"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -0,0 +1,89 @@
|
||||
.configure-service-modal {
|
||||
&__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 3px;
|
||||
border: 1px solid var(--l1-border);
|
||||
padding: 14px;
|
||||
|
||||
&-regions-switch-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
|
||||
&-label {
|
||||
color: var(--l1-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 20px;
|
||||
letter-spacing: -0.07px;
|
||||
}
|
||||
}
|
||||
|
||||
&-switch-description {
|
||||
margin-top: 4px;
|
||||
color: var(--l2-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 18px;
|
||||
letter-spacing: -0.06px;
|
||||
}
|
||||
&-form-item {
|
||||
&:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-modal-body {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.ant-modal-footer {
|
||||
margin: 0;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.configure-service-modal {
|
||||
&__body {
|
||||
border-color: var(--l1-border);
|
||||
|
||||
&-regions-switch-switch {
|
||||
&-label {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&-switch-description {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-btn {
|
||||
&.ant-btn-default {
|
||||
background: var(--l1-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l1-border);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&.ant-btn-primary {
|
||||
// Keep primary button same as dark mode
|
||||
background: var(--primary-background);
|
||||
color: var(--l1-background);
|
||||
|
||||
&:hover {
|
||||
background: var(--bg-robin-400);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { Form, Switch } from 'antd';
|
||||
import SignozModal from 'components/SignozModal/SignozModal';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import {
|
||||
ServiceConfig,
|
||||
SupportedSignals,
|
||||
} from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import { useUpdateServiceConfig } from 'hooks/integration/aws/useUpdateServiceConfig';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
import S3BucketsSelector from './S3BucketsSelector';
|
||||
|
||||
import './ConfigureServiceModal.styles.scss';
|
||||
|
||||
export interface IConfigureServiceModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
serviceName: string;
|
||||
serviceId: string;
|
||||
cloudAccountId: string;
|
||||
supportedSignals: SupportedSignals;
|
||||
initialConfig?: ServiceConfig;
|
||||
}
|
||||
|
||||
function ConfigureServiceModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
serviceName,
|
||||
serviceId,
|
||||
cloudAccountId,
|
||||
initialConfig,
|
||||
supportedSignals,
|
||||
}: IConfigureServiceModalProps): JSX.Element {
|
||||
const [form] = Form.useForm();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
// Track current form values
|
||||
const initialValues = useMemo(
|
||||
() => ({
|
||||
metrics: initialConfig?.metrics?.enabled || false,
|
||||
logs: initialConfig?.logs?.enabled || false,
|
||||
s3Buckets: initialConfig?.logs?.s3_buckets || {},
|
||||
}),
|
||||
[initialConfig],
|
||||
);
|
||||
const [currentValues, setCurrentValues] = useState(initialValues);
|
||||
|
||||
const isSaveDisabled = useMemo(
|
||||
() =>
|
||||
// disable only if current values are same as the initial config
|
||||
currentValues.metrics === initialValues.metrics &&
|
||||
currentValues.logs === initialValues.logs &&
|
||||
isEqual(currentValues.s3Buckets, initialValues.s3Buckets),
|
||||
[currentValues, initialValues],
|
||||
);
|
||||
|
||||
const handleS3BucketsChange = useCallback(
|
||||
(bucketsByRegion: Record<string, string[]>) => {
|
||||
setCurrentValues((prev) => ({
|
||||
...prev,
|
||||
s3Buckets: bucketsByRegion,
|
||||
}));
|
||||
form.setFieldsValue({ s3Buckets: bucketsByRegion });
|
||||
},
|
||||
[form],
|
||||
);
|
||||
|
||||
const {
|
||||
mutate: updateServiceConfig,
|
||||
isLoading: isUpdating,
|
||||
} = useUpdateServiceConfig();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const handleSubmit = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
setIsLoading(true);
|
||||
|
||||
updateServiceConfig(
|
||||
{
|
||||
serviceId,
|
||||
payload: {
|
||||
cloud_account_id: cloudAccountId,
|
||||
config: {
|
||||
logs: {
|
||||
enabled: values.logs,
|
||||
s3_buckets: values.s3Buckets,
|
||||
},
|
||||
metrics: {
|
||||
enabled: values.metrics,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries([
|
||||
REACT_QUERY_KEY.AWS_SERVICE_DETAILS,
|
||||
serviceId,
|
||||
]);
|
||||
onClose();
|
||||
|
||||
logEvent('AWS Integration: Service settings saved', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
logsEnabled: values?.logs,
|
||||
metricsEnabled: values?.metrics,
|
||||
});
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Failed to update service config:', error);
|
||||
},
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Form submission failed:', error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [
|
||||
form,
|
||||
updateServiceConfig,
|
||||
serviceId,
|
||||
cloudAccountId,
|
||||
queryClient,
|
||||
onClose,
|
||||
]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
form.resetFields();
|
||||
onClose();
|
||||
}, [form, onClose]);
|
||||
|
||||
return (
|
||||
<SignozModal
|
||||
title={
|
||||
<div className="account-settings-modal__title">Configure {serviceName}</div>
|
||||
}
|
||||
centered
|
||||
open={isOpen}
|
||||
okText="Save"
|
||||
okButtonProps={{
|
||||
disabled: isSaveDisabled,
|
||||
className: 'account-settings-modal__footer-save-button',
|
||||
loading: isLoading || isUpdating,
|
||||
}}
|
||||
onCancel={handleClose}
|
||||
onOk={handleSubmit}
|
||||
cancelText="Close"
|
||||
cancelButtonProps={{
|
||||
className: 'account-settings-modal__footer-close-button',
|
||||
}}
|
||||
width={672}
|
||||
rootClassName=" configure-service-modal"
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
metrics: initialConfig?.metrics?.enabled || false,
|
||||
logs: initialConfig?.logs?.enabled || false,
|
||||
s3Buckets: initialConfig?.logs?.s3_buckets || {},
|
||||
}}
|
||||
>
|
||||
<div className=" configure-service-modal__body">
|
||||
{supportedSignals.metrics && (
|
||||
<Form.Item
|
||||
name="metrics"
|
||||
valuePropName="checked"
|
||||
className="configure-service-modal__body-form-item"
|
||||
>
|
||||
<div className="configure-service-modal__body-regions-switch-switch">
|
||||
<Switch
|
||||
checked={currentValues.metrics}
|
||||
onChange={(checked): void => {
|
||||
setCurrentValues((prev) => ({ ...prev, metrics: checked }));
|
||||
form.setFieldsValue({ metrics: checked });
|
||||
}}
|
||||
/>
|
||||
<span className="configure-service-modal__body-regions-switch-switch-label">
|
||||
Metric Collection
|
||||
</span>
|
||||
</div>
|
||||
<div className="configure-service-modal__body-switch-description">
|
||||
Metric Collection is enabled for this AWS account. We recommend keeping
|
||||
this enabled, but you can disable metric collection if you do not want
|
||||
to monitor your AWS infrastructure.
|
||||
</div>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{supportedSignals.logs && (
|
||||
<>
|
||||
<Form.Item
|
||||
name="logs"
|
||||
valuePropName="checked"
|
||||
className="configure-service-modal__body-form-item"
|
||||
>
|
||||
<div className="configure-service-modal__body-regions-switch-switch">
|
||||
<Switch
|
||||
checked={currentValues.logs}
|
||||
onChange={(checked): void => {
|
||||
setCurrentValues((prev) => ({ ...prev, logs: checked }));
|
||||
form.setFieldsValue({ logs: checked });
|
||||
}}
|
||||
/>
|
||||
<span className="configure-service-modal__body-regions-switch-switch-label">
|
||||
Log Collection
|
||||
</span>
|
||||
</div>
|
||||
<div className="configure-service-modal__body-switch-description">
|
||||
To ingest logs from your AWS services, you must complete several steps
|
||||
</div>
|
||||
</Form.Item>
|
||||
|
||||
{currentValues.logs && serviceId === 's3sync' && (
|
||||
<Form.Item name="s3Buckets" noStyle>
|
||||
<S3BucketsSelector
|
||||
initialBucketsByRegion={currentValues.s3Buckets}
|
||||
onChange={handleS3BucketsChange}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Form>
|
||||
</SignozModal>
|
||||
);
|
||||
}
|
||||
|
||||
ConfigureServiceModal.defaultProps = {
|
||||
initialConfig: {
|
||||
metrics: { enabled: false },
|
||||
logs: { enabled: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default ConfigureServiceModal;
|
||||
@@ -1,18 +1,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { Select, Skeleton } from 'antd';
|
||||
import { useListAccounts } from 'api/generated/services/cloudintegration';
|
||||
import { INTEGRATION_TYPES } from 'container/Integrations/constants';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { Form, Select, Skeleton, Typography } from 'antd';
|
||||
import { useAwsAccounts } from 'hooks/integration/aws/useAwsAccounts';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
|
||||
import { mapAccountDtoToAwsCloudAccount } from '../mapAwsCloudAccountFromDto';
|
||||
import { CloudAccount } from '../types';
|
||||
|
||||
import './S3BucketsSelector.styles.scss';
|
||||
const { Title } = Typography;
|
||||
|
||||
interface S3BucketsSelectorProps {
|
||||
onChange?: (bucketsByRegion: Record<string, string[]>) => void;
|
||||
initialBucketsByRegion?: Record<string, string[]>;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -22,29 +17,13 @@ interface S3BucketsSelectorProps {
|
||||
function S3BucketsSelector({
|
||||
onChange,
|
||||
initialBucketsByRegion = {},
|
||||
disabled: isSelectorDisabled = false,
|
||||
}: S3BucketsSelectorProps): JSX.Element {
|
||||
const cloudAccountId = useUrlQuery().get('cloudAccountId');
|
||||
const { data: listAccountsResponse, isLoading } = useListAccounts({
|
||||
cloudProvider: INTEGRATION_TYPES.AWS,
|
||||
});
|
||||
const accounts = useMemo((): CloudAccount[] | undefined => {
|
||||
const raw = listAccountsResponse?.data?.accounts;
|
||||
if (!raw) {
|
||||
return undefined;
|
||||
}
|
||||
return raw
|
||||
.map(mapAccountDtoToAwsCloudAccount)
|
||||
.filter((account): account is CloudAccount => account !== null);
|
||||
}, [listAccountsResponse]);
|
||||
const { data: accounts, isLoading } = useAwsAccounts();
|
||||
const [bucketsByRegion, setBucketsByRegion] = useState<
|
||||
Record<string, string[]>
|
||||
>(initialBucketsByRegion);
|
||||
|
||||
useEffect(() => {
|
||||
setBucketsByRegion(initialBucketsByRegion);
|
||||
}, [initialBucketsByRegion]);
|
||||
|
||||
// Find the active AWS account based on the URL query parameter
|
||||
const activeAccount = useMemo(
|
||||
() =>
|
||||
@@ -102,41 +81,37 @@ function S3BucketsSelector({
|
||||
|
||||
return (
|
||||
<div className="s3-buckets-selector">
|
||||
<div className="s3-buckets-selector-title">Select S3 Buckets by Region</div>
|
||||
<div className="s3-buckets-selector-content">
|
||||
{allRegions.map((region) => {
|
||||
const isRegionUnavailable = isRegionDisabled(region);
|
||||
<Title level={5}>Select S3 Buckets by Region</Title>
|
||||
|
||||
return (
|
||||
<div key={region} className="s3-buckets-selector-region">
|
||||
<div className="s3-buckets-selector-region-header">
|
||||
<div className="s3-buckets-selector-region-label">{region}</div>
|
||||
{isRegionUnavailable && (
|
||||
<div className="s3-buckets-selector-region-help">
|
||||
Region disabled in account settings; S3 buckets here will not be
|
||||
synced.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="s3-buckets-selector-region-select">
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder={`Enter S3 bucket names for ${region}`}
|
||||
value={bucketsByRegion[region] || []}
|
||||
onChange={(value): void => handleRegionBucketsChange(region, value)}
|
||||
tokenSeparators={[',']}
|
||||
allowClear
|
||||
disabled={isSelectorDisabled || isRegionUnavailable}
|
||||
suffixIcon={null}
|
||||
notFoundContent={null}
|
||||
filterOption={false}
|
||||
showSearch
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{allRegions.map((region) => {
|
||||
const disabled = isRegionDisabled(region);
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
key={region}
|
||||
label={region}
|
||||
{...(disabled && {
|
||||
help:
|
||||
'Region disabled in account settings; S3 buckets here will not be synced.',
|
||||
validateStatus: 'warning',
|
||||
})}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder={`Enter S3 bucket names for ${region}`}
|
||||
value={bucketsByRegion[region] || []}
|
||||
onChange={(value): void => handleRegionBucketsChange(region, value)}
|
||||
tokenSeparators={[',']}
|
||||
allowClear
|
||||
disabled={disabled}
|
||||
suffixIcon={null}
|
||||
notFoundContent={null}
|
||||
filterOption={false}
|
||||
showSearch
|
||||
/>
|
||||
</Form.Item>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { Button, Tabs, TabsProps } from 'antd';
|
||||
import { MarkdownRenderer } from 'components/MarkdownRenderer/MarkdownRenderer';
|
||||
import Spinner from 'components/Spinner';
|
||||
import CloudServiceDashboards from 'container/CloudIntegrationPage/ServicesSection/CloudServiceDashboards';
|
||||
import CloudServiceDataCollected from 'container/CloudIntegrationPage/ServicesSection/CloudServiceDataCollected';
|
||||
import { IServiceStatus } from 'container/CloudIntegrationPage/ServicesSection/types';
|
||||
import dayjs from 'dayjs';
|
||||
import { useServiceDetails } from 'hooks/integration/aws/useServiceDetails';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
|
||||
import logEvent from '../../../api/common/logEvent';
|
||||
import ConfigureServiceModal from './ConfigureServiceModal';
|
||||
|
||||
const getStatus = (
|
||||
logsLastReceivedTimestamp: number | undefined,
|
||||
metricsLastReceivedTimestamp: number | undefined,
|
||||
): { text: string; className: string } => {
|
||||
if (!logsLastReceivedTimestamp && !metricsLastReceivedTimestamp) {
|
||||
return { text: 'No Data Yet', className: 'service-status--no-data' };
|
||||
}
|
||||
|
||||
const latestTimestamp = Math.max(
|
||||
logsLastReceivedTimestamp || 0,
|
||||
metricsLastReceivedTimestamp || 0,
|
||||
);
|
||||
|
||||
const isStale = dayjs().diff(dayjs(latestTimestamp), 'minute') > 30;
|
||||
|
||||
if (isStale) {
|
||||
return { text: 'Stale Data', className: 'service-status--stale-data' };
|
||||
}
|
||||
|
||||
return { text: 'Connected', className: 'service-status--connected' };
|
||||
};
|
||||
|
||||
function ServiceStatus({
|
||||
serviceStatus,
|
||||
}: {
|
||||
serviceStatus: IServiceStatus | undefined;
|
||||
}): JSX.Element {
|
||||
const logsLastReceivedTimestamp = serviceStatus?.logs?.last_received_ts_ms;
|
||||
const metricsLastReceivedTimestamp =
|
||||
serviceStatus?.metrics?.last_received_ts_ms;
|
||||
|
||||
const { text, className } = getStatus(
|
||||
logsLastReceivedTimestamp,
|
||||
metricsLastReceivedTimestamp,
|
||||
);
|
||||
|
||||
return <div className={`service-status ${className}`}>{text}</div>;
|
||||
}
|
||||
|
||||
function getTabItems(serviceDetailsData: any): TabsProps['items'] {
|
||||
const dashboards = serviceDetailsData?.assets.dashboards || [];
|
||||
const dataCollected = serviceDetailsData?.data_collected || {};
|
||||
const items: TabsProps['items'] = [];
|
||||
|
||||
if (dashboards.length) {
|
||||
items.push({
|
||||
key: 'dashboards',
|
||||
label: `Dashboards (${dashboards.length})`,
|
||||
children: <CloudServiceDashboards service={serviceDetailsData} />,
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
key: 'data-collected',
|
||||
label: 'Data Collected',
|
||||
children: (
|
||||
<CloudServiceDataCollected
|
||||
logsData={dataCollected.logs || []}
|
||||
metricsData={dataCollected.metrics || []}
|
||||
/>
|
||||
),
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function ServiceDetails(): JSX.Element | null {
|
||||
const urlQuery = useUrlQuery();
|
||||
const cloudAccountId = urlQuery.get('cloudAccountId');
|
||||
const serviceId = urlQuery.get('service');
|
||||
const [isConfigureServiceModalOpen, setIsConfigureServiceModalOpen] = useState(
|
||||
false,
|
||||
);
|
||||
const openServiceConfigModal = (): void => {
|
||||
setIsConfigureServiceModalOpen(true);
|
||||
logEvent('AWS Integration: Service settings viewed', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
});
|
||||
};
|
||||
|
||||
const { data: serviceDetailsData, isLoading } = useServiceDetails(
|
||||
serviceId || '',
|
||||
cloudAccountId || undefined,
|
||||
);
|
||||
|
||||
const { config, supported_signals } = serviceDetailsData ?? {};
|
||||
|
||||
const totalSupportedSignals = Object.entries(supported_signals || {}).filter(
|
||||
([, value]) => !!value,
|
||||
).length;
|
||||
const enabledSignals = useMemo(
|
||||
() =>
|
||||
Object.values(config || {}).filter((item) => item && item.enabled).length,
|
||||
[config],
|
||||
);
|
||||
|
||||
const isAnySignalConfigured = useMemo(
|
||||
() => !!config?.logs?.enabled || !!config?.metrics?.enabled,
|
||||
[config],
|
||||
);
|
||||
|
||||
// log telemetry event on visiting details of a service.
|
||||
useEffect(() => {
|
||||
if (serviceId) {
|
||||
logEvent('AWS Integration: Service viewed', {
|
||||
cloudAccountId,
|
||||
serviceId,
|
||||
});
|
||||
}
|
||||
}, [cloudAccountId, serviceId]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner size="large" height="50vh" />;
|
||||
}
|
||||
|
||||
if (!serviceDetailsData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const tabItems = getTabItems(serviceDetailsData);
|
||||
|
||||
return (
|
||||
<div className="service-details">
|
||||
<div className="service-details__title-bar">
|
||||
<div className="service-details__details-title">Details</div>
|
||||
<div className="service-details__right-actions">
|
||||
{isAnySignalConfigured && (
|
||||
<ServiceStatus serviceStatus={serviceDetailsData.status} />
|
||||
)}
|
||||
|
||||
{!!cloudAccountId &&
|
||||
(isAnySignalConfigured ? (
|
||||
<Button
|
||||
className="configure-button configure-button--default"
|
||||
onClick={openServiceConfigModal}
|
||||
>
|
||||
Configure ({enabledSignals}/{totalSupportedSignals})
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="primary"
|
||||
className="configure-button configure-button--primary"
|
||||
onClick={openServiceConfigModal}
|
||||
>
|
||||
Enable Service
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="service-details__overview">
|
||||
<MarkdownRenderer
|
||||
variables={{}}
|
||||
markdownContent={serviceDetailsData?.overview}
|
||||
/>
|
||||
</div>
|
||||
<div className="service-details__tabs">
|
||||
<Tabs items={tabItems} />
|
||||
</div>
|
||||
{isConfigureServiceModalOpen && (
|
||||
<ConfigureServiceModal
|
||||
isOpen
|
||||
onClose={(): void => setIsConfigureServiceModalOpen(false)}
|
||||
serviceName={serviceDetailsData.title}
|
||||
serviceId={serviceId || ''}
|
||||
cloudAccountId={cloudAccountId || ''}
|
||||
initialConfig={serviceDetailsData.config}
|
||||
supportedSignals={serviceDetailsData.supported_signals || {}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServiceDetails;
|
||||
@@ -0,0 +1,75 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom-v5-compat';
|
||||
import Spinner from 'components/Spinner';
|
||||
import { useGetAccountServices } from 'hooks/integration/aws/useGetAccountServices';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
|
||||
import ServiceItem from './ServiceItem';
|
||||
|
||||
interface ServicesListProps {
|
||||
cloudAccountId: string;
|
||||
filter: 'all_services' | 'enabled' | 'available';
|
||||
}
|
||||
|
||||
function ServicesList({
|
||||
cloudAccountId,
|
||||
filter,
|
||||
}: ServicesListProps): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const navigate = useNavigate();
|
||||
const { data: services = [], isLoading } = useGetAccountServices(
|
||||
cloudAccountId,
|
||||
);
|
||||
const activeService = urlQuery.get('service');
|
||||
|
||||
const handleActiveService = useCallback(
|
||||
(serviceId: string): void => {
|
||||
const latestUrlQuery = new URLSearchParams(window.location.search);
|
||||
latestUrlQuery.set('service', serviceId);
|
||||
navigate({ search: latestUrlQuery.toString() });
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const filteredServices = useMemo(() => {
|
||||
if (filter === 'all_services') {
|
||||
return services;
|
||||
}
|
||||
|
||||
return services.filter((service) => {
|
||||
const isEnabled =
|
||||
service?.config?.logs?.enabled || service?.config?.metrics?.enabled;
|
||||
return filter === 'enabled' ? isEnabled : !isEnabled;
|
||||
});
|
||||
}, [services, filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeService || !services?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleActiveService(services[0].id);
|
||||
}, [services, activeService, handleActiveService]);
|
||||
|
||||
if (isLoading) {
|
||||
return <Spinner size="large" height="25vh" />;
|
||||
}
|
||||
if (!services) {
|
||||
return <div>No services found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="services-list">
|
||||
{filteredServices.map((service) => (
|
||||
<ServiceItem
|
||||
key={service.id}
|
||||
service={service}
|
||||
onClick={handleActiveService}
|
||||
isActive={service.id === activeService}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServicesList;
|
||||
@@ -1,8 +1,4 @@
|
||||
.services-tabs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(100% - 54px); /* 54px is the height of the header */
|
||||
|
||||
.ant-tabs-tab {
|
||||
font-family: 'Inter';
|
||||
padding: 16px 4px 14px;
|
||||
@@ -22,60 +18,21 @@
|
||||
background: var(--primary-background);
|
||||
}
|
||||
}
|
||||
|
||||
.services-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
gap: 10px;
|
||||
&__sidebar {
|
||||
width: 240px;
|
||||
border-right: 1px solid var(--l2-border);
|
||||
height: 100%;
|
||||
width: 16%;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
width: 84%;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.service-details-loading,
|
||||
.services-list-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
|
||||
.service-details-loading-item {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--muted);
|
||||
}
|
||||
}
|
||||
|
||||
.services-list-empty-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
|
||||
padding: 12px;
|
||||
color: var(--l2-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
|
||||
.empty-state-svg {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.services-filter {
|
||||
padding: 12px;
|
||||
|
||||
padding: 16px 0;
|
||||
.ant-select-selector {
|
||||
background-color: var(--l3-background) !important;
|
||||
border: 1px solid var(--l1-border) !important;
|
||||
@@ -89,111 +46,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.aws-services-list-view {
|
||||
height: 100%;
|
||||
|
||||
.aws-services-list-view-sidebar {
|
||||
width: 240px;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--l3-background);
|
||||
padding: 12px;
|
||||
|
||||
.aws-services-list-view-sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
.aws-services-enabled {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.aws-services-not-enabled {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aws-services-list-view-sidebar-content-header {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.44px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.aws-services-list-view-sidebar-content-item-empty-message {
|
||||
color: var(--l2-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 163.636% */
|
||||
letter-spacing: 0.44px;
|
||||
}
|
||||
|
||||
.aws-services-list-view-sidebar-content-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 4px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
.aws-services-list-view-sidebar-content-item-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.aws-services-list-view-sidebar-content-item-title {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--l1-foreground);
|
||||
font-family: Inter;
|
||||
font-size: 13px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
line-height: 18px; /* 128.571% */
|
||||
|
||||
background-color: var(--l3-background);
|
||||
|
||||
.aws-services-list-view-sidebar-content-item-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.aws-services-list-view-main {
|
||||
flex: 1;
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.service-item {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
@@ -208,22 +60,20 @@
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--l3-background);
|
||||
background-color: var(--bg-ink-100); /* keep: no semantic equivalent */
|
||||
}
|
||||
&__icon-wrapper {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--l3-background);
|
||||
border: 1px solid var(--l1-border);
|
||||
border-radius: 4px;
|
||||
|
||||
.service-item__icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
object-fit: contain;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
&__title {
|
||||
@@ -240,13 +90,11 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
&__title-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 48px;
|
||||
padding: 8px 12px;
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
|
||||
.service-details__details-title {
|
||||
@@ -257,7 +105,6 @@
|
||||
letter-spacing: -0.07px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.service-details__right-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -273,30 +120,19 @@
|
||||
border-radius: 2px;
|
||||
line-height: normal;
|
||||
&--connected {
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--success-background) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--success-background) 10%, transparent);
|
||||
color: var(--callout-success-title);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-forest-500) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-forest-500) 10%, transparent);
|
||||
color: var(--bg-forest-400);
|
||||
}
|
||||
&--stale-data {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--warning-background-hover) 10%,
|
||||
transparent
|
||||
);
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--warning-background-hover) 10%, transparent);
|
||||
color: var(--callout-warning-title);
|
||||
background: color-mix(in srgb, var(--bg-amber-400) 10%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-amber-400) 10%, transparent);
|
||||
color: var(--bg-amber-400);
|
||||
}
|
||||
&--no-data {
|
||||
border: 1px solid
|
||||
color-mix(in srgb, var(--danger-background-hover) 10%, transparent);
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--danger-background-hover) 10%,
|
||||
transparent
|
||||
);
|
||||
color: var(--callout-error-description);
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
|
||||
color: var(--bg-cherry-400);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,28 +157,21 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__overview {
|
||||
color: var(--l2-foreground);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 20px; /* 142.857% */
|
||||
letter-spacing: -0.07px;
|
||||
width: 100%;
|
||||
|
||||
padding: 8px 12px;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
padding: 0px 12px 12px 8px;
|
||||
|
||||
.ant-tabs {
|
||||
&-ink-bar {
|
||||
background-color: transparent;
|
||||
}
|
||||
&-nav {
|
||||
padding: 0;
|
||||
|
||||
padding: 8px 0 18px;
|
||||
&-wrap {
|
||||
padding: 0;
|
||||
}
|
||||
@@ -461,3 +290,153 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.lightMode {
|
||||
.services-tabs {
|
||||
.ant-tabs-tab {
|
||||
&.ant-tabs-tab-active {
|
||||
.ant-tabs-tab-btn {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.services-filter {
|
||||
.ant-select-selector {
|
||||
background-color: var(--l1-background) !important;
|
||||
border-color: var(--l1-border) !important;
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
|
||||
.ant-select-arrow {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.service-item {
|
||||
&:not(:last-child) {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--l3-background);
|
||||
}
|
||||
|
||||
&__icon-wrapper {
|
||||
background-color: var(--l1-background);
|
||||
border-color: var(--l1-border);
|
||||
}
|
||||
|
||||
&__title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.service-details {
|
||||
&__title-bar {
|
||||
border-bottom: 1px solid var(--l1-border);
|
||||
|
||||
.service-details__details-title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
.configure-button {
|
||||
color: var(--l1-foreground);
|
||||
background: var(--l1-background);
|
||||
border-color: var(--l1-border);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--l2-foreground);
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.service-status {
|
||||
&--connected {
|
||||
border: 1px solid color-mix(in srgb, var(--bg-forest-500) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-forest-500) 10%, transparent);
|
||||
color: var(--bg-forest-500);
|
||||
}
|
||||
|
||||
&--stale-data {
|
||||
border: 1px solid color-mix(in srgb, var(--bg-amber-400) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-amber-400) 10%, transparent);
|
||||
color: var(--bg-amber-500);
|
||||
}
|
||||
|
||||
&--no-data {
|
||||
border: 1px solid color-mix(in srgb, var(--bg-cherry-400) 20%, transparent);
|
||||
background: color-mix(in srgb, var(--bg-cherry-400) 10%, transparent);
|
||||
color: var(--bg-cherry-500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__overview {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
|
||||
&__tabs {
|
||||
.ant-tabs {
|
||||
&-tab {
|
||||
&-btn {
|
||||
color: var(--l1-foreground) !important;
|
||||
|
||||
&[aria-selected='true'] {
|
||||
color: var(--l1-foreground) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&-active {
|
||||
background: var(--l3-background);
|
||||
}
|
||||
}
|
||||
|
||||
&-nav-list {
|
||||
border-color: var(--l1-border);
|
||||
background: var(--l1-background);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-service {
|
||||
&-dashboard-item {
|
||||
&__title {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
&-data-collected {
|
||||
&__table {
|
||||
.ant-table {
|
||||
border-color: var(--l1-border);
|
||||
|
||||
.ant-table-thead {
|
||||
> tr > th {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-tbody {
|
||||
> tr {
|
||||
&:nth-child(odd),
|
||||
&:hover > td {
|
||||
background: var(--l1-background) !important;
|
||||
}
|
||||
|
||||
> td {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__table-heading {
|
||||
color: var(--l1-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useQuery } from 'react-query';
|
||||
import { Color } from '@signozhq/design-tokens';
|
||||
import type { SelectProps, TabsProps } from 'antd';
|
||||
import { Select, Tabs } from 'antd';
|
||||
import { getAwsServices } from 'api/integration/aws';
|
||||
import { REACT_QUERY_KEY } from 'constants/reactQueryKeys';
|
||||
import useUrlQuery from 'hooks/useUrlQuery';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
|
||||
import ServiceDetails from './ServiceDetails';
|
||||
import ServicesList from './ServicesList';
|
||||
|
||||
import './ServicesTabs.style.scss';
|
||||
|
||||
export enum ServiceFilterType {
|
||||
ALL_SERVICES = 'all_services',
|
||||
ENABLED = 'enabled',
|
||||
AVAILABLE = 'available',
|
||||
}
|
||||
|
||||
interface ServicesFilterProps {
|
||||
cloudAccountId: string;
|
||||
onFilterChange: (value: ServiceFilterType) => void;
|
||||
}
|
||||
|
||||
function ServicesFilter({
|
||||
cloudAccountId,
|
||||
onFilterChange,
|
||||
}: ServicesFilterProps): JSX.Element | null {
|
||||
const { data: services, isLoading } = useQuery(
|
||||
[REACT_QUERY_KEY.AWS_SERVICES, cloudAccountId],
|
||||
() => getAwsServices(cloudAccountId),
|
||||
);
|
||||
|
||||
const { enabledCount, availableCount } = useMemo(() => {
|
||||
if (!services) {
|
||||
return { enabledCount: 0, availableCount: 0 };
|
||||
}
|
||||
|
||||
return services.reduce(
|
||||
(acc, service) => {
|
||||
const isEnabled =
|
||||
service?.config?.logs?.enabled || service?.config?.metrics?.enabled;
|
||||
return {
|
||||
enabledCount: acc.enabledCount + (isEnabled ? 1 : 0),
|
||||
availableCount: acc.availableCount + (isEnabled ? 0 : 1),
|
||||
};
|
||||
},
|
||||
{ enabledCount: 0, availableCount: 0 },
|
||||
);
|
||||
}, [services]);
|
||||
|
||||
const selectOptions: SelectProps['options'] = useMemo(
|
||||
() => [
|
||||
{ value: 'all_services', label: `All Services (${services?.length || 0})` },
|
||||
{ value: 'enabled', label: `Enabled (${enabledCount})` },
|
||||
{ value: 'available', label: `Available (${availableCount})` },
|
||||
],
|
||||
[services, enabledCount, availableCount],
|
||||
);
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
if (!services?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="services-filter">
|
||||
<Select
|
||||
style={{ width: '100%' }}
|
||||
defaultValue={ServiceFilterType.ALL_SERVICES}
|
||||
options={selectOptions}
|
||||
className="services-sidebar__select"
|
||||
suffixIcon={<ChevronDown size={16} color={Color.BG_VANILLA_400} />}
|
||||
onChange={onFilterChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ServicesSection(): JSX.Element {
|
||||
const urlQuery = useUrlQuery();
|
||||
const cloudAccountId = urlQuery.get('cloudAccountId') || '';
|
||||
|
||||
const [activeFilter, setActiveFilter] = useState<
|
||||
'all_services' | 'enabled' | 'available'
|
||||
>('all_services');
|
||||
|
||||
return (
|
||||
<div className="services-section">
|
||||
<div className="services-section__sidebar">
|
||||
<ServicesFilter
|
||||
cloudAccountId={cloudAccountId}
|
||||
onFilterChange={setActiveFilter}
|
||||
/>
|
||||
<ServicesList cloudAccountId={cloudAccountId} filter={activeFilter} />
|
||||
</div>
|
||||
<div className="services-section__content">
|
||||
<ServiceDetails />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ServicesTabs(): JSX.Element {
|
||||
const tabItems: TabsProps['items'] = [
|
||||
{
|
||||
key: 'services',
|
||||
label: 'Services For Integration',
|
||||
children: <ServicesSection />,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="services-tabs">
|
||||
<Tabs defaultActiveKey="services" items={tabItems} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ServicesTabs;
|
||||
@@ -0,0 +1,161 @@
|
||||
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest, RestRequest } from 'msw'; // Import RestRequest for req.json() typing
|
||||
|
||||
import { UpdateServiceConfigPayload } from '../types';
|
||||
import { accountsResponse, CLOUD_ACCOUNT_ID, initialBuckets } from './mockData';
|
||||
import {
|
||||
assertGenericModalElements,
|
||||
assertS3SyncSpecificElements,
|
||||
renderModal,
|
||||
} from './utils';
|
||||
|
||||
// --- MOCKS ---
|
||||
jest.mock('hooks/useUrlQuery', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => ({
|
||||
get: jest.fn((paramName: string) => {
|
||||
if (paramName === 'cloudAccountId') {
|
||||
return CLOUD_ACCOUNT_ID;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
// --- TEST SUITE ---
|
||||
describe('ConfigureServiceModal for S3 Sync service', () => {
|
||||
jest.setTimeout(10000);
|
||||
beforeEach(() => {
|
||||
server.use(
|
||||
rest.get(
|
||||
'http://localhost/api/v1/cloud-integrations/aws/accounts',
|
||||
(req, res, ctx) => res(ctx.json(accountsResponse)),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should render with logs collection switch and bucket selectors (no buckets initially selected)', async () => {
|
||||
act(() => {
|
||||
renderModal({}); // No initial S3 buckets, defaults to 's3sync' serviceId
|
||||
});
|
||||
await assertGenericModalElements(); // Use new generic assertion
|
||||
await assertS3SyncSpecificElements({}); // Use new S3-specific assertion
|
||||
});
|
||||
|
||||
it('should render with logs collection switch and bucket selectors (some buckets initially selected)', async () => {
|
||||
act(() => {
|
||||
renderModal(initialBuckets); // Defaults to 's3sync' serviceId
|
||||
});
|
||||
await assertGenericModalElements(); // Use new generic assertion
|
||||
await assertS3SyncSpecificElements(initialBuckets); // Use new S3-specific assertion
|
||||
});
|
||||
|
||||
it('should enable save button after adding a new bucket via combobox', async () => {
|
||||
act(() => {
|
||||
renderModal(initialBuckets); // Defaults to 's3sync' serviceId
|
||||
});
|
||||
await assertGenericModalElements();
|
||||
await assertS3SyncSpecificElements(initialBuckets);
|
||||
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
||||
|
||||
const targetCombobox = screen.getAllByRole('combobox')[0];
|
||||
const newBucketName = 'a-newly-added-bucket';
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(targetCombobox, { target: { value: newBucketName } });
|
||||
fireEvent.keyDown(targetCombobox, {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
keyCode: 13,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(newBucketName)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('should send updated bucket configuration on save', async () => {
|
||||
let capturedPayload: UpdateServiceConfigPayload | null = null;
|
||||
const mockUpdateConfigUrl =
|
||||
'http://localhost/api/v1/cloud-integrations/aws/services/s3sync/config';
|
||||
|
||||
// Override POST handler specifically for this test to capture payload
|
||||
server.use(
|
||||
rest.post(mockUpdateConfigUrl, async (req: RestRequest, res, ctx) => {
|
||||
capturedPayload = await req.json();
|
||||
return res(ctx.status(200), ctx.json({ message: 'Config updated' }));
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
renderModal(initialBuckets); // Defaults to 's3sync' serviceId
|
||||
});
|
||||
await assertGenericModalElements();
|
||||
await assertS3SyncSpecificElements(initialBuckets);
|
||||
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
|
||||
|
||||
const newBucketName = 'another-new-bucket';
|
||||
// As before, targeting the first combobox, assumed to be for 'ap-south-1'.
|
||||
const targetCombobox = screen.getAllByRole('combobox')[0];
|
||||
|
||||
act(() => {
|
||||
fireEvent.change(targetCombobox, { target: { value: newBucketName } });
|
||||
fireEvent.keyDown(targetCombobox, {
|
||||
key: 'Enter',
|
||||
code: 'Enter',
|
||||
keyCode: 13,
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(newBucketName)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /save/i })).toBeEnabled();
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByRole('button', { name: /save/i }));
|
||||
});
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(capturedPayload).not.toBeNull();
|
||||
});
|
||||
|
||||
expect(capturedPayload).toEqual({
|
||||
cloud_account_id: CLOUD_ACCOUNT_ID,
|
||||
config: {
|
||||
logs: {
|
||||
enabled: true,
|
||||
s3_buckets: {
|
||||
'us-east-2': ['first-bucket', 'second-bucket'], // Existing buckets
|
||||
'ap-south-1': [newBucketName], // Newly added bucket for the first region
|
||||
},
|
||||
},
|
||||
metrics: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render S3 bucket region selector UI for services other than s3sync', async () => {
|
||||
const otherServiceId = 'cloudwatch';
|
||||
act(() => {
|
||||
renderModal({}, otherServiceId);
|
||||
});
|
||||
await assertGenericModalElements();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.queryByRole('heading', { name: /select s3 buckets by region/i }),
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
const regions = accountsResponse.data.accounts[0]?.config?.regions || [];
|
||||
regions.forEach((region) => {
|
||||
expect(
|
||||
screen.queryByText(`Enter S3 bucket names for ${region}`),
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { IConfigureServiceModalProps } from '../ConfigureServiceModal';
|
||||
|
||||
const CLOUD_ACCOUNT_ID = '123456789012';
|
||||
|
||||
const initialBuckets = { 'us-east-2': ['first-bucket', 'second-bucket'] };
|
||||
|
||||
const accountsResponse = {
|
||||
status: 'success',
|
||||
data: {
|
||||
accounts: [
|
||||
{
|
||||
id: 'a1b2c3d4-e5f6-7890-1234-567890abcdef',
|
||||
cloud_account_id: CLOUD_ACCOUNT_ID,
|
||||
config: {
|
||||
regions: ['ap-south-1', 'ap-south-2', 'us-east-1', 'us-east-2'],
|
||||
},
|
||||
status: {
|
||||
integration: {
|
||||
last_heartbeat_ts_ms: 1747114366214,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const defaultModalProps: Omit<IConfigureServiceModalProps, 'initialConfig'> = {
|
||||
isOpen: true,
|
||||
onClose: jest.fn(),
|
||||
serviceName: 'S3 Sync',
|
||||
serviceId: 's3sync',
|
||||
cloudAccountId: CLOUD_ACCOUNT_ID,
|
||||
supportedSignals: {
|
||||
logs: true,
|
||||
metrics: false,
|
||||
},
|
||||
};
|
||||
|
||||
export {
|
||||
accountsResponse,
|
||||
CLOUD_ACCOUNT_ID,
|
||||
defaultModalProps,
|
||||
initialBuckets,
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import { render, RenderResult, screen, waitFor } from '@testing-library/react';
|
||||
import MockQueryClientProvider from 'providers/test/MockQueryClientProvider';
|
||||
|
||||
import ConfigureServiceModal from '../ConfigureServiceModal';
|
||||
import { accountsResponse, defaultModalProps } from './mockData';
|
||||
|
||||
/**
|
||||
* Renders the ConfigureServiceModal with specified S3 bucket initial configurations.
|
||||
*/
|
||||
const renderModal = (
|
||||
initialConfigLogsS3Buckets: Record<string, string[]> = {},
|
||||
serviceId = 's3sync',
|
||||
): RenderResult => {
|
||||
const initialConfig = {
|
||||
logs: { enabled: true, s3_buckets: initialConfigLogsS3Buckets },
|
||||
metrics: { enabled: false },
|
||||
};
|
||||
|
||||
return render(
|
||||
<MockQueryClientProvider>
|
||||
<ConfigureServiceModal
|
||||
{...defaultModalProps}
|
||||
serviceId={serviceId}
|
||||
initialConfig={initialConfig}
|
||||
/>
|
||||
</MockQueryClientProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts that generic UI elements of the modal are present.
|
||||
*/
|
||||
const assertGenericModalElements = async (): Promise<void> => {
|
||||
await waitFor(() => {
|
||||
expect(screen.getByRole('switch')).toBeInTheDocument();
|
||||
expect(screen.getByText(/log collection/i)).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByText(
|
||||
/to ingest logs from your aws services, you must complete several steps/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Asserts the state of S3 bucket selectors for each region, specific to S3 Sync.
|
||||
*/
|
||||
const assertS3SyncSpecificElements = async (
|
||||
expectedBucketsByRegion: Record<string, string[]> = {},
|
||||
): Promise<void> => {
|
||||
const regions = accountsResponse.data.accounts[0]?.config?.regions || [];
|
||||
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
screen.getByRole('heading', { name: /select s3 buckets by region/i }),
|
||||
).toBeInTheDocument();
|
||||
|
||||
regions.forEach((region) => {
|
||||
expect(screen.getByText(region)).toBeInTheDocument();
|
||||
const bucketsForRegion = expectedBucketsByRegion[region] || [];
|
||||
if (bucketsForRegion.length > 0) {
|
||||
bucketsForRegion.forEach((bucket) => {
|
||||
expect(screen.getByText(bucket)).toBeInTheDocument();
|
||||
});
|
||||
} else {
|
||||
expect(
|
||||
screen.getByText(`Enter S3 bucket names for ${region}`),
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
assertGenericModalElements,
|
||||
assertS3SyncSpecificElements,
|
||||
renderModal,
|
||||
};
|
||||
@@ -1,40 +1,90 @@
|
||||
import { ServiceData } from 'container/Integrations/types';
|
||||
|
||||
interface Service {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
config: AWSServiceConfig;
|
||||
config: ServiceConfig;
|
||||
}
|
||||
|
||||
interface S3BucketsByRegion {
|
||||
[region: string]: string[];
|
||||
interface Dashboard {
|
||||
id: string;
|
||||
url: string;
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
interface LogField {
|
||||
name: string;
|
||||
path: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
interface Metric {
|
||||
name: string;
|
||||
type: string;
|
||||
unit: string;
|
||||
}
|
||||
|
||||
interface ConfigStatus {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
interface DataStatus {
|
||||
last_received_ts_ms: number;
|
||||
last_received_from: string;
|
||||
}
|
||||
|
||||
interface S3BucketsByRegion {
|
||||
[region: string]: string[];
|
||||
}
|
||||
|
||||
interface LogsConfig extends ConfigStatus {
|
||||
s3_buckets?: S3BucketsByRegion;
|
||||
}
|
||||
|
||||
interface AWSServiceConfig {
|
||||
interface ServiceConfig {
|
||||
logs: LogsConfig;
|
||||
metrics: ConfigStatus;
|
||||
s3_sync?: LogsConfig;
|
||||
}
|
||||
|
||||
interface IServiceStatus {
|
||||
logs: DataStatus | null;
|
||||
metrics: DataStatus | null;
|
||||
}
|
||||
|
||||
interface SupportedSignals {
|
||||
metrics: boolean;
|
||||
logs: boolean;
|
||||
}
|
||||
|
||||
interface ServiceData {
|
||||
id: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
overview: string;
|
||||
supported_signals: SupportedSignals;
|
||||
assets: {
|
||||
dashboards: Dashboard[];
|
||||
};
|
||||
data_collected: {
|
||||
logs?: LogField[];
|
||||
metrics: Metric[];
|
||||
};
|
||||
config?: ServiceConfig;
|
||||
status?: IServiceStatus;
|
||||
}
|
||||
|
||||
interface ServiceDetailsResponse {
|
||||
status: 'success';
|
||||
data: ServiceData;
|
||||
}
|
||||
|
||||
export interface AWSCloudAccountConfig {
|
||||
interface CloudAccountConfig {
|
||||
regions: string[];
|
||||
}
|
||||
|
||||
export interface IntegrationStatus {
|
||||
interface IntegrationStatus {
|
||||
last_heartbeat_ts_ms: number;
|
||||
}
|
||||
|
||||
@@ -45,9 +95,8 @@ interface AccountStatus {
|
||||
interface CloudAccount {
|
||||
id: string;
|
||||
cloud_account_id: string;
|
||||
config: AWSCloudAccountConfig;
|
||||
config: CloudAccountConfig;
|
||||
status: AccountStatus;
|
||||
providerAccountId: string;
|
||||
}
|
||||
|
||||
interface CloudAccountsData {
|
||||
@@ -84,13 +133,15 @@ interface UpdateServiceConfigResponse {
|
||||
}
|
||||
|
||||
export type {
|
||||
AWSServiceConfig,
|
||||
CloudAccount,
|
||||
CloudAccountsData,
|
||||
IServiceStatus,
|
||||
S3BucketsByRegion,
|
||||
Service,
|
||||
ServiceConfig,
|
||||
ServiceData,
|
||||
ServiceDetailsResponse,
|
||||
SupportedSignals,
|
||||
UpdateServiceConfigPayload,
|
||||
UpdateServiceConfigResponse,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react';
|
||||
import { server } from 'mocks-server/server';
|
||||
import { rest } from 'msw';
|
||||
import {
|
||||
IntegrationType,
|
||||
RequestIntegrationBtn,
|
||||
} from 'pages/Integrations/RequestIntegrationBtn';
|
||||
import i18n from 'ReactI18';
|
||||
|
||||
describe('Request AWS integration', () => {
|
||||
it('should render the request integration button', async () => {
|
||||
let capturedPayload: any;
|
||||
server.use(
|
||||
rest.post('http://localhost/api/v1/event', async (req, res, ctx) => {
|
||||
capturedPayload = await req.json();
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
statusCode: 200,
|
||||
error: null,
|
||||
payload: 'Event Processed Successfully',
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
act(() => {
|
||||
render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<RequestIntegrationBtn type={IntegrationType.AWS_SERVICES} />{' '}
|
||||
</I18nextProvider>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByText(
|
||||
/can't find what you’re looking for\? request more integrations/i,
|
||||
),
|
||||
).toBeInTheDocument();
|
||||
|
||||
await act(() => {
|
||||
fireEvent.change(screen.getByPlaceholderText(/Enter integration name/i), {
|
||||
target: { value: 's3 sync' },
|
||||
});
|
||||
|
||||
const submitButton = screen.getByRole('button', { name: /submit/i });
|
||||
|
||||
expect(submitButton).toBeEnabled();
|
||||
|
||||
fireEvent.click(submitButton);
|
||||
});
|
||||
|
||||
expect(capturedPayload.eventName).toBeDefined();
|
||||
expect(capturedPayload.attributes).toBeDefined();
|
||||
|
||||
expect(capturedPayload.eventName).toBe('AWS service integration requested');
|
||||
expect(capturedPayload.attributes).toEqual({
|
||||
screen: 'AWS integration details',
|
||||
integration: 's3 sync',
|
||||
deployment_url: 'localhost',
|
||||
user_email: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user