Compare commits

...

78 Commits

Author SHA1 Message Date
YounixM
3476f3032d format 2024-06-22 14:45:47 +05:30
YounixM
4b757b382f feat: resizable table 2024-06-22 14:04:55 +05:30
CheetoDa
8ff392bc96 feat: azure monitoring docs (#5159)
* feat: azure monitoring docs

* chore: mapped paths

* chore: fixed instructions

* fix: added central collector steps

* fix: handle default azure steps, card alignment and reload issues

* fix: removed return true

---------

Co-authored-by: YounixM <myounis.ar@live.com>
2024-06-21 15:05:37 +05:30
SagarRajput-7
b59d9c7b90 fix: handled value as string from queryParams for trace filters (#5274)
* fix: handled value as string from queryParams for trace filters

* fix: added isArray check
2024-06-21 12:55:27 +05:30
Rajat Dabade
afcee9cd02 refactor: add to query should not open log detail drawer (#4732)
Co-authored-by: Rajat-Dabade <rajat@signoz.io>
2024-06-21 11:46:04 +05:30
Vikrant Gupta
82a079e687 fix: move date time picker to click rather than hover (#5296) 2024-06-20 19:19:42 +05:30
Yunus M
adfeaaa1f0 feat: pass fill gaps to query range api (#5276) 2024-06-20 18:34:05 +05:30
Srikanth Chekuri
6ee9705599 chore: bump SigNoz/signoz-otel-collector and SigNoz/prometheus (#5294) 2024-06-20 18:33:45 +05:30
Vikrant Gupta
67965c8e4d fix: dependent variable panel not updating (#5283)
* fix: dependent variable panel not updating

* fix: build issues
2024-06-20 17:21:04 +05:30
Yunus M
38b1de5ccc feat: [5038] enable list view - add to dashboard (#5268)
* feat: [5038] enable list view - add to dashboard

* feat: pass page size for list view export
2024-06-20 17:05:18 +05:30
Yunus M
64e06ab3f9 fix: update from typography link to react router dom link component to maintain global state (#5279) 2024-06-20 11:34:27 +05:30
Vikrant Gupta
f3c2fb0246 fix: dashboard empty state learn more link not working (#5287) 2024-06-19 20:31:36 +05:30
Nityananda Gohain
a4e98e565d feat: sanitize query and remove groupBy for list panel query (#5285) 2024-06-19 15:40:34 +05:30
Srikanth Chekuri
faa1728b8c chore: threshold rule panel type to graph (#5284) 2024-06-19 14:19:30 +05:30
SagarRajput-7
b69e97d7b0 fix: fixed flakiness in alert list actions - delete, edit, clone & toggle (#5237)
* fix: fixed flakiness in alert list actions - delete, edit, clone & toggle

* fix: added onhover dropdown open and close
2024-06-19 12:10:43 +05:30
Vikrant Gupta
c0195e9dc9 fix: added null checks for stacked bar chart with fallbacks (#5282) 2024-06-19 11:50:18 +05:30
Vishal Sharma
b69545a771 fix: update companyDomain in before firing every event (#5275) 2024-06-19 10:49:57 +05:30
Vikrant Gupta
9a6db272c1 fix: update the error boundary components with sentry error boundary components (#5271)
* fix: update the error boundary components with sentry error boundary components

* fix: update fallback components
2024-06-18 19:04:06 +05:30
SagarRajput-7
45d6430ab3 fix: fixed panelType when creating alerts from histogram dashboard (#5251)
* fix: fixed panelType when creating alerts from histogram dashboard

* fix: added PANEL_TYPES.TIME_SERIES always for all type in case of alerts
2024-06-18 13:48:28 +05:30
SagarRajput-7
cf7bf32ac2 fix: fixed lightMode style for histogram panel (#5236) 2024-06-18 13:38:30 +05:30
SagarRajput-7
1695b4f06d fix: convert timestamp in new trace explorer to user browser timezone and readable format (#5235)
* fix: convert timestamp in new trace explorer to user browser timezone and readable format

* fix: code refactor
2024-06-18 13:24:54 +05:30
SagarRajput-7
a65d5095a0 feat: added checkbox selection in dashboard variables (#5191)
* feat: added checkbox selection in dashboard variables

* feat: added checkbox selection - handling with only and all

* feat: added checkbox selection - style changes

* fix: fixed deselecting all options

* feat: fixed all showing up in single select

* feat: improve styles

* feat: fixed single select getting all values and array issues

* feat: updated test case

* feat: added max tag shown logic with count length and info on hover for overflowed content
2024-06-18 13:02:15 +05:30
Vikrant Gupta
0fade428ef fix: table row data doesn't align with the response (#5248)
* fix: wrong values getting associated with the table rows

* fix: table columns rendering

* fix: remove console logs
2024-06-18 12:25:10 +05:30
Vikrant Gupta
3b4b9e43b3 fix: trace explorer not highlighting in sidenav (#5263) 2024-06-18 11:58:35 +05:30
Srikanth Chekuri
c104b758ba chore: adjust the step interval for builder queries (#5253) 2024-06-17 22:59:28 +05:30
Vikrant Gupta
2a4e97f8da fix: table sorting when units are present (#5249) 2024-06-17 15:51:04 +05:30
Srikanth Chekuri
f1b5da9916 chore: fix elapsed time formatting (#5238) 2024-06-17 09:00:55 +05:30
Yunus M
fe87711b25 fix: update view options not visible (#5232)
Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2024-06-15 15:53:26 +05:30
SagarRajput-7
f824aa17dc feat: added APM to new trace filter redirection (#5225) 2024-06-15 13:53:56 +05:30
Yunus M
e6ee5fc9e3 fix: text wrap in log details view, remove json/raw tag (#5231) 2024-06-15 13:07:26 +05:30
SagarRajput-7
a788230e70 feat: added alert rule empty state and product edu (#5185)
* feat: added alert rule empty state and product edu

* feat: added lightMode styles

* chore: update docs and card links

* feat: code refactor and added loadingState for newAlert btn

* chore: update alert links

---------

Co-authored-by: makeavish <makeavish786@gmail.com>
2024-06-15 12:56:37 +05:30
Yunus M
34750aba84 feat: show warning when the top level operations count is more than 2500 (#5193) 2024-06-14 17:06:40 +05:30
Srikanth Chekuri
dbfa4e80bb chore: add common resource attributes for span metrics (#5224) 2024-06-14 16:24:28 +05:30
Srikanth Chekuri
ded58f5392 fix: do not return zero value for non-aggregated table result (#5217) 2024-06-14 16:23:56 +05:30
SagarRajput-7
aa9689e025 feat: added an option to create channel when no Channels are available in alert config (#5195)
* feat: added an option to create channel when no Channels are availabel in alert config

* feat: added tooltip for the case when nochannel

* feat: opened notification channel creation in new tab

* feat: added role permission on create-notification-btn and disabled on loading

* feat: added admin permission required message in tooltip

---------

Co-authored-by: Vishal Sharma <makeavish786@gmail.com>
2024-06-14 11:23:34 +05:30
Vikrant Gupta
f391ca8bb1 fix: dashboard listing default sorting by updatedAt (#5200)
* fix: dashboard listing default sorting by createdAt

* fix: respect pagination options

* fix: make it backwards compatible

* fix: sort by default by updated at

* fix: sort by default by updated at
2024-06-14 11:22:59 +05:30
SagarRajput-7
191a2a319d feat: added link for example alerts (#5216) 2024-06-13 23:50:47 +05:30
Vishal Sharma
313fa4ae23 chore: open clickhouse builder by default on creating new exception based alert (#5214) 2024-06-13 22:16:39 +05:30
Srikanth Chekuri
cacf4b99c2 fix: apply having and limit clause on formulas (#5201) 2024-06-13 20:37:44 +05:30
SagarRajput-7
1f4a8b9834 fix: fixed flaky pipelineSearchSection test case (#5189) 2024-06-13 10:40:49 +05:30
SagarRajput-7
a681f6f397 fix: fix colors of graphs for lightMode and darkMode (#5192)
* fix: fix colors of graphs for lightMode and darkMode

* fix: fix colors of graphs for lightMode and darkMode
2024-06-13 09:57:29 +05:30
Vikrant Gupta
dc294ff6d5 fix: enable custom aggregate interval for all data sources across application (#5074)
* fix: step interval not working for logs alerts

* fix: build issues

* fix: do not auto update the step interval when user enters some value

* feat: remove initial default 60 from step interval

* feat: revert last change

* fix: step interval mapping

* fix: remove initial default 60 from step interval

* Revert "fix: remove initial default 60 from step interval"

This reverts commit d23ce5e7e2.

* chore: fix backend

* chore: remove backend changes

* feat: enable the aggregate every interval across product

* fix: handle full view and landing view changes

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-06-12 17:38:05 +05:30
Vikrant Gupta
af8907d4f8 fix: default alerts yaxis unit doesn't reset on page change (#5199) 2024-06-12 17:34:42 +05:30
Srikanth Chekuri
f01b4f2c03 chore: support custom step interval (#5186)
* chore: support custom step interval

* chore: add comments
2024-06-12 12:21:27 +05:30
dependabot[bot]
a94231c00a chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/azidentity from 1.3.0 to 1.6.0 (#5194)
Bumps [github.com/Azure/azure-sdk-for-go/sdk/azidentity](https://github.com/Azure/azure-sdk-for-go) from 1.3.0 to 1.6.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.3.0...sdk/azcore/v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/azidentity
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-12 07:30:29 +05:30
Srikanth Chekuri
2f949d2738 chore: address large number of staticcheck issues (#5176)
* chore: address large number of staticcheck issues

* chore: fix tests

* chore: fix more issue

* chore: fix options
2024-06-11 20:10:38 +05:30
Srikanth Chekuri
1645523ae9 chore: add fill gaps for query range (#5155)
* chore: add fill gaps for query range

* chore: fix empty result
2024-06-11 19:58:52 +05:30
SagarRajput-7
a319d1ec53 feat: added option to download billing usage data as csv (#5158)
* feat: added option to download billing usage data as csv

* feat: rounded off values to 2 decimal places

* feat: removed state and use useRef

* feat: removed ref and added a function handler for csv

* feat: added try-catch logic for handleCsvDownload function

* feat: added successful notification
2024-06-11 19:21:33 +05:30
Vikrant Gupta
699f79d6ba fix: respect the sorting params present in the URL for dashboards list page (#5183)
* fix: respect the sorting params present in the URL for dashboards list page

* fix: move the list order params to context
2024-06-10 18:13:51 +05:30
Vikrant Gupta
a3e36cbac9 fix: unsaved changes popping up for newly created dashboard in settings (#5182)
* fix: unsaved changes popping up for newly created dashboard in settings

* fix: width changing of right bar when adding new queries

* fix: address review comments
2024-06-10 18:12:20 +05:30
Vikrant Gupta
f2aba5035a feat: added support for bar chart stacking (#5138)
* feat: stacked bars uplot poc

* feat: stacked bars uplot poc

* feat: reverse the legend order

* fix: tooltip

* feat: added bands

* feat: added bands calculation function

* feat: code cleanup and added toggle for stacked and unstacked bars

* feat: minor fixes and better naming

* feat: fix jest test cases

* feat: fix data on view mode of bar chart stacked

* feat: make transulecent colors

* fix: build issues

* fix: legend issues in bar chart edit mode

* feat: added implementation details and refactored code in tooltip plugin

* fix: added missing return statement

* fix: eslint prettier issues

* fix: legend visibility issues on view mode

* fix: legend visibility issues on view mode

* feat: added info text

* fix: add info text only in full view mode

* fix: issue with zero index
2024-06-10 17:15:30 +05:30
Vikrant Gupta
6af5aa0253 fix: clickhouse and promQL queries table column headers not handled (#5164)
* fix: clickhouse and promQL queries table column headers not handled

* fix: handling of per query units for clickhouse and promQL queries as well
2024-06-10 12:43:03 +05:30
Srikanth Chekuri
8a9c8031f5 fix: use correct column name for v4 tables (#5177) 2024-06-10 11:29:31 +05:30
Yunus M
cf54b5f9ec chore: track from which page user visited to support page (#5168)
* chore: track from which page user visited to support page

* chore: add safety check

* fix: remove the merged_queries label from histogram

---------

Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
2024-06-07 14:49:34 +05:30
Vikrant Gupta
a17928df88 fix: clone panel randomly jumping around in the middle causing layout issues (#5161) 2024-06-06 17:16:14 +05:30
Vikrant Gupta
c53e6de689 fix: theme analytics not getting reported as user is being fetched delayed (#5162) 2024-06-06 17:04:00 +05:30
SagarRajput-7
35c054835a fix: fixed broken header style in dashboard detail page (#5154) 2024-06-05 22:17:28 +05:30
Srikanth Chekuri
e5f96ac896 fix: do not mutate the rule condition (#5141)
* fix: do not mutate the rule condition

* chore: add unit

* chore: add test
2024-06-05 19:35:48 +05:30
SagarRajput-7
694f2562bf feat: integration: added copy test tracking, facing issue btn and request more integration section (#5130)
* feat: integration: added copy test tracking, facing issue btn and request more integration section

* feat: removed copy btn tracking from onboarding markdown

* feat: removed copy btn tracking from onboarding markdown

* feat: added track event on Configure option clicks

* feat: code cleanup

* feat: code cleanup

* feat: changed trackEvent to logEvent

* feat: changed text in integration facing issue button

* feat: sliced copied text

* feat: code cleanup
2024-06-05 19:35:03 +05:30
Srikanth Chekuri
6a829489a8 fix: remove the need for unnecessary dummy time column (#5108)
* fix: remove the need for unnecessary dummy time column

* chore: allow no timestamp

* chore: remove grouping sets
2024-06-05 19:33:45 +05:30
Srikanth Chekuri
d1c075983f fix: formula evaluation with non-participating queries (#5143) 2024-06-05 19:25:45 +05:30
SagarRajput-7
4c7f90dad8 feat: added facing issues and requestIntegration section in integration pages (#5147)
* feat: added facing issues and requestIntegration section in integration pages

* feat: changed text in integration facing issue button

* feat: fixed facing-issue tooltip styles

* feat: code cleanup

* feat: added margin bottom to request more component
2024-06-05 19:18:34 +05:30
Yunus M
bc8a235915 fix: incorrect order by label in query builder (#5148) 2024-06-05 18:28:58 +05:30
SagarRajput-7
c703f5290a feat: trace-filter style and light mode fixes (#5142)
* feat: trace-filter style and light mode fixes

* fix: removed duration option passed as undefined

* feat: fixed the debounced function on attribute values API call
2024-06-05 17:51:59 +05:30
Yunus M
309ed3d1de chore: update phin version in resolutions (#5149) 2024-06-05 12:42:13 +05:30
Vikrant Gupta
2a3622130f fix: alerts builder light theme plot tag issue (#5146)
* fix: alerts builder light theme plot tag issue

* fix: remove console logs
2024-06-05 12:11:11 +05:30
Rajat Dabade
7e9bf2d48d feat: add histogram visualisation support (#4858)
* refactor: added panel type histogram and basic setup done

* feat: histogram for one query

* refactor: multiple query support histogram

* chore: typecorrection

* refactor: done with legend part

* refactor: legend change fix

* refactor: fix the tooltip value for histogram

* refactor: disable drag for histogram

* fix: tsc

* refactor: handled panel type change in edit mode

* refactor: full view done (#4881)

Co-authored-by: Rajat-Dabade <rajat@signoz.io>

* [Feat]: Full view histogram (#4867)

* refactor: added panel type histogram and basic setup done

* feat: histogram for one query

* refactor: multiple query support histogram

* chore: typecorrection

* refactor: done with legend part

* refactor: legend change fix

* refactor: fix the tooltip value for histogram

* refactor: disable drag for histogram

* fix: tsc

* refactor: handled panel type change in edit mode

* refactor: full view done

---------

Co-authored-by: Rajat-Dabade <rajat@signoz.io>
Co-authored-by: Yunus M <myounis.ar@live.com>

* feat: histogram customisations (#5133)

* feat: added bucket size and bucket width enhancements for histogram

* fix: added handling for bucket size bucket count and combine into one series

* fix: added bidirectional sync

* fix: remove extra props from interfaces

* fix: hide legends when merging all queries

* fix: minor legend fixes

* fix: build issues

---------

Co-authored-by: Rajat-Dabade <rajat@signoz.io>
Co-authored-by: Yunus M <myounis.ar@live.com>
Co-authored-by: Vikrant Gupta <vikrant.thomso@gmail.com>
2024-06-05 07:07:49 +05:30
Yunus M
b39f703919 feat: support multi ingestion keys (#5105)
* feat: support multi ingestion keys

* fix: remove unwanted changes

* feat: limits ui

* feat: handle limit updates from component

* feat: handle limit updates per signal

* feat: integrate multiple ingestion key api

* feat: handle crud for limits

* fix: lint errors

* feat: support query search for ingestion name

* feat: show utilized size in limits

* feat: show multiple ingestions ui only if gateway is enabled

* feat: handle decimal values for ingestion size

* feat: enable multiple ingestion keys for all users with gateway enabled

* chore: remove yarn.lock

---------

Co-authored-by: Yunus A M <younix@Yunuss-MacBook-Pro.local>
Co-authored-by: Prashant Shahi <prashant@signoz.io>
2024-06-04 18:40:15 +05:30
Vibhu Pandey
7f39d8282c (feature) multiple ingestion keys (#4762)
* feat(keys): add support for multiple ingestion keys

* ci(git): remove vendor/ from git

* feat(gateway): create a proxy for sending requests to the gateway

* fix(sqlite): remove keys schema

* fix(api): replace with constant

* fix(server): remove redundant options

* fix(server): remove redundant options

* test(gateway): add unit tests for gateway proxy

* ci(docker): update gateway url

* refactor(gateway): move gateway to api layer

* fix(manager): fix declared error in manager

* feat(testing): add a new testing docker-compose

* fix(license): revert to nil license since select will never return a norows error

* feat(gateway): add feature flags

* chore(server): add a logger

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-06-04 18:25:24 +05:30
SagarRajput-7
9733612be8 feat: added trace-filter in new trace-explorer (#5081)
* feat: added trace-filter in new trace-explorer

* feat: added trace-filter in new trace-explorer

* feat: style improvement

* feat: query builder sync and filter section refactor

* feat: added duration and code refactor

* feat: added default open case

* feat: removed API calls and used keys from const

* feat: added sync and prepare data logic for querybuilder

* feat: added styles for lightmode

* feat: code refactor and sync issue fixed

* feat: code refactor and sync issue fixed

* feat: code refactor and feedback issue fixed

* feat: checkbox label and other feedback fix

* feat: filter open and close btn style and handling

* feat: added filter reset and clear all

* feat: fixed query modification issue when filtering

* feat: code refactor

* feat: search text via BE API

* feat: added CTA btn for old explorer page

* feat: make trace-explorer default page

* feat: removed new ribbon on CTA for old trace explorer

* feat: fixed qb and filter panel sync via url state

* feat: fixed duration section issues
2024-06-04 14:03:49 +05:30
SagarRajput-7
1ce36c8344 feat: added facing issues btn in new dashboard revamp (#5131) 2024-06-04 12:40:20 +05:30
Vikrant Gupta
ac2dc44abb fix: dashboard variables should properly load for imported dashboards (#5123)
Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2024-06-04 11:18:44 +05:30
Vikrant Gupta
2145e353c8 feat: per column unit option for table type (#5134)
* feat: base setup for individual column type units

* feat: added logic for y axis unit selection

* fix: light mode design

* feat: fix the mutation of original datasource array
2024-06-04 11:14:54 +05:30
Vikrant Gupta
be9c3f0697 fix: page breaking when query range response is null for table panel … (#5139)
* fix: page breaking when query range response is null for table panel type

* fix: page breaking when query range response is null for table panel type

* fix: page breaking when query range response is null for table panel type
2024-06-04 00:15:37 +05:30
CheetoDa
03838f5fcc feat: added docs for Angular (#5132)
* feat: added docs for Angular

* chore: updated app ts file
2024-06-03 13:28:42 +05:30
Srikanth Chekuri
592073a564 chore: add AWS RDS and ElastiCache integration (#5071) 2024-06-02 14:42:05 +05:30
Srikanth Chekuri
93df475969 fix: use text/template for ClickHouse query templating (#5128) 2024-06-01 08:22:16 +05:30
Prashant Shahi
0f9c8d91ef chore(query-service): update GoValuateFuncs definitions (#5106)
* fix: parse both unix int and RFC3339 string in toUnixTimestamp
* chore: return GoValuateFuncs as float64
* chore: remove toUnixTimestamp function

---------

Signed-off-by: Prashant Shahi <prashant@signoz.io>
2024-05-31 21:22:03 +05:30
407 changed files with 30864 additions and 2972 deletions

View File

@@ -49,6 +49,6 @@ jobs:
git pull
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz
make run-testing
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

View File

@@ -50,6 +50,6 @@ jobs:
git checkout --track origin/${GITHUB_BRANCH}
make build-ee-query-service-amd64
make build-frontend-amd64
make run-signoz
make run-testing
EOF
gcloud compute ssh ${GCP_INSTANCE} --zone ${GCP_ZONE} --ssh-key-expire-after=15m --tunnel-through-iap --project ${GCP_PROJECT} --command "${COMMAND}"

6
.gitignore vendored
View File

@@ -62,4 +62,8 @@ e2e/test-results/
e2e/playwright-report/
e2e/blob-report/
e2e/playwright/.cache/
e2e/.auth
e2e/.auth
# go
vendor/
**/main/**

View File

@@ -156,6 +156,9 @@ pull-signoz:
run-signoz:
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml up --build -d
run-testing:
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.testing.yaml up --build -d
down-signoz:
@docker-compose -f $(STANDALONE_DIRECTORY)/docker-compose.yaml down -v

View File

@@ -77,7 +77,16 @@ processors:
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: 'signoz.collector.id'
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
@@ -108,6 +117,15 @@ processors:
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
exporters:
clickhousetraces:

View File

@@ -0,0 +1,307 @@
version: "2.4"
x-clickhouse-defaults: &clickhouse-defaults
restart: on-failure
# addding non LTS version due to this fix https://github.com/ClickHouse/ClickHouse/commit/32caf8716352f45c1b617274c7508c86b7d1afab
image: clickhouse/clickhouse-server:24.1.2-alpine
tty: true
depends_on:
- zookeeper-1
# - zookeeper-2
# - zookeeper-3
logging:
options:
max-size: 50m
max-file: "3"
healthcheck:
# "clickhouse", "client", "-u ${CLICKHOUSE_USER}", "--password ${CLICKHOUSE_PASSWORD}", "-q 'SELECT 1'"
test:
[
"CMD",
"wget",
"--spider",
"-q",
"0.0.0.0:8123/ping"
]
interval: 30s
timeout: 5s
retries: 3
ulimits:
nproc: 65535
nofile:
soft: 262144
hard: 262144
x-db-depend: &db-depend
depends_on:
clickhouse:
condition: service_healthy
otel-collector-migrator:
condition: service_completed_successfully
# clickhouse-2:
# condition: service_healthy
# clickhouse-3:
# condition: service_healthy
services:
zookeeper-1:
image: bitnami/zookeeper:3.7.1
container_name: signoz-zookeeper-1
hostname: zookeeper-1
user: root
ports:
- "2181:2181"
- "2888:2888"
- "3888:3888"
volumes:
- ./data/zookeeper-1:/bitnami/zookeeper
environment:
- ZOO_SERVER_ID=1
# - ZOO_SERVERS=0.0.0.0:2888:3888,zookeeper-2:2888:3888,zookeeper-3:2888:3888
- ALLOW_ANONYMOUS_LOGIN=yes
- ZOO_AUTOPURGE_INTERVAL=1
# zookeeper-2:
# image: bitnami/zookeeper:3.7.0
# container_name: signoz-zookeeper-2
# hostname: zookeeper-2
# user: root
# ports:
# - "2182:2181"
# - "2889:2888"
# - "3889:3888"
# volumes:
# - ./data/zookeeper-2:/bitnami/zookeeper
# environment:
# - ZOO_SERVER_ID=2
# - ZOO_SERVERS=zookeeper-1:2888:3888,0.0.0.0:2888:3888,zookeeper-3:2888:3888
# - ALLOW_ANONYMOUS_LOGIN=yes
# - ZOO_AUTOPURGE_INTERVAL=1
# zookeeper-3:
# image: bitnami/zookeeper:3.7.0
# container_name: signoz-zookeeper-3
# hostname: zookeeper-3
# user: root
# ports:
# - "2183:2181"
# - "2890:2888"
# - "3890:3888"
# volumes:
# - ./data/zookeeper-3:/bitnami/zookeeper
# environment:
# - ZOO_SERVER_ID=3
# - ZOO_SERVERS=zookeeper-1:2888:3888,zookeeper-2:2888:3888,0.0.0.0:2888:3888
# - ALLOW_ANONYMOUS_LOGIN=yes
# - ZOO_AUTOPURGE_INTERVAL=1
clickhouse:
<<: *clickhouse-defaults
container_name: signoz-clickhouse
hostname: clickhouse
ports:
- "9000:9000"
- "8123:8123"
- "9181:9181"
volumes:
- ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
- ./clickhouse-users.xml:/etc/clickhouse-server/users.xml
- ./custom-function.xml:/etc/clickhouse-server/custom-function.xml
- ./clickhouse-cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
# - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml
- ./data/clickhouse/:/var/lib/clickhouse/
- ./user_scripts:/var/lib/clickhouse/user_scripts/
# clickhouse-2:
# <<: *clickhouse-defaults
# container_name: signoz-clickhouse-2
# hostname: clickhouse-2
# ports:
# - "9001:9000"
# - "8124:8123"
# - "9182:9181"
# volumes:
# - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
# - ./clickhouse-users.xml:/etc/clickhouse-server/users.xml
# - ./custom-function.xml:/etc/clickhouse-server/custom-function.xml
# - ./clickhouse-cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
# # - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ./data/clickhouse-2/:/var/lib/clickhouse/
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
# clickhouse-3:
# <<: *clickhouse-defaults
# container_name: signoz-clickhouse-3
# hostname: clickhouse-3
# ports:
# - "9002:9000"
# - "8125:8123"
# - "9183:9181"
# volumes:
# - ./clickhouse-config.xml:/etc/clickhouse-server/config.xml
# - ./clickhouse-users.xml:/etc/clickhouse-server/users.xml
# - ./custom-function.xml:/etc/clickhouse-server/custom-function.xml
# - ./clickhouse-cluster.xml:/etc/clickhouse-server/config.d/cluster.xml
# # - ./clickhouse-storage.xml:/etc/clickhouse-server/config.d/storage.xml
# - ./data/clickhouse-3/:/var/lib/clickhouse/
# - ./user_scripts:/var/lib/clickhouse/user_scripts/
alertmanager:
image: signoz/alertmanager:${ALERTMANAGER_TAG:-0.23.5}
container_name: signoz-alertmanager
volumes:
- ./data/alertmanager:/data
depends_on:
query-service:
condition: service_healthy
restart: on-failure
command:
- --queryService.url=http://query-service:8085
- --storage.path=/data
# Notes for Maintainers/Contributors who will change Line Numbers of Frontend & Query-Section. Please Update Line Numbers in `./scripts/commentLinesForSetup.sh` & `./CONTRIBUTING.md`
query-service:
image: signoz/query-service:${DOCKER_TAG:-0.46.0}
container_name: signoz-query-service
command:
[
"-config=/root/config/prometheus.yml",
"-gateway-url=https://api.staging.signoz.cloud"
# "--prefer-delta=true"
]
# ports:
# - "6060:6060" # pprof port
# - "8080:8080" # query-service port
volumes:
- ./prometheus.yml:/root/config/prometheus.yml
- ../dashboards:/root/config/dashboards
- ./data/signoz/:/var/lib/signoz/
environment:
- ClickHouseUrl=tcp://clickhouse:9000
- ALERTMANAGER_API_PREFIX=http://alertmanager:9093/api/
- SIGNOZ_LOCAL_DB_PATH=/var/lib/signoz/signoz.db
- DASHBOARDS_PATH=/root/config/dashboards
- STORAGE=clickhouse
- GODEBUG=netdns=go
- TELEMETRY_ENABLED=true
- DEPLOYMENT_TYPE=docker-standalone-amd
restart: on-failure
healthcheck:
test:
[
"CMD",
"wget",
"--spider",
"-q",
"localhost:8080/api/v1/health"
]
interval: 30s
timeout: 5s
retries: 3
<<: *db-depend
frontend:
image: signoz/frontend:${DOCKER_TAG:-0.46.0}
container_name: signoz-frontend
restart: on-failure
depends_on:
- alertmanager
- query-service
ports:
- "3301:3301"
volumes:
- ../common/nginx-config.conf:/etc/nginx/conf.d/default.conf
otel-collector-migrator:
image: signoz/signoz-schema-migrator:${OTELCOL_TAG:-0.88.24}
container_name: otel-migrator
command:
- "--dsn=tcp://clickhouse:9000"
depends_on:
clickhouse:
condition: service_healthy
# clickhouse-2:
# condition: service_healthy
# clickhouse-3:
# condition: service_healthy
otel-collector:
image: signoz/signoz-otel-collector:${OTELCOL_TAG:-0.88.24}
container_name: signoz-otel-collector
command:
[
"--config=/etc/otel-collector-config.yaml",
"--manager-config=/etc/manager-config.yaml",
"--copy-path=/var/tmp/collector-config.yaml",
"--feature-gates=-pkg.translator.prometheus.NormalizeName"
]
user: root # required for reading docker container logs
volumes:
- ./otel-collector-config.yaml:/etc/otel-collector-config.yaml
- ./otel-collector-opamp-config.yaml:/etc/manager-config.yaml
- /var/lib/docker/containers:/var/lib/docker/containers:ro
environment:
- OTEL_RESOURCE_ATTRIBUTES=host.name=signoz-host,os.type=linux
- DOCKER_MULTI_NODE_CLUSTER=false
- LOW_CARDINAL_EXCEPTION_GROUPING=false
ports:
# - "1777:1777" # pprof extension
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
# - "8888:8888" # OtelCollector internal metrics
# - "8889:8889" # signoz spanmetrics exposed by the agent
# - "9411:9411" # Zipkin port
# - "13133:13133" # health check extension
# - "14250:14250" # Jaeger gRPC
# - "14268:14268" # Jaeger thrift HTTP
# - "55678:55678" # OpenCensus receiver
# - "55679:55679" # zPages extension
restart: on-failure
depends_on:
clickhouse:
condition: service_healthy
otel-collector-migrator:
condition: service_completed_successfully
query-service:
condition: service_healthy
logspout:
image: "gliderlabs/logspout:v3.2.14"
container_name: signoz-logspout
volumes:
- /etc/hostname:/etc/host_hostname:ro
- /var/run/docker.sock:/var/run/docker.sock
command: syslog+tcp://otel-collector:2255
depends_on:
- otel-collector
restart: on-failure
hotrod:
image: jaegertracing/example-hotrod:1.30
container_name: hotrod
logging:
options:
max-size: 50m
max-file: "3"
command: [ "all" ]
environment:
- JAEGER_ENDPOINT=http://otel-collector:14268/api/traces
load-hotrod:
image: "signoz/locust:1.2.3"
container_name: load-hotrod
hostname: load-hotrod
environment:
ATTACKED_HOST: http://hotrod:8080
LOCUST_MODE: standalone
NO_PROXY: standalone
TASK_DELAY_FROM: 5
TASK_DELAY_TO: 30
QUIET_MODE: "${QUIET_MODE:-false}"
LOCUST_OPTS: "--headless -u 10 -r 1"
volumes:
- ../common/locust-scripts:/locust

View File

@@ -168,7 +168,7 @@ services:
container_name: signoz-query-service
command:
[
"-config=/root/config/prometheus.yml",
"-config=/root/config/prometheus.yml"
# "--prefer-delta=true"
]
# ports:

View File

@@ -75,7 +75,16 @@ processors:
# This is added to ensure the uniqueness of the timeseries
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: 'signoz.collector.id'
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
# memory_limiter:
# # 80% of maximum memory up to 2G
# limit_mib: 1500
@@ -111,6 +120,15 @@ processors:
# Otherwise, identical timeseries produced by multiple replicas of
# collectors result in incorrect APM metrics
- name: signoz.collector.id
- name: service.version
- name: browser.platform
- name: browser.mobile
- name: k8s.cluster.name
- name: k8s.node.name
- name: k8s.namespace.name
- name: host.name
- name: host.type
- name: container.name
extensions:
health_check:

View File

@@ -2,10 +2,12 @@ package api
import (
"net/http"
"net/http/httputil"
"time"
"github.com/gorilla/mux"
"go.signoz.io/signoz/ee/query-service/dao"
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
"go.signoz.io/signoz/ee/query-service/license"
"go.signoz.io/signoz/ee/query-service/usage"
@@ -35,6 +37,7 @@ type APIHandlerOptions struct {
IntegrationsController *integrations.Controller
LogsParsingPipelineController *logparsingpipeline.LogParsingPipelineController
Cache cache.Cache
Gateway *httputil.ReverseProxy
// Querier Influx Interval
FluxInterval time.Duration
}
@@ -95,6 +98,10 @@ func (ah *APIHandler) AppDao() dao.ModelDao {
return ah.opts.AppDao
}
func (ah *APIHandler) Gateway() *httputil.ReverseProxy {
return ah.opts.Gateway
}
func (ah *APIHandler) CheckFeature(f string) bool {
err := ah.FF().CheckFeature(f)
return err == nil
@@ -170,6 +177,9 @@ func (ah *APIHandler) RegisterRoutes(router *mux.Router, am *baseapp.AuthMiddlew
am.ViewAccess(ah.listLicensesV2)).
Methods(http.MethodGet)
// Gateway
router.PathPrefix(gateway.RoutePrefix).HandlerFunc(am.AdminAccess(ah.ServeGatewayHTTP))
ah.APIHandler.RegisterRoutes(router, am)
}

View File

@@ -14,7 +14,6 @@ import (
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/model"
"go.signoz.io/signoz/pkg/query-service/auth"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
basemodel "go.signoz.io/signoz/pkg/query-service/model"
)
@@ -51,7 +50,7 @@ func (ah *APIHandler) loginUser(w http.ResponseWriter, r *http.Request) {
}
// if all looks good, call auth
resp, err := auth.Login(ctx, &req)
resp, err := baseauth.Login(ctx, &req)
if ah.HandleError(w, err, http.StatusUnauthorized) {
return
}
@@ -130,7 +129,7 @@ func (ah *APIHandler) registerUser(w http.ResponseWriter, r *http.Request) {
} else {
// no-sso, validate password
if err := auth.ValidatePassword(req.Password); err != nil {
if err := baseauth.ValidatePassword(req.Password); err != nil {
RespondError(w, model.InternalError(fmt.Errorf("password is not in a valid format")), nil)
return
}
@@ -241,6 +240,11 @@ func (ah *APIHandler) receiveGoogleAuth(w http.ResponseWriter, r *http.Request)
// prepare google callback handler using parsedState -
// which contains redirect URL (front-end endpoint)
callbackHandler, err := domain.PrepareGoogleOAuthProvider(parsedState)
if err != nil {
zap.L().Error("[receiveGoogleAuth] failed to prepare google oauth provider", zap.String("domain", domain.String()), zap.Error(err))
handleSsoError(w, r, redirectUri)
return
}
identity, err := callbackHandler.HandleCallback(r)
if err != nil {

View File

@@ -0,0 +1,34 @@
package api
import (
"net/http"
"strings"
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
)
func (ah *APIHandler) ServeGatewayHTTP(rw http.ResponseWriter, req *http.Request) {
ctx := req.Context()
if !strings.HasPrefix(req.URL.Path, gateway.RoutePrefix+gateway.AllowedPrefix) {
rw.WriteHeader(http.StatusNotFound)
return
}
license, err := ah.LM().GetRepo().GetActiveLicense(ctx)
if err != nil {
RespondError(rw, err, nil)
return
}
//Create headers
var licenseKey string
if license != nil {
licenseKey = license.Key
}
req.Header.Set("X-Signoz-Cloud-Api-Key", licenseKey)
req.Header.Set("X-Consumer-Username", "lid:00000000-0000-0000-0000-000000000000")
req.Header.Set("X-Consumer-Groups", "ns:default")
ah.Gateway().ServeHTTP(rw, req)
}

View File

@@ -21,15 +21,15 @@ import (
// GetMetricResultEE runs the query and returns list of time series
func (r *ClickhouseReader) GetMetricResultEE(ctx context.Context, query string) ([]*basemodel.Series, string, error) {
defer utils.Elapsed("GetMetricResult")()
defer utils.Elapsed("GetMetricResult", nil)()
zap.L().Info("Executing metric result query: ", zap.String("query", query))
var hash string
// If getSubTreeSpans function is used in the clickhouse query
if strings.Index(query, "getSubTreeSpans(") != -1 {
if strings.Contains(query, "getSubTreeSpans(") {
var err error
query, hash, err = r.getSubTreeSpansCustomFunction(ctx, query, hash)
if err == fmt.Errorf("No spans found for the given query") {
if err == fmt.Errorf("no spans found for the given query") {
return nil, "", nil
}
if err != nil {
@@ -183,7 +183,7 @@ func (r *ClickhouseReader) getSubTreeSpansCustomFunction(ctx context.Context, qu
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return query, hash, fmt.Errorf("Error in processing sql query")
return query, hash, fmt.Errorf("error in processing sql query")
}
var searchScanResponses []basemodel.SearchSpanDBResponseItem
@@ -193,14 +193,14 @@ func (r *ClickhouseReader) getSubTreeSpansCustomFunction(ctx context.Context, qu
modelQuery := fmt.Sprintf("SELECT timestamp, traceID, model FROM %s.%s WHERE traceID=$1", r.TraceDB, r.SpansTable)
if len(getSpansSubQueryDBResponses) == 0 {
return query, hash, fmt.Errorf("No spans found for the given query")
return query, hash, fmt.Errorf("no spans found for the given query")
}
zap.L().Debug("Executing query to fetch all the spans from the same TraceID: ", zap.String("modelQuery", modelQuery))
err = r.conn.Select(ctx, &searchScanResponses, modelQuery, getSpansSubQueryDBResponses[0].TraceID)
if err != nil {
zap.L().Error("Error in processing sql query", zap.Error(err))
return query, hash, fmt.Errorf("Error in processing sql query")
return query, hash, fmt.Errorf("error in processing sql query")
}
// Process model to fetch the spans
@@ -263,6 +263,7 @@ func (r *ClickhouseReader) getSubTreeSpansCustomFunction(ctx context.Context, qu
return query, hash, nil
}
//lint:ignore SA4009 return hash is feeded to the query
func processQuery(query string, hash string) (string, string, string) {
re3 := regexp.MustCompile(`getSubTreeSpans`)

View File

@@ -61,7 +61,7 @@ func SmartTraceAlgorithm(payload []basemodel.SearchSpanResponseItem, targetSpanI
// If the target span is not found, return span not found error
if targetSpan == nil {
return nil, errors.New("Span not found")
return nil, errors.New("span not found")
}
// Build the final result
@@ -118,8 +118,8 @@ func SmartTraceAlgorithm(payload []basemodel.SearchSpanResponseItem, targetSpanI
}
searchSpansResult := []basemodel.SearchSpansResult{{
Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"},
Events: make([][]interface{}, len(resultSpansSet)),
Columns: []string{"__time", "SpanId", "TraceId", "ServiceName", "Name", "Kind", "DurationNano", "TagsKeys", "TagsValues", "References", "Events", "HasError"},
Events: make([][]interface{}, len(resultSpansSet)),
IsSubTree: true,
},
}
@@ -219,7 +219,7 @@ func breadthFirstSearch(spansPtr *model.SpanForTraceDetails, targetId string) (*
}
for _, child := range current.Children {
if ok, _ := visited[child.SpanID]; !ok {
if ok := visited[child.SpanID]; !ok {
queue = append(queue, child)
}
}

View File

@@ -8,6 +8,7 @@ import (
"io"
"net"
"net/http"
"net/http/httputil"
_ "net/http/pprof" // http profiler
"os"
"regexp"
@@ -24,9 +25,9 @@ import (
"go.signoz.io/signoz/ee/query-service/auth"
"go.signoz.io/signoz/ee/query-service/constants"
"go.signoz.io/signoz/ee/query-service/dao"
"go.signoz.io/signoz/ee/query-service/integrations/gateway"
"go.signoz.io/signoz/ee/query-service/interfaces"
baseauth "go.signoz.io/signoz/pkg/query-service/auth"
baseInterface "go.signoz.io/signoz/pkg/query-service/interfaces"
v3 "go.signoz.io/signoz/pkg/query-service/model/v3"
licensepkg "go.signoz.io/signoz/ee/query-service/license"
@@ -71,14 +72,13 @@ type ServerOptions struct {
CacheConfigPath string
FluxInterval string
Cluster string
GatewayUrl string
}
// Server runs HTTP api service
type Server struct {
serverOptions *ServerOptions
conn net.Listener
ruleManager *rules.Manager
separatePorts bool
// public http router
httpConn net.Listener
@@ -88,9 +88,6 @@ type Server struct {
privateConn net.Listener
privateHTTP *http.Server
// feature flags
featureLookup baseint.FeatureLookup
// Usage manager
usageManager *usage.Manager
@@ -122,8 +119,33 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
localDB.SetMaxOpenConns(10)
gatewayFeature := basemodel.Feature{
Name: "GATEWAY",
Active: false,
Usage: 0,
UsageLimit: -1,
Route: "",
}
//Activate this feature if the url is not empty
var gatewayProxy *httputil.ReverseProxy
if serverOptions.GatewayUrl == "" {
gatewayFeature.Active = false
gatewayProxy, err = gateway.NewNoopProxy()
if err != nil {
return nil, err
}
} else {
zap.L().Info("Enabling gateway feature flag ...")
gatewayFeature.Active = true
gatewayProxy, err = gateway.NewProxy(serverOptions.GatewayUrl, gateway.RoutePrefix)
if err != nil {
return nil, err
}
}
// initiate license manager
lm, err := licensepkg.StartManager("sqlite", localDB)
lm, err := licensepkg.StartManager("sqlite", localDB, gatewayFeature)
if err != nil {
return nil, err
}
@@ -248,6 +270,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
LogsParsingPipelineController: logParsingPipelineController,
Cache: c,
FluxInterval: fluxInterval,
Gateway: gatewayProxy,
}
apiHandler, err := api.NewAPIHandler(apiOpts)
@@ -288,7 +311,7 @@ func NewServer(serverOptions *ServerOptions) (*Server, error) {
func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server, error) {
r := mux.NewRouter()
r := baseapp.NewRouter()
r.Use(baseapp.LogCommentEnricher)
r.Use(setTimeoutMiddleware)
@@ -315,7 +338,7 @@ func (s *Server) createPrivateServer(apiHandler *api.APIHandler) (*http.Server,
func (s *Server) createPublicServer(apiHandler *api.APIHandler) (*http.Server, error) {
r := mux.NewRouter()
r := baseapp.NewRouter()
// add auth middleware
getUserFromRequest := func(r *http.Request) (*basemodel.UserPayload, error) {
@@ -356,7 +379,7 @@ func loggingMiddleware(next http.Handler) http.Handler {
path, _ := route.GetPathTemplate()
startTime := time.Now()
next.ServeHTTP(w, r)
zap.L().Info(path+"\ttimeTaken:"+time.Now().Sub(startTime).String(), zap.Duration("timeTaken", time.Now().Sub(startTime)), zap.String("path", path))
zap.L().Info(path, zap.Duration("timeTaken", time.Since(startTime)), zap.String("path", path))
})
}
@@ -368,7 +391,7 @@ func loggingMiddlewarePrivate(next http.Handler) http.Handler {
path, _ := route.GetPathTemplate()
startTime := time.Now()
next.ServeHTTP(w, r)
zap.L().Info(path+"\tprivatePort: true \ttimeTaken"+time.Now().Sub(startTime).String(), zap.Duration("timeTaken", time.Now().Sub(startTime)), zap.String("path", path), zap.Bool("tprivatePort", true))
zap.L().Info(path, zap.Duration("timeTaken", time.Since(startTime)), zap.String("path", path), zap.Bool("tprivatePort", true))
})
}
@@ -682,7 +705,7 @@ func makeRulesManager(
db *sqlx.DB,
ch baseint.Reader,
disableRules bool,
fm baseInterface.FeatureLookup) (*rules.Manager, error) {
fm baseint.FeatureLookup) (*rules.Manager, error) {
// create engine
pqle, err := pqle.FromConfigPath(promConfigPath)

View File

@@ -34,7 +34,7 @@ type ModelDao interface {
GetDomainByEmail(ctx context.Context, email string) (*model.OrgDomain, basemodel.BaseApiError)
CreatePAT(ctx context.Context, p model.PAT) (model.PAT, basemodel.BaseApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) (basemodel.BaseApiError)
UpdatePAT(ctx context.Context, p model.PAT, id string) basemodel.BaseApiError
GetPAT(ctx context.Context, pat string) (*model.PAT, basemodel.BaseApiError)
UpdatePATLastUsed(ctx context.Context, pat string, lastUsed int64) basemodel.BaseApiError
GetPATByID(ctx context.Context, id string) (*model.PAT, basemodel.BaseApiError)

View File

@@ -0,0 +1,9 @@
package gateway
import (
"net/http/httputil"
)
func NewNoopProxy() (*httputil.ReverseProxy, error) {
return nil, nil
}

View File

@@ -0,0 +1,66 @@
package gateway
import (
"net/http"
"net/http/httputil"
"net/url"
"path"
"strings"
)
const (
RoutePrefix string = "/api/gateway"
AllowedPrefix string = "/v1/workspaces/me"
)
type proxy struct {
url *url.URL
stripPath string
}
func NewProxy(u string, stripPath string) (*httputil.ReverseProxy, error) {
url, err := url.Parse(u)
if err != nil {
return nil, err
}
proxy := &proxy{url: url, stripPath: stripPath}
return &httputil.ReverseProxy{
Rewrite: proxy.rewrite,
ModifyResponse: proxy.modifyResponse,
ErrorHandler: proxy.errorHandler,
}, nil
}
func (p *proxy) rewrite(pr *httputil.ProxyRequest) {
pr.SetURL(p.url)
pr.SetXForwarded()
pr.Out.URL.Path = cleanPath(strings.ReplaceAll(pr.Out.URL.Path, p.stripPath, ""))
}
func (p *proxy) modifyResponse(res *http.Response) error {
return nil
}
func (p *proxy) errorHandler(rw http.ResponseWriter, req *http.Request, err error) {
rw.WriteHeader(http.StatusBadGateway)
}
func cleanPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
np := path.Clean(p)
if p[len(p)-1] == '/' && np != "/" {
if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
np = p
} else {
np += "/"
}
}
return np
}

View File

@@ -0,0 +1,61 @@
package gateway
import (
"context"
"net/http"
"net/http/httputil"
"net/url"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProxyRewrite(t *testing.T) {
testCases := []struct {
name string
url *url.URL
stripPath string
in *url.URL
expected *url.URL
}{
{
name: "SamePathAdded",
url: &url.URL{Scheme: "http", Host: "backend", Path: "/path1"},
stripPath: "/strip",
in: &url.URL{Scheme: "http", Host: "localhost", Path: "/strip/path1"},
expected: &url.URL{Scheme: "http", Host: "backend", Path: "/path1/path1"},
},
{
name: "NoStripPathInput",
url: &url.URL{Scheme: "http", Host: "backend"},
stripPath: "",
in: &url.URL{Scheme: "http", Host: "localhost", Path: "/strip/path1"},
expected: &url.URL{Scheme: "http", Host: "backend", Path: "/strip/path1"},
},
{
name: "NoStripPathPresentInReq",
url: &url.URL{Scheme: "http", Host: "backend"},
stripPath: "/not-found",
in: &url.URL{Scheme: "http", Host: "localhost", Path: "/strip/path1"},
expected: &url.URL{Scheme: "http", Host: "backend", Path: "/strip/path1"},
},
}
for _, tc := range testCases {
proxy, err := NewProxy(tc.url.String(), tc.stripPath)
require.NoError(t, err)
inReq, err := http.NewRequest(http.MethodGet, tc.in.String(), nil)
require.NoError(t, err)
proxyReq := &httputil.ProxyRequest{
In: inReq,
Out: inReq.Clone(context.Background()),
}
proxy.Rewrite(proxyReq)
assert.Equal(t, tc.expected.Host, proxyReq.Out.URL.Host)
assert.Equal(t, tc.expected.Scheme, proxyReq.Out.URL.Scheme)
assert.Equal(t, tc.expected.Path, proxyReq.Out.URL.Path)
assert.Equal(t, tc.expected.Query(), proxyReq.Out.URL.Query())
}
}

View File

@@ -2,11 +2,6 @@ package signozio
type status string
const (
statusSuccess status = "success"
statusError status = "error"
)
type ActivationResult struct {
Status status `json:"status"`
Data *ActivationResponse `json:"data,omitempty"`

View File

@@ -48,8 +48,9 @@ func (r *Repo) GetLicenses(ctx context.Context) ([]model.License, error) {
return licenses, nil
}
// GetActiveLicense fetches the latest active license from DB
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, error) {
// GetActiveLicense fetches the latest active license from DB.
// If the license is not present, expect a nil license and a nil error in the output.
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
var err error
licenses := []model.License{}
@@ -57,7 +58,7 @@ func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, error) {
err = r.db.Select(&licenses, query)
if err != nil {
return nil, fmt.Errorf("failed to get active licenses from db: %v", err)
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
}
var active *model.License
@@ -110,7 +111,7 @@ func (r *Repo) UpdatePlanDetails(ctx context.Context,
planDetails string) error {
if key == "" {
return fmt.Errorf("Update Plan Details failed: license key is required")
return fmt.Errorf("update plan details failed: license key is required")
}
query := `UPDATE licenses

View File

@@ -49,8 +49,7 @@ type Manager struct {
activeFeatures basemodel.FeatureSet
}
func StartManager(dbType string, db *sqlx.DB) (*Manager, error) {
func StartManager(dbType string, db *sqlx.DB, features ...basemodel.Feature) (*Manager, error) {
if LM != nil {
return LM, nil
}
@@ -66,7 +65,7 @@ func StartManager(dbType string, db *sqlx.DB) (*Manager, error) {
repo: &repo,
}
if err := m.start(); err != nil {
if err := m.start(features...); err != nil {
return m, err
}
LM = m
@@ -74,8 +73,8 @@ func StartManager(dbType string, db *sqlx.DB) (*Manager, error) {
}
// start loads active license in memory and initiates validator
func (lm *Manager) start() error {
err := lm.LoadActiveLicense()
func (lm *Manager) start(features ...basemodel.Feature) error {
err := lm.LoadActiveLicense(features...)
return err
}
@@ -85,7 +84,7 @@ func (lm *Manager) Stop() {
<-lm.terminated
}
func (lm *Manager) SetActive(l *model.License) {
func (lm *Manager) SetActive(l *model.License, features ...basemodel.Feature) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
@@ -94,7 +93,7 @@ func (lm *Manager) SetActive(l *model.License) {
}
lm.activeLicense = l
lm.activeFeatures = l.FeatureSet
lm.activeFeatures = append(l.FeatureSet, features...)
// set default features
setDefaultFeatures(lm)
@@ -116,14 +115,13 @@ func setDefaultFeatures(lm *Manager) {
}
// LoadActiveLicense loads the most recent active license
func (lm *Manager) LoadActiveLicense() error {
var err error
func (lm *Manager) LoadActiveLicense(features ...basemodel.Feature) error {
active, err := lm.repo.GetActiveLicense(context.Background())
if err != nil {
return err
}
if active != nil {
lm.SetActive(active)
lm.SetActive(active, features...)
} else {
zap.L().Info("No active license found, defaulting to basic plan")
// if no active license is found, we default to basic(free) plan with all default features

View File

@@ -32,7 +32,7 @@ func InitDB(db *sqlx.DB) error {
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("Error in creating licenses table: %s", err.Error())
return fmt.Errorf("error in creating licenses table: %s", err.Error())
}
table_schema = `CREATE TABLE IF NOT EXISTS feature_status (
@@ -45,7 +45,7 @@ func InitDB(db *sqlx.DB) error {
_, err = db.Exec(table_schema)
if err != nil {
return fmt.Errorf("Error in creating feature_status table: %s", err.Error())
return fmt.Errorf("error in creating feature_status table: %s", err.Error())
}
return nil

View File

@@ -14,7 +14,6 @@ import (
semconv "go.opentelemetry.io/otel/semconv/v1.4.0"
"go.signoz.io/signoz/ee/query-service/app"
"go.signoz.io/signoz/pkg/query-service/auth"
"go.signoz.io/signoz/pkg/query-service/constants"
baseconst "go.signoz.io/signoz/pkg/query-service/constants"
"go.signoz.io/signoz/pkg/query-service/migrate"
"go.signoz.io/signoz/pkg/query-service/version"
@@ -52,7 +51,8 @@ func initZapLog(enableQueryServiceLogOTLPExport bool) *zap.Logger {
)
if enableQueryServiceLogOTLPExport {
ctx, _ := context.WithTimeout(ctx, time.Second*30)
ctx, cancel := context.WithTimeout(ctx, time.Second*30)
defer cancel()
conn, err := grpc.DialContext(ctx, baseconst.OTLPTarget, grpc.WithBlock(), grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("failed to establish connection: %v", err)
@@ -95,6 +95,7 @@ func main() {
var maxIdleConns int
var maxOpenConns int
var dialTimeout time.Duration
var gatewayUrl string
flag.StringVar(&promConfigPath, "config", "./config/prometheus.yml", "(prometheus config to read metrics)")
flag.StringVar(&skipTopLvlOpsPath, "skip-top-level-ops", "", "(config file to skip top level operations)")
@@ -109,6 +110,7 @@ func main() {
flag.StringVar(&fluxInterval, "flux-interval", "5m", "(cache config to use)")
flag.BoolVar(&enableQueryServiceLogOTLPExport, "enable.query.service.log.otlp.export", false, "(enable query service log otlp export)")
flag.StringVar(&cluster, "cluster", "cluster", "(cluster name - defaults to 'cluster')")
flag.StringVar(&gatewayUrl, "gateway-url", "", "(url to the gateway)")
flag.Parse()
@@ -134,6 +136,7 @@ func main() {
CacheConfigPath: cacheConfigPath,
FluxInterval: fluxInterval,
Cluster: cluster,
GatewayUrl: gatewayUrl,
}
// Read the jwt secret key
@@ -145,7 +148,7 @@ func main() {
zap.L().Info("JWT secret key set successfully.")
}
if err := migrate.Migrate(constants.RELATIONAL_DATASOURCE_PATH); err != nil {
if err := migrate.Migrate(baseconst.RELATIONAL_DATASOURCE_PATH); err != nil {
zap.L().Error("Failed to migrate", zap.Error(err))
} else {
zap.L().Info("Migration successful")

View File

@@ -104,7 +104,7 @@ func (od *OrgDomain) GetSAMLCert() string {
// requesting OAuth and also used in processing response from google
func (od *OrgDomain) PrepareGoogleOAuthProvider(siteUrl *url.URL) (sso.OAuthCallbackProvider, error) {
if od.GoogleAuthConfig == nil {
return nil, fmt.Errorf("Google auth is not setup correctly for this domain")
return nil, fmt.Errorf("GOOGLE OAUTH is not setup correctly for this domain")
}
return od.GoogleAuthConfig.GetProvider(od.Name, siteUrl)

View File

@@ -53,7 +53,7 @@ func New(dbType string, modelDao dao.ModelDao, licenseRepo *license.Repo, clickh
tenantID := ""
if len(hostNameRegexMatches) == 2 {
tenantID = hostNameRegexMatches[1]
tenantID = strings.TrimRight(tenantID, "-clickhouse")
tenantID = strings.TrimSuffix(tenantID, "-clickhouse")
}
m := &Manager{

View File

@@ -34,6 +34,8 @@
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@faker-js/faker": "8.4.1",
"@grafana/data": "^9.5.2",
"@mdx-js/loader": "2.3.0",
"@mdx-js/react": "2.3.0",
@@ -43,6 +45,7 @@
"@sentry/react": "7.102.1",
"@sentry/webpack-plugin": "2.16.0",
"@signozhq/design-tokens": "0.0.8",
"@tanstack/react-table": "8.17.3",
"@uiw/react-md-editor": "3.23.5",
"@visx/group": "3.3.0",
"@visx/shape": "3.5.0",
@@ -88,6 +91,7 @@
"lucide-react": "0.379.0",
"mini-css-extract-plugin": "2.4.5",
"papaparse": "5.4.1",
"rc-tween-one": "3.0.6",
"react": "18.2.0",
"react-addons-update": "15.6.3",
"react-beautiful-dnd": "13.1.1",
@@ -235,6 +239,7 @@
"@types/react-dom": "18.0.10",
"debug": "4.3.4",
"semver": "7.5.4",
"xml2js": "0.5.0"
"xml2js": "0.5.0",
"phin": "^3.7.1"
}
}

View File

@@ -0,0 +1,10 @@
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.99715 27.2944C4.70156 27.2944 4.74156 27.6477 4.74156 28.3143C4.74156 28.981 4.70156 29.3543 5.05493 29.3543C5.40831 29.3543 27.7778 29.3143 28.0134 29.2965C28.2489 29.2765 28.1889 28.4143 28.1889 28.081C28.1889 27.6699 28.2467 27.3166 27.9156 27.2966C27.5822 27.2766 5.11494 27.2944 4.99715 27.2944Z" fill="#ED6D30"/>
<path d="M5.07275 21.8602L5.09498 27.3132L27.7956 27.291L27.8467 21.7135L27.3466 21.1536L5.255 21.1158L5.07275 21.8602Z" fill="#F78A51"/>
<path d="M5.53728 21.4707L5.07278 21.8596L5.07056 22.724C5.07056 22.724 5.22169 22.8306 5.37282 22.7551C5.52395 22.6795 5.73508 22.5329 5.92177 22.5173C6.21959 22.4951 6.19514 22.7795 6.48184 22.7795C6.76855 22.7795 7.02858 22.4929 7.27083 22.4929C7.51308 22.4929 7.62421 22.7995 7.88202 22.784C8.13983 22.7684 8.28429 22.5084 8.60655 22.5173C8.86436 22.524 8.90881 22.784 9.22663 22.784C9.54445 22.784 9.70669 22.4818 9.97784 22.4818C10.249 22.4818 10.3379 22.8018 10.6401 22.8018C10.9424 22.8018 11.0246 22.4818 11.3713 22.4818C11.7181 22.4818 11.6892 22.784 11.9759 22.7529C12.2626 22.7218 12.2915 22.4729 12.6382 22.4573C12.9849 22.4418 13.0204 22.784 13.3227 22.784C13.625 22.784 13.6161 22.5373 13.8739 22.5373C14.1317 22.5373 18.9145 22.5262 19.0968 22.5262C19.279 22.5262 19.559 22.8462 19.8613 22.8462C20.1636 22.8462 20.0791 22.504 20.4103 22.4951C20.6081 22.4907 20.9925 22.824 21.2192 22.824C21.4459 22.824 21.5282 22.4818 21.7838 22.4662C22.0393 22.4507 22.4194 22.844 22.7217 22.8129C23.0239 22.7818 22.8728 22.4796 23.0995 22.4507C23.3262 22.4196 23.7796 22.784 24.0818 22.7973C24.3841 22.8129 24.1885 22.404 24.5041 22.404C24.8197 22.404 25.0642 22.7507 25.3953 22.7662C25.7265 22.7818 25.502 22.4196 25.8332 22.3884C26.1643 22.3573 26.4066 22.8418 26.7244 22.8106C27.0422 22.7795 26.9066 22.4329 27.1778 22.4173C27.4489 22.4018 27.8267 22.644 27.8267 22.644L27.8401 21.7063L14.7807 17.582L5.53728 21.4707Z" fill="#ED6D30"/>
<path d="M13.8049 29.3267C13.8049 29.3267 13.8605 22.7804 13.8516 22.6204C13.8405 22.4271 14.0116 22.3804 14.1494 22.3804C14.2871 22.3804 18.8558 22.3804 18.9935 22.3804C19.1313 22.3804 19.2113 22.4827 19.2224 22.6093C19.2335 22.736 19.2002 29.3156 19.2002 29.3156L13.8049 29.3267Z" fill="#51362F"/>
<path d="M4.15465 18.7244C4.15465 18.7244 3.23898 20.7487 3.24787 20.902C3.25676 21.0553 3.51234 21.9864 3.92128 22.0109C4.48135 22.0442 4.58359 21.5531 4.67693 21.5531C4.77028 21.5531 4.89474 22.0331 5.21478 22.0797C5.58816 22.1331 5.85708 21.5331 6.00154 21.5331C6.14601 21.5331 6.21713 22.0553 6.55495 22.0553C6.89277 22.0553 7.25281 21.4909 7.38616 21.502C7.51951 21.5131 7.64842 22.102 7.92401 22.102C8.20182 22.102 8.47296 21.5998 8.71299 21.5753C8.83745 21.5642 8.95525 22.1375 9.18194 22.1464C9.40864 22.1575 9.79535 21.5531 9.99093 21.5531C10.1865 21.5531 10.3399 22.1775 10.6377 22.1486C10.9355 22.1197 11.3378 21.5642 11.48 21.5642C11.6222 21.5642 11.7778 22.1264 12.0112 22.1375C12.2223 22.1464 12.5713 21.6087 12.7135 21.5998C12.8557 21.5909 13.0269 22.1486 13.2625 22.1486C13.498 22.1486 13.7536 21.5442 13.9492 21.5331C14.1448 21.522 14.227 22.102 14.4626 22.102C14.6982 22.102 15.0471 21.5175 15.2627 21.5087C15.4783 21.4975 15.5961 22.0686 15.8117 22.0686C16.0272 22.0686 16.2673 21.4887 16.4206 21.482C16.6584 21.4731 16.8096 22.0464 17.1385 22.0575C17.4674 22.0686 17.6008 21.5042 17.8564 21.5042C18.1119 21.5042 18.1853 22.0375 18.472 22.0486C18.7587 22.0597 18.9943 21.4953 19.2099 21.5042C19.4254 21.5153 19.5677 22.0264 19.8055 22.0264C20.0433 22.0264 20.2767 21.5042 20.4522 21.5131C20.6256 21.5242 20.8634 22.0464 21.099 22.0464C21.3346 22.0464 21.5302 21.5064 21.6435 21.502C21.8613 21.4953 22.0836 22.0664 22.3102 22.0464C22.5369 22.0264 22.7992 21.4642 22.9948 21.4731C23.1904 21.4842 23.4904 22.1108 23.726 22.0909C23.9616 22.0709 24.1616 21.4753 24.3772 21.4842C24.5928 21.4931 24.7661 22.0331 25.0395 22.0331C25.2906 22.0331 25.4306 21.5175 25.6573 21.5064C25.884 21.4953 26.0952 21.9997 26.3308 21.9753C26.5663 21.9509 26.6619 21.482 26.8686 21.4731C27.0731 21.462 27.3753 22.0042 27.6731 21.9931C27.971 21.982 28.1243 21.562 28.2888 21.5531C28.4532 21.5442 28.5955 22.0109 28.9955 22.0042C29.3556 21.9997 29.8267 21.3264 29.7334 20.8554C29.6401 20.3843 28.3599 18.5066 28.3599 18.5066L4.15465 18.7244Z" fill="#6C4D43"/>
<path d="M6.09496 13.357C6.09496 13.357 4.90148 15.0328 4.1925 16.5641C3.48352 18.0954 3.21016 19.0022 3.16571 19.8956C3.12126 20.7691 3.24794 20.9024 3.24794 20.9024L4.54366 19.4867C4.54366 19.4867 4.55699 20.8247 4.65256 20.838C4.74813 20.8513 5.74603 19.4578 5.8127 19.4445C5.8816 19.4311 5.8816 20.8513 5.97717 20.8513C6.07274 20.8513 7.09731 19.4178 7.16621 19.4178C7.2351 19.4178 7.26177 20.838 7.34401 20.838C7.42624 20.838 8.35524 19.3911 8.42414 19.4045C8.49304 19.4178 8.73751 20.9202 8.81975 20.9202C8.90198 20.9202 9.76209 19.3911 9.85765 19.3911C9.95322 19.3911 10.0621 20.9758 10.171 20.9758C10.2799 20.9758 11.1267 19.4467 11.1956 19.4467C11.2645 19.4467 11.5379 20.9625 11.6468 20.9491C11.7557 20.9358 12.5069 19.4467 12.5758 19.4734C12.6447 19.5 12.8225 20.9358 12.9447 20.9358C13.0669 20.9358 13.7226 19.4334 13.8315 19.4334C13.9404 19.4334 14.216 20.8913 14.2982 20.8913C14.3804 20.8913 15.0627 19.4289 15.145 19.4156C15.2272 19.4023 15.665 21.0269 15.8006 21.0269C15.9362 21.0269 16.3474 19.5245 16.4429 19.5378C16.5385 19.5512 17.1808 20.9713 17.2341 20.9713C17.2875 20.9713 17.7675 19.4823 17.8209 19.4823C17.8742 19.4823 18.5165 20.8335 18.6121 20.8491C18.7076 20.8624 19.0632 19.4978 19.1321 19.5245C19.201 19.5512 19.8567 20.958 19.9389 20.9713C20.0211 20.9847 20.3078 19.4956 20.3901 19.4956C20.4723 19.4956 21.3724 21.1336 21.4413 21.1202C21.5102 21.1069 21.5925 19.4667 21.6725 19.4534C21.7547 19.44 22.8326 21.0647 22.9148 21.0513C22.9971 21.038 22.9548 19.3978 23.0104 19.3978C23.066 19.3978 23.9527 20.9269 24.075 20.9136C24.1972 20.9002 24.3061 19.48 24.3884 19.48C24.4706 19.48 25.4529 21.1469 25.5774 21.1336C25.7019 21.1202 25.6041 19.5756 25.6596 19.5623C25.7152 19.5489 26.8198 20.9558 26.8753 20.9424C26.9309 20.9291 26.9153 19.4267 27.0109 19.4134C27.1065 19.4 28.131 20.8758 28.2266 20.8469C28.3222 20.8202 28.3355 19.3445 28.3911 19.3311C28.4466 19.3178 29.7268 20.8535 29.7268 20.8535C29.7268 20.8535 29.9757 19.5178 29.5357 18.2377C29.0956 16.9575 28.0266 15.1595 27.5087 14.395C26.9931 13.6304 26.6909 13.277 26.6909 13.277L14.0648 11.6591L6.09496 13.357Z" fill="#A37F69"/>
<path d="M10.4736 8.22084C10.4736 8.22084 8.78668 9.88105 7.98214 10.8412C7.17759 11.8013 6.09301 13.3548 6.09301 13.3548C6.09301 13.3548 5.69963 15.1728 5.8152 15.1862C5.93299 15.1995 7.08647 13.4615 7.19093 13.4726C7.29539 13.4859 7.02202 15.2239 7.12648 15.2506C7.23093 15.2773 8.51554 13.4482 8.57999 13.4348C8.64444 13.4215 8.3733 15.2373 8.4622 15.2639C8.5511 15.2906 9.85126 13.4482 9.92905 13.4482C10.0068 13.4482 10.1113 15.1484 10.2135 15.1484C10.3158 15.1484 11.1736 13.4237 11.2514 13.4348C11.3292 13.4482 11.5115 15.2128 11.6404 15.2373C11.7693 15.2639 12.3671 13.4082 12.4716 13.3948C12.576 13.3815 12.8339 15.3417 12.9516 15.3417C13.0694 15.3417 13.6917 13.4215 13.7695 13.4215C13.8473 13.4215 14.0429 15.3417 14.1718 15.3417C14.3007 15.3417 14.8852 13.3837 14.963 13.3837C15.0408 13.3837 15.5986 15.2639 15.6898 15.2395C15.7809 15.2128 16.2743 13.3593 16.3654 13.3704C16.4565 13.3837 16.8833 15.1862 17.041 15.2128C17.1966 15.2395 17.6122 13.4615 17.7411 13.4615C17.87 13.4615 18.2079 15.4329 18.3634 15.4329C18.519 15.4329 18.8702 13.4615 18.948 13.4615C19.0257 13.4615 19.7392 15.4084 19.857 15.4195C19.9747 15.4329 20.1037 13.5637 20.2459 13.5504C20.3881 13.5371 21.1549 15.4195 21.2327 15.4062C21.3105 15.3929 21.3749 13.5637 21.4527 13.5504C21.5305 13.5371 22.3995 15.2639 22.5417 15.2639C22.684 15.2639 22.5929 13.4726 22.724 13.4859C22.8529 13.4993 24.1508 15.3662 24.2686 15.3662C24.3864 15.3662 23.9308 13.4193 24.0353 13.3948C24.1397 13.3682 25.5021 15.4706 25.6443 15.4306C25.7866 15.3906 25.2821 13.5237 25.371 13.4971C25.4621 13.4704 26.8756 15.3262 27.0067 15.2751C27.1356 15.2239 26.7 13.277 26.7 13.277C26.7 13.277 25.3976 11.5768 24.7242 10.7478C24.0486 9.91661 22.9862 8.81425 22.9862 8.81425L17.7478 6.19836L10.4736 8.22084Z" fill="#BD9177"/>
<path d="M10.4734 8.2202C10.4734 8.2202 9.83556 9.42236 9.96447 9.49791C10.0934 9.57346 11.6736 8.05576 11.8269 8.09354C11.9803 8.13131 11.3157 9.70012 11.5336 9.75123C11.7514 9.80234 12.7959 8.0291 12.9248 8.05354C13.0515 8.07798 12.6559 9.77567 12.8604 9.84011C13.0649 9.90455 13.945 7.9891 14.085 8.01576C14.225 8.04021 14.1872 9.929 14.3139 9.94233C14.4406 9.95566 15.0918 8.10465 15.1807 8.10465C15.2696 8.10465 15.5252 10.0579 15.6785 10.069C15.8319 10.0823 16.2897 8.03576 16.3919 8.03576C16.4942 8.03576 17.0053 9.96677 17.172 9.96677C17.3387 9.96677 17.4387 8.01799 17.5276 7.98021C17.6165 7.94244 18.3633 9.85122 18.5767 9.85122C18.7611 9.85122 18.4478 7.95132 18.5633 7.92466C18.6789 7.90021 19.7368 9.889 19.9546 9.87789C20.1724 9.86456 19.7946 8.02243 19.8968 8.02243C19.9991 8.02243 21.1681 9.86456 21.3592 9.86456C21.5504 9.86456 20.9592 7.99132 21.0747 7.96466C21.1903 7.94021 22.9305 9.60679 23.0328 9.58013C23.135 9.55568 22.9817 8.81128 22.9817 8.81128C22.9817 8.81128 18.7833 4.49595 16.4342 4.48484C14.0339 4.47151 10.4734 8.2202 10.4734 8.2202Z" fill="#D2A590"/>
</svg>

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="2.94" y1="3.74" x2="8.67" y2="3.74" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="b" x1="9.13" y1="3.79" x2="14.85" y2="3.79" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="c" x1=".01" y1="9.12" x2="5.73" y2="9.12" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="d" x1="6.18" y1="9.08" x2="11.9" y2="9.08" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="e" x1="12.35" y1="9.13" x2="18.08" y2="9.13" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="f" x1="2.87" y1="14.56" x2="8.6" y2="14.56" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient><linearGradient id="g" x1="9.05" y1="14.6" x2="14.78" y2="14.6" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b77af4"/><stop offset="1" stop-color="#773adc"/></linearGradient></defs><path fill="url(#a)" d="M5.8 1.22l-2.86.53v3.9l2.86.61 2.87-1.15V2.2L5.8 1.22z"/><path d="M5.91 6.2l2.62-1.06A.2.2 0 008.65 5V2.36a.21.21 0 00-.13-.18l-2.65-.9h-.12l-2.6.48a.2.2 0 00-.15.18v3.53a.19.19 0 00.15.19l2.63.55a.32.32 0 00.13-.01z" fill="none"/><path d="M2.94 1.75v3.9l2.89.61v-5zm1.22 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2l.93-.16z" fill="#341a6e"/><path fill="url(#b)" d="M11.99 1.27l-2.86.53v3.9l2.86.61 2.86-1.16v-2.9l-2.86-.98z"/><path d="M9.13 1.8v3.9l2.87.61v-5zm1.21 3.6l-.81-.16v-3l.81-.13zm1.26.23l-.93-.15V2.05l.93-.17z" fill="#341a6e"/><path fill="url(#c)" d="M2.87 6.6l-2.86.53v3.9l2.86.61 2.87-1.15V7.58L2.87 6.6z"/><path d="M0 7.13V11l2.89.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.27.26l-.93-.15V7.38l.93-.16z" fill="#341a6e"/><path fill="url(#d)" d="M9.04 6.56l-2.86.53v3.9l2.86.62 2.86-1.16V7.54l-2.86-.98z"/><path d="M6.18 7.09V11l2.88.61v-5zm1.21 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.34l.93-.16z" fill="#341a6e"/><path fill="url(#e)" d="M15.21 6.61l-2.86.53v3.9l2.86.61 2.87-1.15V7.59l-2.87-.98z"/><path d="M12.35 7.14V11l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15V7.39l.93-.16z" fill="#341a6e"/><path fill="url(#f)" d="M5.73 12.04l-2.86.52v3.9l2.86.62 2.87-1.16v-2.9l-2.87-.98z"/><path d="M5.84 17l2.61-1a.18.18 0 00.12-.18v-2.6a.2.2 0 00-.13-.22l-2.64-.9a.17.17 0 00-.12 0l-2.6.47a.19.19 0 00-.16.19v3.54a.19.19 0 00.15.19L5.7 17a.23.23 0 00.14 0z" fill="none"/><path d="M2.87 12.56v3.9l2.89.62V12zm1.22 3.61L3.28 16v-3l.81-.14zm1.26.23l-.93-.15v-3.44l.93-.16z" fill="#341a6e"/><path fill="url(#g)" d="M11.91 12.08l-2.86.53v3.9l2.86.61 2.87-1.15v-2.91l-2.87-.98z"/><path d="M9.05 12.61v3.9l2.89.61v-5zm1.22 3.61l-.81-.17v-3l.81-.14zm1.26.22l-.93-.15v-3.43l.93-.16z" fill="#341a6e"/></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="b" x1="4.4" y1="11.48" x2="4.37" y2="7.53" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="c" x1="10.13" y1="15.45" x2="10.13" y2="11.9" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><linearGradient id="d" x1="14.18" y1="11.15" x2="14.18" y2="7.38" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ccc"/><stop offset="1" stop-color="#fcfcfc"/></linearGradient><radialGradient id="a" cx="13428.81" cy="3518.86" r="56.67" gradientTransform="matrix(.15 0 0 .15 -2005.33 -518.83)" gradientUnits="userSpaceOnUse"><stop offset=".18" stop-color="#5ea0ef"/><stop offset="1" stop-color="#0078d4"/></radialGradient></defs><path d="M14.21 15.72A8.5 8.5 0 013.79 2.28l.09-.06a8.5 8.5 0 0110.33 13.5" fill="url(#a)"/><path d="M6.69 7.23a13 13 0 018.91-3.58 8.47 8.47 0 00-1.49-1.44 14.34 14.34 0 00-4.69 1.1 12.54 12.54 0 00-4.08 2.82 2.76 2.76 0 011.35 1.1zM2.48 10.65a17.86 17.86 0 00-.83 2.62 7.82 7.82 0 00.62.92c.18.23.35.44.55.65a17.94 17.94 0 011.08-3.47 2.76 2.76 0 01-1.42-.72z" fill="#fff" opacity=".6"/><path d="M3.46 6.11a12 12 0 01-.69-2.94 8.15 8.15 0 00-1.1 1.45A12.69 12.69 0 002.24 7a2.69 2.69 0 011.22-.89z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><path d="M8.36 13.67a1.77 1.77 0 01.54-1.27 11.88 11.88 0 01-2.53-1.86 2.74 2.74 0 01-1.49.83 13.1 13.1 0 001.45 1.28 12.12 12.12 0 002.05 1.25 1.79 1.79 0 01-.02-.23zM14.66 13.88a12 12 0 01-2.76-.32.41.41 0 010 .11 1.75 1.75 0 01-.51 1.24 13.69 13.69 0 003.42.24A8.21 8.21 0 0016 13.81a11.5 11.5 0 01-1.34.07z" fill="#f2f2f2" opacity=".55"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/><path d="M12.32 8.93a1.83 1.83 0 01.61-1 25.5 25.5 0 01-4.46-4.14 16.91 16.91 0 01-2-2.92 7.64 7.64 0 00-1.09.42 18.14 18.14 0 002.15 3.18 26.44 26.44 0 004.79 4.46z" fill="#f2f2f2" opacity=".7"/><circle cx="14.18" cy="9.27" r="1.89" fill="url(#d)"/><path d="M17.35 10.54l-.35-.17-.3-.16h-.06l-.26-.21h-.07L16 9.8a1.76 1.76 0 01-.64.92c.12.08.25.15.38.22l.08.05.35.19.86.45a8.63 8.63 0 00.29-1.11z" fill="#f2f2f2" opacity=".55"/><circle cx="4.38" cy="8.68" r="2.73" fill="url(#b)"/><circle cx="10.13" cy="13.67" r="1.78" fill="url(#c)"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18"><defs><linearGradient id="b27f1ad0-7d11-4247-9da3-91bce6211f32" x1="8.798" y1="8.703" x2="14.683" y2="8.703" gradientUnits="userSpaceOnUse"><stop offset="0.001" stop-color="#773adc" /><stop offset="1" stop-color="#552f99" /></linearGradient><linearGradient id="b2f92112-4ca9-4b17-a019-c9f26c1a4a8f" x1="5.764" y1="3.777" x2="5.764" y2="13.78" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a67af4" /><stop offset="0.999" stop-color="#773adc" /></linearGradient></defs><g id="b8a0486a-5501-4d92-b540-a766c4b3b548"><g><g><g><path d="M16.932,11.578a8.448,8.448,0,0,1-7.95,5.59,8.15,8.15,0,0,1-2.33-.33,2.133,2.133,0,0,0,.18-.83c.01,0,.03.01.04.01a7.422,7.422,0,0,0,2.11.3,7.646,7.646,0,0,0,6.85-4.28l.01-.01Z" fill="#32bedd" /><path d="M3.582,14.068a2.025,2.025,0,0,0-.64.56,8.6,8.6,0,0,1-1.67-2.44l1.04.23v.26a.6.6,0,0,0,.47.59l.14.03a6.136,6.136,0,0,0,.62.73Z" fill="#32bedd" /><path d="M12.352.958a2.28,2.28,0,0,0-.27.81c-.02-.01-.05-.02-.07-.03a7.479,7.479,0,0,0-3.03-.63,7.643,7.643,0,0,0-5.9,2.8l-.29.06a.6.6,0,0,0-.48.58v.46l-1.02.19A8.454,8.454,0,0,1,8.982.268,8.6,8.6,0,0,1,12.352.958Z" fill="#32bedd" /><path d="M16.872,5.7l-1.09-.38a6.6,6.6,0,0,0-.72-1.16c-.02-.03-.04-.05-.05-.07a2.083,2.083,0,0,0,.72-.45A7.81,7.81,0,0,1,16.872,5.7Z" fill="#32bedd" /><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="#fff" /><g><g id="e918f286-5032-4942-ad29-ea17e6f1cc90"><path d="M1.1,5.668l1.21-.23v6.55l-1.23-.27-.99-.22a.111.111,0,0,1-.09-.12v-5.4a.12.12,0,0,1,.09-.12Z" fill="#a67af4" /></g><g><g id="a47a99dd-4d47-4c70-8c42-c5ac274ce496"><g><path d="M10.072,11.908l2.54.56L8.672,14.1c-.02,0-.03.01-.05.01a.154.154,0,0,1-.15-.15V3.448a.154.154,0,0,1,.15-.15.09.09,0,0,1,.05.01l4.46,1.56-3.05.57a.565.565,0,0,0-.44.54v5.4A.537.537,0,0,0,10.072,11.908Z" fill="url(#b27f1ad0-7d11-4247-9da3-91bce6211f32)" /><path d="M8.586,3.3,2.878,4.378a.177.177,0,0,0-.14.175V12.68a.177.177,0,0,0,.137.174L8.581,14.1a.176.176,0,0,0,.21-.174V3.478A.175.175,0,0,0,8.619,3.3Z" fill="url(#b2f92112-4ca9-4b17-a019-c9f26c1a4a8f)" /></g></g><polygon points="5.948 4.921 5.948 12.483 7.934 12.814 7.934 4.564 5.948 4.921" fill="#b796f9" opacity="0.5" /><polygon points="3.509 5.329 3.509 11.954 5.238 12.317 5.238 5.031 3.509 5.329" fill="#b796f9" opacity="0.5" /></g></g></g><path d="M16,2.048a1.755,1.755,0,1,1-1.76-1.76A1.756,1.756,0,0,1,16,2.048Z" fill="#32bedd" /><circle cx="4.65" cy="15.973" r="1.759" fill="#32bedd" /></g><path d="M18,6.689v3.844a.222.222,0,0,1-.133.2l-.766.316-3.07,1.268-.011,0a.126.126,0,0,1-.038,0,.1.1,0,0,1-.1-.1V5.234a.1.1,0,0,1,.054-.088l0,0,.019,0a.031.031,0,0,1,.019,0,.055.055,0,0,1,.034.008l.011,0,.012,0L17.05,6.2l.8.282A.213.213,0,0,1,18,6.689Z" fill="#773adc" /><path d="M13.959,5.14l-3.8.715a.118.118,0,0,0-.093.117v5.409a.118.118,0,0,0,.091.116l3.8.831a.115.115,0,0,0,.137-.09.109.109,0,0,0,0-.026V5.256a.117.117,0,0,0-.115-.118A.082.082,0,0,0,13.959,5.14Z" fill="#a67af4" /></g></g></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="-175.993" y1="-343.723" x2="-175.993" y2="-359.232" gradientTransform="matrix(1.156 0 0 1.029 212.573 370.548)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fea11b"/><stop offset=".284" stop-color="#fea51a"/><stop offset=".547" stop-color="#feb018"/><stop offset=".8" stop-color="#ffc314"/><stop offset="1" stop-color="#ffd70f"/></linearGradient></defs><path d="M5.54 13.105l-.586.588a.267.267 0 01-.377 0L.223 9.353a.533.533 0 010-.755l.588-.59 4.732 4.718a.267.267 0 010 .378z" fill="#50e6ff"/><path d="M4.863 4.305l.59.588a.267.267 0 010 .378L.806 9.932l-.59-.589a.533.533 0 01-.001-.754l4.273-4.285a.267.267 0 01.376 0z" fill="#1490df"/><path d="M17.19 8.012l.588.59a.533.533 0 01-.001.754l-4.354 4.34a.267.267 0 01-.377 0l-.586-.587a.267.267 0 010-.377l4.732-4.718z" fill="#50e6ff"/><path d="M17.782 9.34l-.59.589-4.648-4.662a.267.267 0 010-.377l.59-.588a.267.267 0 01.378 0l4.273 4.286a.533.533 0 010 .753z" fill="#1490df"/><path d="M8.459 9.9H4.87a.193.193 0 01-.2-.181.166.166 0 01.018-.075L8.991 1.13a.206.206 0 01.186-.106h4.245a.193.193 0 01.2.181.165.165 0 01-.035.1L8.534 7.966h4.928a.193.193 0 01.2.181.176.176 0 01-.052.122l-8.189 8.519c-.077.046-.624.5-.356-.189z" fill="url(#a)"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><radialGradient id="b" cx="9.36" cy="10.57" r="7.07" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f2f2f2"/><stop offset=".58" stop-color="#eee"/><stop offset="1" stop-color="#e6e6e6"/></radialGradient><linearGradient id="a" x1="2.59" y1="10.16" x2="15.41" y2="10.16" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#005ba1"/><stop offset=".07" stop-color="#0060a9"/><stop offset=".36" stop-color="#0071c8"/><stop offset=".52" stop-color="#0078d4"/><stop offset=".64" stop-color="#0074cd"/><stop offset=".82" stop-color="#006abb"/><stop offset="1" stop-color="#005ba1"/></linearGradient></defs><path d="M9 5.14c-3.54 0-6.41-1-6.41-2.32v12.36c0 1.27 2.82 2.3 6.32 2.32H9c3.54 0 6.41-1 6.41-2.32V2.82c0 1.29-2.87 2.32-6.41 2.32z" fill="url(#a)"/><path d="M15.41 2.82c0 1.29-2.87 2.32-6.41 2.32s-6.41-1-6.41-2.32S5.46.5 9 .5s6.41 1 6.41 2.32" fill="#e8e8e8"/><path d="M13.92 2.63c0 .82-2.21 1.48-4.92 1.48s-4.92-.66-4.92-1.48S6.29 1.16 9 1.16s4.92.66 4.92 1.47" fill="#50e6ff"/><path d="M9 3a11.55 11.55 0 00-3.89.57A11.42 11.42 0 009 4.11a11.15 11.15 0 003.89-.58A11.84 11.84 0 009 3z" fill="#198ab3"/><path d="M12.9 11.4V8H12v4.13h2.46v-.73zM5.76 9.73a1.83 1.83 0 01-.51-.31.44.44 0 01-.12-.32.34.34 0 01.15-.3.68.68 0 01.42-.12 1.62 1.62 0 011 .29v-.86a2.58 2.58 0 00-1-.16 1.64 1.64 0 00-1.09.34 1.08 1.08 0 00-.42.89c0 .51.32.91 1 1.21a2.88 2.88 0 01.62.36.42.42 0 01.15.32.38.38 0 01-.16.31.81.81 0 01-.45.11 1.66 1.66 0 01-1.09-.42V12a2.17 2.17 0 001.07.24 1.88 1.88 0 001.18-.33 1.08 1.08 0 00.33-.91 1.05 1.05 0 00-.25-.7 2.42 2.42 0 00-.83-.57zM11 11.32a2.34 2.34 0 00.33-1.26A2.32 2.32 0 0011 9a1.81 1.81 0 00-.7-.75 2 2 0 00-1-.26 2.11 2.11 0 00-1.08.27 1.86 1.86 0 00-.73.74 2.46 2.46 0 00-.26 1.14 2.26 2.26 0 00.24 1 1.76 1.76 0 00.69.74 2.06 2.06 0 001 .3l.86 1h1.21L10 12.08a1.79 1.79 0 001-.76zm-1-.25a.94.94 0 01-.76.35.92.92 0 01-.76-.36 1.52 1.52 0 01-.29-1 1.53 1.53 0 01.29-1 1 1 0 01.78-.37.87.87 0 01.75.37 1.62 1.62 0 01.27 1 1.46 1.46 0 01-.28 1.01z" fill="url(#b)"/></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><linearGradient id="a" x1="8.88" y1="12.21" x2="8.88" y2=".21" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0078d4"/><stop offset=".82" stop-color="#5ea0ef"/></linearGradient><linearGradient id="b" x1="8.88" y1="16.84" x2="8.88" y2="12.21" gradientUnits="userSpaceOnUse"><stop offset=".15" stop-color="#ccc"/><stop offset="1" stop-color="#707070"/></linearGradient></defs><rect x="-.12" y=".21" width="18" height="12" rx=".6" fill="url(#a)"/><path fill="#50e6ff" d="M11.88 4.46v3.49l-3 1.76v-3.5l3-1.75z"/><path fill="#c3f1ff" d="M11.88 4.46l-3 1.76-3-1.76 3-1.75 3 1.75z"/><path fill="#9cebff" d="M8.88 6.22v3.49l-3-1.76V4.46l3 1.76z"/><path fill="#c3f1ff" d="M5.88 7.95l3-1.74v3.5l-3-1.76z"/><path fill="#9cebff" d="M11.88 7.95l-3-1.74v3.5l3-1.76z"/><path d="M12.49 15.84c-1.78-.28-1.85-1.56-1.85-3.63H7.11c0 2.07-.06 3.35-1.84 3.63a1 1 0 00-.89 1h9a1 1 0 00-.89-1z" fill="url(#b)"/></svg>

After

Width:  |  Height:  |  Size: 973 B

View File

@@ -108,7 +108,7 @@
"user_tooltip_more_help": "More details on how to create alerts",
"choose_alert_type": "Choose a type for the alert",
"metric_based_alert": "Metric based Alert",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data.",
"log_based_alert": "Log-based Alert",
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
"traces_based_alert": "Trace-based Alert",

View File

@@ -0,0 +1,3 @@
{
"delete_confirm_message": "Are you sure you want to delete {{keyName}}? Deleting an ingestion key is irreversible and cannot be undone."
}

View File

@@ -108,7 +108,7 @@
"user_tooltip_more_help": "More details on how to create alerts",
"choose_alert_type": "Choose a type for the alert",
"metric_based_alert": "Metric based Alert",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data",
"metric_based_alert_desc": "Send a notification when a condition occurs in the metric data.",
"log_based_alert": "Log-based Alert",
"log_based_alert_desc": "Send a notification when a condition occurs in the logs data.",
"traces_based_alert": "Trace-based Alert",

View File

@@ -0,0 +1,4 @@
{
"delete_confirm_message": "Are you sure you want to delete {{keyName}}? Deleting an ingestion key is irreversible and cannot be undone.",
"delete_limit_confirm_message": "Are you sure you want to delete {{limit_name}} limit for ingestion key {{keyName}}?"
}

View File

@@ -8,6 +8,7 @@
"GET_STARTED_LOGS_MANAGEMENT": "SigNoz | Get Started | Logs",
"GET_STARTED_INFRASTRUCTURE_MONITORING": "SigNoz | Get Started | Infrastructure",
"GET_STARTED_AWS_MONITORING": "SigNoz | Get Started | AWS",
"GET_STARTED_AZURE_MONITORING": "SigNoz | Get Started | AZURE",
"TRACE": "SigNoz | Trace",
"TRACE_DETAIL": "SigNoz | Trace Detail",
"TRACES_EXPLORER": "SigNoz | Traces Explorer",

View File

@@ -178,23 +178,25 @@ function App(): JSX.Element {
}, [pathname]);
useEffect(() => {
try {
const isThemeAnalyticsSent = getLocalStorageApi(
LOCALSTORAGE.THEME_ANALYTICS,
);
if (!isThemeAnalyticsSent) {
trackEvent('Theme Analytics', {
theme: isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT,
user: pick(user, ['email', 'userId', 'name']),
org,
});
setLocalStorageApi(LOCALSTORAGE.THEME_ANALYTICS, 'true');
if (user && user?.email && user?.userId && user?.name) {
try {
const isThemeAnalyticsSent = getLocalStorageApi(
LOCALSTORAGE.THEME_ANALYTICS_V1,
);
if (!isThemeAnalyticsSent) {
trackEvent('Theme Analytics', {
theme: isDarkMode ? THEME_MODE.DARK : THEME_MODE.LIGHT,
user: pick(user, ['email', 'userId', 'name']),
org,
});
setLocalStorageApi(LOCALSTORAGE.THEME_ANALYTICS_V1, 'true');
}
} catch {
console.error('Failed to parse local storage theme analytics event');
}
} catch {
console.error('Failed to parse local storage theme analytics event');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [user]);
return (
<ConfigProvider theme={themeConfig}>

View File

@@ -11,6 +11,13 @@ export const ServiceMetricsPage = Loadable(
),
);
export const ServiceTopLevelOperationsPage = Loadable(
() =>
import(
/* webpackChunkName: "ServiceMetricsPage" */ 'pages/ServiceTopLevelOperations'
),
);
export const ServiceMapPage = Loadable(
() => import(/* webpackChunkName: "ServiceMapPage" */ 'modules/Servicemap'),
);

View File

@@ -33,6 +33,7 @@ import {
ServiceMapPage,
ServiceMetricsPage,
ServicesTablePage,
ServiceTopLevelOperationsPage,
SettingsPage,
ShortcutsPage,
SignupPage,
@@ -84,6 +85,13 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'SERVICE_METRICS',
},
{
path: ROUTES.SERVICE_TOP_LEVEL_OPERATIONS,
exact: true,
component: ServiceTopLevelOperationsPage,
isPrivate: true,
key: 'SERVICE_TOP_LEVEL_OPERATIONS',
},
{
path: ROUTES.SERVICE_MAP,
component: ServiceMapPage,

View File

@@ -16,7 +16,7 @@ export function ErrorResponseHandler(error: AxiosError): ErrorResponse {
return {
statusCode,
payload: null,
error: data.errorType,
error: data.errorType || data.type,
message: null,
};
}

View File

@@ -0,0 +1,29 @@
import { GatewayApiV1Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
CreateIngestionKeyProps,
IngestionKeyProps,
} from 'types/api/ingestionKeys/types';
const createIngestionKey = async (
props: CreateIngestionKeyProps,
): Promise<SuccessResponse<IngestionKeyProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.post('/workspaces/me/keys', {
...props,
});
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default createIngestionKey;

View File

@@ -0,0 +1,26 @@
import { GatewayApiV1Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AllIngestionKeyProps } from 'types/api/ingestionKeys/types';
const deleteIngestionKey = async (
id: string,
): Promise<SuccessResponse<AllIngestionKeyProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.delete(
`/workspaces/me/keys/${id}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default deleteIngestionKey;

View File

@@ -0,0 +1,21 @@
import { GatewayApiV1Instance } from 'api';
import { AxiosResponse } from 'axios';
import {
AllIngestionKeyProps,
GetIngestionKeyProps,
} from 'types/api/ingestionKeys/types';
export const getAllIngestionKeys = (
props: GetIngestionKeyProps,
): Promise<AxiosResponse<AllIngestionKeyProps>> => {
// eslint-disable-next-line @typescript-eslint/naming-convention
const { search, per_page, page } = props;
const BASE_URL = '/workspaces/me/keys';
const URL_QUERY_PARAMS =
search && search.length > 0
? `/search?name=${search}&page=1&per_page=100`
: `?page=${page}&per_page=${per_page}`;
return GatewayApiV1Instance.get(`${BASE_URL}${URL_QUERY_PARAMS}`);
};

View File

@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { GatewayApiV1Instance } from 'api';
import axios from 'axios';
import {
AddLimitProps,
LimitSuccessProps,
} from 'types/api/ingestionKeys/limits/types';
interface SuccessResponse<T> {
statusCode: number;
error: null;
message: string;
payload: T;
}
interface ErrorResponse {
statusCode: number;
error: string;
message: string;
payload: null;
}
const createLimitForIngestionKey = async (
props: AddLimitProps,
): Promise<SuccessResponse<LimitSuccessProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.post(
`/workspaces/me/keys/${props.keyID}/limits`,
{
...props,
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
if (axios.isAxiosError(error)) {
// Axios error
const errResponse: ErrorResponse = {
statusCode: error.response?.status || 500,
error: error.response?.data?.error,
message: error.response?.data?.status || 'An error occurred',
payload: null,
};
throw errResponse;
} else {
// Non-Axios error
const errResponse: ErrorResponse = {
statusCode: 500,
error: 'Unknown error',
message: 'An unknown error occurred',
payload: null,
};
throw errResponse;
}
}
};
export default createLimitForIngestionKey;

View File

@@ -0,0 +1,26 @@
import { GatewayApiV1Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { AllIngestionKeyProps } from 'types/api/ingestionKeys/types';
const deleteLimitsForIngestionKey = async (
id: string,
): Promise<SuccessResponse<AllIngestionKeyProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.delete(
`/workspaces/me/limits/${id}`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default deleteLimitsForIngestionKey;

View File

@@ -0,0 +1,65 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
import { GatewayApiV1Instance } from 'api';
import axios from 'axios';
import {
LimitSuccessProps,
UpdateLimitProps,
} from 'types/api/ingestionKeys/limits/types';
interface SuccessResponse<T> {
statusCode: number;
error: null;
message: string;
payload: T;
}
interface ErrorResponse {
statusCode: number;
error: string;
message: string;
payload: null;
}
const updateLimitForIngestionKey = async (
props: UpdateLimitProps,
): Promise<SuccessResponse<LimitSuccessProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.patch(
`/workspaces/me/limits/${props.limitID}`,
{
config: props.config,
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
if (axios.isAxiosError(error)) {
// Axios error
const errResponse: ErrorResponse = {
statusCode: error.response?.status || 500,
error: error.response?.data?.error,
message: error.response?.data?.status || 'An error occurred',
payload: null,
};
throw errResponse;
} else {
// Non-Axios error
const errResponse: ErrorResponse = {
statusCode: 500,
error: 'Unknown error',
message: 'An unknown error occurred',
payload: null,
};
throw errResponse;
}
}
};
export default updateLimitForIngestionKey;

View File

@@ -0,0 +1,32 @@
import { GatewayApiV1Instance } from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
IngestionKeysPayloadProps,
UpdateIngestionKeyProps,
} from 'types/api/ingestionKeys/types';
const updateIngestionKey = async (
props: UpdateIngestionKeyProps,
): Promise<SuccessResponse<IngestionKeysPayloadProps> | ErrorResponse> => {
try {
const response = await GatewayApiV1Instance.patch(
`/workspaces/me/keys/${props.id}`,
{
...props.data,
},
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};
export default updateIngestionKey;

View File

@@ -3,6 +3,7 @@ const apiV1 = '/api/v1/';
export const apiV2 = '/api/v2/';
export const apiV3 = '/api/v3/';
export const apiV4 = '/api/v4/';
export const gatewayApiV1 = '/api/gateway/v1';
export const apiAlertManager = '/api/alertmanager';
export default apiV1;

View File

@@ -9,7 +9,13 @@ import { ENVIRONMENT } from 'constants/env';
import { LOCALSTORAGE } from 'constants/localStorage';
import store from 'store';
import apiV1, { apiAlertManager, apiV2, apiV3, apiV4 } from './apiV1';
import apiV1, {
apiAlertManager,
apiV2,
apiV3,
apiV4,
gatewayApiV1,
} from './apiV1';
import { Logout } from './utils';
const interceptorsResponse = (
@@ -134,6 +140,19 @@ ApiV4Instance.interceptors.response.use(
ApiV4Instance.interceptors.request.use(interceptorsRequestResponse);
//
// gateway Api V1
export const GatewayApiV1Instance = axios.create({
baseURL: `${ENVIRONMENT.baseURL}${gatewayApiV1}`,
});
GatewayApiV1Instance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,
);
GatewayApiV1Instance.interceptors.request.use(interceptorsRequestResponse);
//
AxiosAlertManagerInstance.interceptors.response.use(
interceptorsResponse,
interceptorRejected,

View File

@@ -287,7 +287,7 @@ function CustomTimePicker({
)
}
arrow={false}
trigger="hover"
trigger="click"
open={open}
onOpenChange={handleOpenChange}
style={{

View File

@@ -3,6 +3,7 @@ import './DropDown.styles.scss';
import { EllipsisOutlined } from '@ant-design/icons';
import { Button, Dropdown, MenuProps } from 'antd';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useState } from 'react';
function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
const isDarkMode = useIsDarkMode();
@@ -14,12 +15,24 @@ function DropDown({ element }: { element: JSX.Element[] }): JSX.Element {
}),
);
const [isDdOpen, setDdOpen] = useState<boolean>(false);
return (
<Dropdown menu={{ items }}>
<Dropdown
menu={{
items,
onMouseEnter: (): void => setDdOpen(true),
onMouseLeave: (): void => setDdOpen(false),
}}
open={isDdOpen}
>
<Button
type="link"
className={!isDarkMode ? 'dropdown-button--dark' : 'dropdown-button'}
onClick={(e): void => e.preventDefault()}
onClick={(e): void => {
e.preventDefault();
setDdOpen(true);
}}
>
<EllipsisOutlined className="dropdown-icon" />
</Button>

View File

@@ -2,7 +2,7 @@ import './AddToQueryHOC.styles.scss';
import { Popover } from 'antd';
import { OPERATORS } from 'constants/queryBuilder';
import { memo, ReactNode, useCallback, useMemo } from 'react';
import { memo, MouseEvent, ReactNode, useMemo } from 'react';
function AddToQueryHOC({
fieldKey,
@@ -10,9 +10,10 @@ function AddToQueryHOC({
onAddToQuery,
children,
}: AddToQueryHOCProps): JSX.Element {
const handleQueryAdd = useCallback(() => {
const handleQueryAdd = (event: MouseEvent<HTMLDivElement>): void => {
event.stopPropagation();
onAddToQuery(fieldKey, fieldValue, OPERATORS.IN);
}, [fieldKey, fieldValue, onAddToQuery]);
};
const popOverContent = useMemo(() => <span>Add to query: {fieldKey}</span>, [
fieldKey,

View File

@@ -1,25 +1,33 @@
/* eslint-disable prefer-destructuring */
import './CodeCopyBtn.scss';
import { CheckOutlined, CopyOutlined } from '@ant-design/icons';
import cx from 'classnames';
import { useState } from 'react';
import React, { useState } from 'react';
export default function CodeCopyBtn({
function CodeCopyBtn({
children,
onCopyClick,
}: {
children: React.ReactNode;
onCopyClick?: (additionalInfo?: Record<string, unknown>) => void;
}): JSX.Element {
const [isSnippetCopied, setIsSnippetCopied] = useState(false);
const handleClick = (): void => {
let copiedText = '';
if (children && Array.isArray(children)) {
setIsSnippetCopied(true);
navigator.clipboard.writeText(children[0].props.children[0]).finally(() => {
copiedText = (children[0].props.children[0] as string).slice(0, 200); // slicing is done due to the limitation in accepted char length in attributes
setTimeout(() => {
setIsSnippetCopied(false);
}, 1000);
});
copiedText = (children[0].props.children[0] as string).slice(0, 200);
}
onCopyClick?.({ copiedText });
};
return (
@@ -30,3 +38,9 @@ export default function CodeCopyBtn({
</div>
);
}
CodeCopyBtn.defaultProps = {
onCopyClick: (): void => {},
};
export default CodeCopyBtn;

View File

@@ -2,6 +2,8 @@
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import logEvent from 'api/common/logEvent';
import { isEmpty } from 'lodash-es';
import ReactMarkdown from 'react-markdown';
import { CodeProps } from 'react-markdown/lib/ast-to-react';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -15,10 +17,28 @@ interface LinkProps {
children: React.ReactElement;
}
function Pre({ children }: { children: React.ReactNode }): JSX.Element {
function Pre({
children,
elementDetails,
trackCopyAction,
}: {
children: React.ReactNode;
trackCopyAction: boolean;
elementDetails: Record<string, unknown>;
}): JSX.Element {
const { trackingTitle = '', ...rest } = elementDetails;
const handleClick = (additionalInfo?: Record<string, unknown>): void => {
const trackingData = { ...rest, copiedContent: additionalInfo };
if (trackCopyAction && !isEmpty(trackingTitle)) {
logEvent(trackingTitle as string, trackingData);
}
};
return (
<pre className="code-snippet-container">
<CodeCopyBtn>{children}</CodeCopyBtn>
<CodeCopyBtn onCopyClick={handleClick}>{children}</CodeCopyBtn>
{children}
</pre>
);
@@ -83,9 +103,13 @@ function CustomTag({ color }: { color: string }): JSX.Element {
function MarkdownRenderer({
markdownContent,
variables,
trackCopyAction,
elementDetails,
}: {
markdownContent: any;
variables: any;
trackCopyAction?: boolean;
elementDetails?: Record<string, unknown>;
}): JSX.Element {
const interpolatedMarkdown = interpolateMarkdown(markdownContent, variables);
@@ -96,7 +120,12 @@ function MarkdownRenderer({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
a: Link,
pre: Pre,
pre: ({ children }) =>
Pre({
children,
elementDetails: elementDetails ?? {},
trackCopyAction: !!trackCopyAction,
}),
code: Code,
customtag: CustomTag,
}}
@@ -106,4 +135,9 @@ function MarkdownRenderer({
);
}
MarkdownRenderer.defaultProps = {
elementDetails: {},
trackCopyAction: false,
};
export { Code, Link, MarkdownRenderer, Pre };

View File

@@ -0,0 +1,38 @@
.tags-container {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.tags {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.ant-form-item {
margin-bottom: 0;
}
.ant-tag {
margin-right: 0;
background: var(--bg-vanilla-100);
}
}
.add-tag-container {
display: flex;
align-items: center;
gap: 4px;
.ant-form-item {
margin-bottom: 0;
}
.confirm-cancel-actions {
display: flex;
align-items: center;
gap: 2px;
}
}

View File

@@ -0,0 +1,138 @@
import './Tags.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button } from 'antd';
import { Tag } from 'antd/lib';
import Input from 'components/Input';
import { Check, X } from 'lucide-react';
import { TweenOneGroup } from 'rc-tween-one';
import React, { Dispatch, SetStateAction, useState } from 'react';
function Tags({ tags, setTags }: AddTagsProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>('');
const [inputVisible, setInputVisible] = useState<boolean>(false);
const handleInputConfirm = (): void => {
if (tags.indexOf(inputValue) > -1) {
return;
}
if (inputValue) {
setTags([...tags, inputValue]);
}
setInputVisible(false);
setInputValue('');
};
const handleClose = (removedTag: string): void => {
const newTags = tags.filter((tag) => tag !== removedTag);
setTags(newTags);
};
const showInput = (): void => {
setInputVisible(true);
setInputValue('');
};
const hideInput = (): void => {
setInputValue('');
setInputVisible(false);
};
const onChangeHandler = (
value: string,
func: Dispatch<SetStateAction<string>>,
): void => {
func(value);
};
const forMap = (tag: string): React.ReactElement => (
<span key={tag} style={{ display: 'inline-block' }}>
<Tag
closable
onClose={(e): void => {
e.preventDefault();
handleClose(tag);
}}
>
{tag}
</Tag>
</span>
);
const tagChild = tags.map(forMap);
const renderTagsAnimated = (): React.ReactElement => (
<TweenOneGroup
appear={false}
className="tags"
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
onEnd={(e): void => {
if (e.type === 'appear' || e.type === 'enter') {
(e.target as any).style = 'display: inline-block';
}
}}
>
{tagChild}
</TweenOneGroup>
);
return (
<div className="tags-container">
{renderTagsAnimated()}
{inputVisible && (
<div className="add-tag-container">
<Input
type="text"
autoFocus
value={inputValue}
onChangeHandler={(event): void =>
onChangeHandler(event.target.value, setInputValue)
}
onPressEnterHandler={handleInputConfirm}
/>
<div className="confirm-cancel-actions">
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<Check size={14} />}
onClick={handleInputConfirm}
/>
<Button
type="primary"
className="periscope-btn"
size="small"
icon={<X size={14} />}
onClick={hideInput}
/>
</div>
</div>
)}
{!inputVisible && (
<Button
type="primary"
size="small"
style={{
fontSize: '11px',
}}
icon={<PlusOutlined />}
onClick={showInput}
>
New Tag
</Button>
)}
</div>
);
}
interface AddTagsProps {
tags: string[];
setTags: Dispatch<SetStateAction<string[]>>;
}
export default Tags;

View File

@@ -1,6 +1,7 @@
/* eslint-disable sonarjs/cognitive-complexity */
import './Uplot.styles.scss';
import * as Sentry from '@sentry/react';
import { Typography } from 'antd';
import { ToggleGraphProps } from 'components/Graph/types';
import { LineChart } from 'lucide-react';
@@ -13,7 +14,6 @@ import {
useImperativeHandle,
useRef,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import UPlot from 'uplot';
import { dataMatch, optionsUpdateState } from './utils';
@@ -139,7 +139,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
}
return (
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<div className="uplot-graph-container" ref={targetRef}>
{data && data[0] && data[0]?.length === 0 ? (
<div className="not-found">
@@ -147,7 +147,7 @@ const Uplot = forwardRef<ToggleGraphProps | undefined, UplotProps>(
</div>
) : null}
</div>
</ErrorBoundary>
</Sentry.ErrorBoundary>
);
},
);

View File

@@ -2,8 +2,27 @@
color: var(--bg-amber-500);
border-color: var(--bg-amber-500);
.ant-btn:hover {
> .ant-btn:hover {
color: var(--bg-amber-400) !important;
border-color: var(--bg-amber-300) !important;
}
}
.lightMode {
.facing-issue-button {
color: var(--bg-vanilla-500);
border-color: var(--bg-vanilla-300);
> .ant-btn:hover {
color: var(--bg-vanilla-500) !important;
border-color: var(--bg-vanilla-300) !important;
}
}
}
.tooltip-overlay {
text-wrap: nowrap;
.ant-tooltip-inner {
width: max-content;
}
}

View File

@@ -39,7 +39,12 @@ function FacingIssueBtn({
return isCloudUserVal && isChatSupportEnabled ? ( // Note: we would need to move this condition to license based in future
<div className="facing-issue-button">
<Tooltip title={onHoverText} autoAdjustOverflow>
<Tooltip
title={onHoverText}
autoAdjustOverflow
style={{ padding: 8 }}
overlayClassName="tooltip-overlay"
>
<Button
className={cx('periscope-btn', 'facing-issue-button', className)}
onClick={handleFacingIssuesClick}

View File

@@ -55,3 +55,20 @@ State: ${(alertDef as any)?.state || ''}
Alert Id: ${ruleId}
Thanks`;
export const integrationsListMessage = `Hi Team,
I need help with Integrations.
Thanks`;
export const integrationDetailMessage = (
selectedIntegration: string,
): string => `
Hi Team,
I need help in configuring this integration.
Integration Id: ${selectedIntegration}
Thanks`;

View File

@@ -20,4 +20,5 @@ export enum FeatureKeys {
ONBOARDING = 'ONBOARDING',
CHAT_SUPPORT = 'CHAT_SUPPORT',
PLANNED_MAINTENANCE = 'PLANNED_MAINTENANCE',
GATEWAY = 'GATEWAY',
}

View File

@@ -18,5 +18,5 @@ export enum LOCALSTORAGE {
DASHBOARD_VARIABLES = 'DASHBOARD_VARIABLES',
SHOW_EXPLORER_TOOLBAR = 'SHOW_EXPLORER_TOOLBAR',
PINNED_ATTRIBUTES = 'PINNED_ATTRIBUTES',
THEME_ANALYTICS = 'THEME_ANALYTICS',
THEME_ANALYTICS_V1 = 'THEME_ANALYTICS_V1',
}

View File

@@ -30,6 +30,7 @@ export const getComponentForPanelType = (
dataSource === DataSource.LOGS ? LogsPanelComponent : TracesTableComponent,
[PANEL_TYPES.BAR]: Uplot,
[PANEL_TYPES.PIE]: null,
[PANEL_TYPES.HISTOGRAM]: Uplot,
[PANEL_TYPES.EMPTY_WIDGET]: null,
};
@@ -39,4 +40,5 @@ export const getComponentForPanelType = (
export const AVAILABLE_EXPORT_PANEL_TYPES = [
PANEL_TYPES.TIME_SERIES,
PANEL_TYPES.TABLE,
PANEL_TYPES.LIST,
];

View File

@@ -174,8 +174,8 @@ export const initialQueryBuilderFormValues: IBuilderQuery = {
sourceNames: alphabet,
}),
disabled: false,
having: [],
stepInterval: 60,
having: [],
limit: null,
orderBy: [],
groupBy: [],
@@ -286,6 +286,7 @@ export enum PANEL_TYPES {
TRACE = 'trace',
BAR = 'bar',
PIE = 'pie',
HISTOGRAM = 'histogram',
EMPTY_WIDGET = 'EMPTY_WIDGET',
}

View File

@@ -2,6 +2,7 @@ const ROUTES = {
SIGN_UP: '/signup',
LOGIN: '/login',
SERVICE_METRICS: '/services/:servicename',
SERVICE_TOP_LEVEL_OPERATIONS: '/services/:servicename/top-level-operations',
SERVICE_MAP: '/service-map',
TRACE: '/trace',
TRACE_DETAIL: '/trace/:id',
@@ -12,6 +13,7 @@ const ROUTES = {
GET_STARTED_INFRASTRUCTURE_MONITORING:
'/get-started/infrastructure-monitoring',
GET_STARTED_AWS_MONITORING: '/get-started/aws-monitoring',
GET_STARTED_AZURE_MONITORING: '/get-started/azure-monitoring',
USAGE_EXPLORER: '/usage-explorer',
APPLICATION: '/services',
ALL_DASHBOARD: '/dashboard',

View File

@@ -78,6 +78,82 @@ const themeColors = {
mediumVioletRed: '#C71585',
paleGreen: '#98FB98',
},
lightModeColor: {
robin: '#3F5ECC',
dodgerBlueDark: '#0C6EED',
steelgrey: '#2f4b7c',
steelpurple: '#665191',
steelindigo: '#a05195',
steelpink: '#d45087',
steelcoral: '#f95d6a',
steelorange: '#ff7c43',
steelgold: '#ffa600',
steelrust: '#de425b',
steelgreen: '#41967e',
mediumOrchidDark: '#C326FD',
seaBuckthornDark: '#E66E05',
seaGreen: '#219653',
turquoiseBlueDark: '#0099CC',
silverDark: '#757575',
outrageousOrangeDark: '#F9521A',
roseBudDark: '#EB6437',
deepSkyBlueDark: '#0595BD',
royalBlue: '#3366E6',
avocadoDark: '#8E8E29',
mintGreenDark: '#00C700',
chestnut: '#B34D4D',
limaDark: '#6E9900',
olive: '#809900',
beautyBushDark: '#E25555',
danube: '#6680B3',
oliveDrab: '#66991A',
lavenderRoseDark: '#F024BD',
electricLimeDark: '#84A800',
radicalRed: '#FF1A66',
harleyOrange: '#E6331A',
gladeGreen: '#66994D',
hemlock: '#66664D',
vidaLoca: '#4D8000',
rust: '#B33300',
red: '#FF0000', // Adding more colors, we need to get better colors from design team
blue: '#0000FF',
green: '#00FF00',
purple: '#800080',
magentaDark: '#EB00EB',
pinkDark: '#FF3D5E',
brown: '#A52A2A',
teal: '#008080',
limeDark: '#07A207',
maroon: '#800000',
navy: '#000080',
gray: '#808080',
skyBlueDark: '#0CA7E4',
indigo: '#4B0082',
slateGray: '#708090',
chocolate: '#D2691E',
tomato: '#FF6347',
steelBlue: '#4682B4',
peruDark: '#D16E0A',
darkOliveGreen: '#556B2F',
indianRed: '#CD5C5C',
mediumSlateBlue: '#7B68EE',
rosyBrownDark: '#CB4848',
darkSlateGray: '#2F4F4F',
fuchsia: '#FF0AFF',
salmonDark: '#FF432E',
darkSalmonDark: '#D26541',
paleVioletRedDark: '#E83089',
mediumPurple: '#9370DB',
darkOrchid: '#9932CC',
mediumSeaGreenDark: '#109E50',
lightCoralDark: '#F85959',
darkSeaGreenDark: '#509F50',
sandyBrownDark: '#D97117',
darkKhakiDark: '#99900A',
cornflowerBlueDark: '#3371E6',
mediumVioletRed: '#C71585',
paleGreenDark: '#0D910D',
},
errorColor: '#d32f2f',
royalGrey: '#888888',
matterhornGrey: '#555555',

View File

@@ -68,7 +68,7 @@ type ExpiryOption = {
label: string;
};
const EXPIRATION_WITHIN_SEVEN_DAYS = 7;
export const EXPIRATION_WITHIN_SEVEN_DAYS = 7;
const API_KEY_EXPIRY_OPTIONS: ExpiryOption[] = [
{ value: '1', label: '1 day' },
@@ -79,6 +79,25 @@ const API_KEY_EXPIRY_OPTIONS: ExpiryOption[] = [
{ value: '0', label: 'No Expiry' },
];
export const isExpiredToken = (expiryTimestamp: number): boolean => {
if (expiryTimestamp === 0) {
return false;
}
const currentTime = dayjs();
const tokenExpiresAt = dayjs.unix(expiryTimestamp);
return tokenExpiresAt.isBefore(currentTime);
};
export const getDateDifference = (
createdTimestamp: number,
expiryTimestamp: number,
): number => {
const differenceInSeconds = Math.abs(expiryTimestamp - createdTimestamp);
// Convert seconds to days
return differenceInSeconds / (60 * 60 * 24);
};
function APIKeys(): JSX.Element {
const { user } = useSelector<AppState, AppReducer>((state) => state.app);
const { notifications } = useNotifications();
@@ -311,25 +330,6 @@ function APIKeys(): JSX.Element {
hideAddViewModal();
};
const getDateDifference = (
createdTimestamp: number,
expiryTimestamp: number,
): number => {
const differenceInSeconds = Math.abs(expiryTimestamp - createdTimestamp);
// Convert seconds to days
return differenceInSeconds / (60 * 60 * 24);
};
const isExpiredToken = (expiryTimestamp: number): boolean => {
if (expiryTimestamp === 0) {
return false;
}
const currentTime = dayjs();
const tokenExpiresAt = dayjs.unix(expiryTimestamp);
return tokenExpiresAt.isBefore(currentTime);
};
const columns: TableProps<APIKeyProps>['columns'] = [
{
title: 'API Key',

View File

@@ -1,5 +1,5 @@
import { PlusOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
import { Tooltip, Typography } from 'antd';
import getAll from 'api/channels/getAll';
import Spinner from 'components/Spinner';
import TextToolTip from 'components/TextToolTip';
@@ -52,11 +52,21 @@ function AlertChannels(): JSX.Element {
url="https://signoz.io/docs/userguide/alerts-management/#setting-notification-channel"
/>
{addNewChannelPermission && (
<Button onClick={onToggleHandler} icon={<PlusOutlined />}>
<Tooltip
title={
!addNewChannelPermission
? 'Ask an admin to create alert channel'
: undefined
}
>
<Button
onClick={onToggleHandler}
icon={<PlusOutlined />}
disabled={!addNewChannelPermission}
>
{t('button_new_channel')}
</Button>
)}
</Tooltip>
</RightActionContainer>
</ButtonContainer>

View File

@@ -3,6 +3,7 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import './AppLayout.styles.scss';
import * as Sentry from '@sentry/react';
import { Flex } from 'antd';
import getLocalStorageKey from 'api/browser/localstorage/get';
import getDynamicConfigs from 'api/dynamicConfigs/getDynamicConfigs';
@@ -27,7 +28,6 @@ import {
useRef,
useState,
} from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { Helmet } from 'react-helmet-async';
import { useTranslation } from 'react-i18next';
import { useQueries } from 'react-query';
@@ -236,7 +236,8 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
pathname === ROUTES.GET_STARTED_APPLICATION_MONITORING ||
pathname === ROUTES.GET_STARTED_INFRASTRUCTURE_MONITORING ||
pathname === ROUTES.GET_STARTED_LOGS_MANAGEMENT ||
pathname === ROUTES.GET_STARTED_AWS_MONITORING;
pathname === ROUTES.GET_STARTED_AWS_MONITORING ||
pathname === ROUTES.GET_STARTED_AZURE_MONITORING;
const [showTrialExpiryBanner, setShowTrialExpiryBanner] = useState(false);
@@ -342,7 +343,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
/>
)}
<div className={cx('app-content', collapsed ? 'collapsed' : '')}>
<ErrorBoundary FallbackComponent={ErrorBoundaryFallback}>
<Sentry.ErrorBoundary fallback={<ErrorBoundaryFallback />}>
<LayoutContent>
<ChildrenContainer
style={{
@@ -360,7 +361,7 @@ function AppLayout(props: AppLayoutProps): JSX.Element {
{children}
</ChildrenContainer>
</LayoutContent>
</ErrorBoundary>
</Sentry.ErrorBoundary>
</div>
</Flex>
</Layout>

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-loop-func */
import './BillingContainer.styles.scss';
import { CheckCircleOutlined } from '@ant-design/icons';
import { CheckCircleOutlined, CloudDownloadOutlined } from '@ant-design/icons';
import { Color } from '@signozhq/design-tokens';
import {
Alert,
@@ -40,6 +40,7 @@ import { isCloudUser } from 'utils/app';
import { getFormattedDate, getRemainingDays } from 'utils/timeUtils';
import { BillingUsageGraph } from './BillingUsageGraph/BillingUsageGraph';
import { prepareCsvData } from './BillingUsageGraph/utils';
interface DataType {
key: string;
@@ -371,6 +372,37 @@ export default function BillingContainer(): JSX.Element {
</Typography>
);
const handleCsvDownload = useCallback((): void => {
try {
const csv = prepareCsvData(apiResponse);
if (!csv.csvData || !csv.fileName) {
throw new Error('Invalid CSV data or file name.');
}
const csvBlob = new Blob([csv.csvData], { type: 'text/csv;charset=utf-8;' });
const csvUrl = URL.createObjectURL(csvBlob);
const downloadLink = document.createElement('a');
downloadLink.href = csvUrl;
downloadLink.download = csv.fileName;
document.body.appendChild(downloadLink); // Required for Firefox
downloadLink.click();
// Clean up
downloadLink.remove();
URL.revokeObjectURL(csvUrl); // Release the memory associated with the object URL
notifications.success({
message: 'Download successful',
});
} catch (error) {
console.error('Error downloading the CSV file:', error);
notifications.error({
message: SOMETHING_WENT_WRONG,
});
}
}, [apiResponse, notifications]);
return (
<div className="billing-container">
<Flex vertical style={{ marginBottom: 16 }}>
@@ -399,17 +431,29 @@ export default function BillingContainer(): JSX.Element {
</Typography.Text>
) : null}
</Flex>
<Button
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading}
onClick={handleBilling}
>
{isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription
? t('upgrade_plan')
: t('manage_billing')}
</Button>
<Flex gap={20}>
<Button
type="dashed"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading || isFetchingBillingData}
onClick={handleCsvDownload}
icon={<CloudDownloadOutlined />}
>
Download CSV
</Button>
<Button
type="primary"
size="middle"
loading={isLoadingBilling || isLoadingManageBilling}
disabled={isLoading}
onClick={handleBilling}
>
{isFreeTrial && !licensesData?.payload?.trialConvertedToSubscription
? t('upgrade_plan')
: t('manage_billing')}
</Button>
</Flex>
</Flex>
{licensesData?.payload?.onTrial &&

View File

@@ -135,7 +135,7 @@ export function BillingUsageGraph(props: BillingUsageGraphProps): JSX.Element {
},
y: {
...getYAxisScale({
series: graphCompatibleData?.data.newResult.data.result,
series: graphCompatibleData?.data?.newResult?.data?.result,
yAxisUnit: '',
softMax: null,
softMin: null,
@@ -159,11 +159,15 @@ export function BillingUsageGraph(props: BillingUsageGraphProps): JSX.Element {
},
padding: [32, 32, 16, 16],
plugins: [
tooltipPlugin(
fillMissingValuesForQuantities(graphCompatibleData, chartData[0]),
'',
true,
),
tooltipPlugin({
apiResponse: fillMissingValuesForQuantities(
graphCompatibleData,
chartData[0],
),
yAxisUnit: '',
isBillingUsageGraphs: true,
isDarkMode,
}),
],
}),
[

View File

@@ -0,0 +1,129 @@
import dayjs from 'dayjs';
export interface QuantityData {
metric: string;
values: [number, number][];
queryName: string;
legend: string;
quantity: number[];
unit: string;
}
interface DataPoint {
date: string;
metric: {
total: number;
cost: number;
};
trace: {
total: number;
cost: number;
};
log: {
total: number;
cost: number;
};
}
interface CsvData {
Date: string;
'Metrics Vol (Mn samples)': number;
'Metrics Cost ($)': number;
'Traces Vol (GBs)': number;
'Traces Cost ($)': number;
'Logs Vol (GBs)': number;
'Logs Cost ($)': number;
}
const formatDate = (timestamp: number): string =>
dayjs.unix(timestamp).format('MM/DD/YYYY');
const getQuantityData = (
data: QuantityData[],
metricName: string,
): QuantityData => {
const defaultData: QuantityData = {
metric: metricName,
values: [],
queryName: metricName,
legend: metricName,
quantity: [],
unit: '',
};
return data.find((d) => d.metric === metricName) || defaultData;
};
const generateCsvData = (quantityData: QuantityData[]): any[] => {
const convertData = (data: QuantityData[]): DataPoint[] => {
const metricsData = getQuantityData(data, 'Metrics');
const tracesData = getQuantityData(data, 'Traces');
const logsData = getQuantityData(data, 'Logs');
const timestamps = metricsData.values.map((value) => value[0]);
return timestamps.map((timestamp, index) => {
const date = formatDate(timestamp);
return {
date,
metric: {
total: metricsData.quantity[index] ?? 0,
cost: metricsData.values[index]?.[1] ?? 0,
},
trace: {
total: tracesData.quantity[index] ?? 0,
cost: tracesData.values[index]?.[1] ?? 0,
},
log: {
total: logsData.quantity[index] ?? 0,
cost: logsData.values[index]?.[1] ?? 0,
},
};
});
};
const formattedData = convertData(quantityData);
// Calculate totals
const totals = formattedData.reduce(
(acc, dataPoint) => {
acc.metric.total += dataPoint.metric.total;
acc.metric.cost += dataPoint.metric.cost;
acc.trace.total += dataPoint.trace.total;
acc.trace.cost += dataPoint.trace.cost;
acc.log.total += dataPoint.log.total;
acc.log.cost += dataPoint.log.cost;
return acc;
},
{
metric: { total: 0, cost: 0 },
trace: { total: 0, cost: 0 },
log: { total: 0, cost: 0 },
},
);
const csvData: CsvData[] = formattedData.map((dataPoint) => ({
Date: dataPoint.date,
'Metrics Vol (Mn samples)': parseFloat(dataPoint.metric.total.toFixed(2)),
'Metrics Cost ($)': parseFloat(dataPoint.metric.cost.toFixed(2)),
'Traces Vol (GBs)': parseFloat(dataPoint.trace.total.toFixed(2)),
'Traces Cost ($)': parseFloat(dataPoint.trace.cost.toFixed(2)),
'Logs Vol (GBs)': parseFloat(dataPoint.log.total.toFixed(2)),
'Logs Cost ($)': parseFloat(dataPoint.log.cost.toFixed(2)),
}));
// Add totals row
csvData.push({
Date: 'Total',
'Metrics Vol (Mn samples)': parseFloat(totals.metric.total.toFixed(2)),
'Metrics Cost ($)': parseFloat(totals.metric.cost.toFixed(2)),
'Traces Vol (GBs)': parseFloat(totals.trace.total.toFixed(2)),
'Traces Cost ($)': parseFloat(totals.trace.cost.toFixed(2)),
'Logs Vol (GBs)': parseFloat(totals.log.total.toFixed(2)),
'Logs Cost ($)': parseFloat(totals.log.cost.toFixed(2)),
});
return csvData;
};
export default generateCsvData;

View File

@@ -1,6 +1,12 @@
import { UsageResponsePayloadProps } from 'api/billing/getUsage';
import dayjs from 'dayjs';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { isEmpty, isNull } from 'lodash-es';
import { unparse } from 'papaparse';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import generateCsvData, { QuantityData } from './generateCsvData';
export const convertDataToMetricRangePayload = (
data: any,
): MetricRangePayloadProps => {
@@ -58,10 +64,7 @@ export const convertDataToMetricRangePayload = (
};
};
export function fillMissingValuesForQuantities(
data: any,
timestampArray: number[],
): MetricRangePayloadProps {
export function quantityDataArr(data: any, timestampArray: number[]): any[] {
const { result } = data.data;
const transformedResultArr: any[] = [];
@@ -76,6 +79,14 @@ export function fillMissingValuesForQuantities(
);
transformedResultArr.push({ ...item, quantity: quantityArray });
});
return transformedResultArr;
}
export function fillMissingValuesForQuantities(
data: any,
timestampArray: number[],
): MetricRangePayloadProps {
const transformedResultArr = quantityDataArr(data, timestampArray);
return {
data: {
@@ -85,3 +96,36 @@ export function fillMissingValuesForQuantities(
},
};
}
const formatDate = (timestamp: number): string =>
dayjs.unix(timestamp).format('MM/DD/YYYY');
export function csvFileName(csvData: QuantityData[]): string {
if (!csvData.length) {
return `billing-usage.csv`;
}
const { values } = csvData[0];
const timestamps = values.map((item) => item[0]);
const startDate = formatDate(Math.min(...timestamps));
const endDate = formatDate(Math.max(...timestamps));
return `billing_usage_(${startDate}-${endDate}).csv`;
}
export function prepareCsvData(
data: Partial<UsageResponsePayloadProps>,
): {
csvData: string;
fileName: string;
} {
const graphCompatibleData = convertDataToMetricRangePayload(data);
const chartData = getUPlotChartData(graphCompatibleData);
const quantityMapArr = quantityDataArr(graphCompatibleData, chartData[0]);
return {
csvData: unparse(generateCsvData(quantityMapArr)),
fileName: csvFileName(quantityMapArr),
};
}

View File

@@ -12,6 +12,30 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
const optionList = getOptionList(t);
function handleRedirection(option: AlertTypes): void {
let url = '';
switch (option) {
case AlertTypes.METRICS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
break;
case AlertTypes.LOGS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
break;
case AlertTypes.TRACES_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
break;
case AlertTypes.EXCEPTIONS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-source-selection-page#examples';
break;
default:
break;
}
window.open(url, '_blank');
}
const renderOptions = useMemo(
() => (
<>
@@ -23,7 +47,16 @@ function SelectAlertType({ onSelect }: SelectAlertTypeProps): JSX.Element {
onSelect(option.selection);
}}
>
{option.description}
{option.description}{' '}
<Typography.Link
onClick={(e): void => {
e.preventDefault();
e.stopPropagation();
handleRedirection(option.selection);
}}
>
Click here to see how to create a sample alert.
</Typography.Link>{' '}
</AlertTypeCard>
))}
</>

View File

@@ -130,7 +130,7 @@ export const exceptionAlertDefaults: AlertDef = {
disabled: false,
},
},
queryType: EQueryType.QUERY_BUILDER,
queryType: EQueryType.CLICKHOUSE,
panelType: PANEL_TYPES.TIME_SERIES,
unit: undefined,
},

View File

@@ -1,18 +1,27 @@
.explorer-options-container {
position: fixed;
bottom: 24px;
left: calc(50% + 240px);
transform: translate(calc(-50% - 120px), 0);
transition: left 0.2s linear;
display: flex;
gap: 16px;
background-color: transparent;
}
.hide-update {
left: calc(50% - 72px) !important;
}
.explorer-update {
position: fixed;
bottom: 24px;
left: calc(50% - 352px);
display: flex;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 50px;
border: 1px solid var(--bg-slate-400);
background: rgba(22, 24, 29, 0.6);
box-shadow: 4px 4px 16px 4px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
.action-icon {
@@ -38,16 +47,10 @@
}
.explorer-options {
position: fixed;
bottom: 24px;
left: calc(50% + 240px);
padding: 10px 12px;
transform: translate(calc(-50% - 120px), 0);
transition: left 0.2s linear;
border: 1px solid var(--bg-slate-400);
border-radius: 50px;
background: rgba(22, 24, 29, 0.6);
box-shadow: 4px 4px 16px 4px rgba(0, 0, 0, 0.25);
backdrop-filter: blur(20px);
cursor: default;
@@ -124,7 +127,7 @@
.app-content {
&.collapsed {
.explorer-options {
.explorer-options-container {
left: calc(50% + 72px);
}
}

View File

@@ -289,7 +289,7 @@ function ExplorerOptions({
const isEditDeleteSupported = allowedRoles.includes(role as string);
return (
<>
<div className="explorer-options-container">
{isQueryUpdated && !isExplorerOptionHidden && (
<div
className={cx(
@@ -493,7 +493,7 @@ function ExplorerOptions({
onExport={onExport}
/>
</Modal>
</>
</div>
);
}

View File

@@ -1,7 +1,17 @@
import { Form, Select, Switch } from 'antd';
import { useEffect, useState } from 'react';
import './FormAlertRules.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Form, Select, Switch, Tooltip } from 'antd';
import getChannels from 'api/channels/getAll';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import useFetch from 'hooks/useFetch';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import { AlertDef, Labels } from 'types/api/alerts/def';
import AppReducer from 'types/reducer/app';
import { requireErrorMessage } from 'utils/form/requireErrorMessage';
import { popupContainer } from 'utils/selectPopupContainer';
@@ -31,6 +41,13 @@ function BasicInfo({
}: BasicInfoProps): JSX.Element {
const { t } = useTranslation('alerts');
const channels = useFetch(getChannels);
const { role } = useSelector<AppState, AppReducer>((state) => state.app);
const [addNewChannelPermission] = useComponentPermission(
['add_new_channel'],
role,
);
const [
shouldBroadCastToAllChannels,
setShouldBroadCastToAllChannels,
@@ -54,6 +71,11 @@ function BasicInfo({
});
};
const noChannels = channels.payload?.length === 0;
const handleCreateNewChannels = useCallback(() => {
window.open(ROUTES.CHANNELS_NEW, '_blank');
}, []);
return (
<>
<StepHeading> {t('alert_form_step3')} </StepHeading>
@@ -137,32 +159,74 @@ function BasicInfo({
name="alert_all_configured_channels"
label="Alert all the configured channels"
>
<Switch
checked={shouldBroadCastToAllChannels}
onChange={handleBroadcastToAllChannels}
/>
<Tooltip
title={
noChannels
? 'No channels. Ask an admin to create a notification channel'
: undefined
}
placement="right"
>
<Switch
checked={shouldBroadCastToAllChannels}
onChange={handleBroadcastToAllChannels}
disabled={noChannels || !!channels.loading}
/>
</Tooltip>
</FormItemMedium>
{!shouldBroadCastToAllChannels && (
<FormItemMedium
label="Notification Channels"
name="notification_channels"
required
rules={[
{ required: true, message: requireErrorMessage(t('field_alert_name')) },
]}
<Tooltip
title={
noChannels
? 'No channels. Ask an admin to create a notification channel'
: undefined
}
placement="right"
>
<ChannelSelect
disabled={shouldBroadCastToAllChannels}
currentValue={alertDef.preferredChannels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
...alertDef,
preferredChannels,
});
}}
/>
</FormItemMedium>
<FormItemMedium
label="Notification Channels"
name="notification_channels"
required
rules={[
{ required: true, message: requireErrorMessage(t('field_alert_name')) },
]}
>
<ChannelSelect
disabled={
shouldBroadCastToAllChannels || noChannels || !!channels.loading
}
currentValue={alertDef.preferredChannels}
channels={channels}
onSelectChannels={(preferredChannels): void => {
setAlertDef({
...alertDef,
preferredChannels,
});
}}
/>
</FormItemMedium>
</Tooltip>
)}
{noChannels && (
<Tooltip
title={
!addNewChannelPermission
? 'Ask an admin to create a notification channel'
: undefined
}
placement="right"
>
<Button
onClick={handleCreateNewChannels}
icon={<PlusOutlined />}
className="create-notification-btn"
disabled={!addNewChannelPermission}
>
Create a notification channel
</Button>
</Tooltip>
)}
</FormContainer>
</>

View File

@@ -1,9 +1,9 @@
import { Select } from 'antd';
import getChannels from 'api/channels/getAll';
import useFetch from 'hooks/useFetch';
import { State } from 'hooks/useFetch';
import { useNotifications } from 'hooks/useNotifications';
import { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import { PayloadProps } from 'types/api/channels/getAll';
import { StyledSelect } from './styles';
@@ -11,38 +11,42 @@ export interface ChannelSelectProps {
disabled?: boolean;
currentValue?: string[];
onSelectChannels: (s: string[]) => void;
channels: State<PayloadProps | undefined>;
}
function ChannelSelect({
disabled,
currentValue,
onSelectChannels,
channels,
}: ChannelSelectProps): JSX.Element | null {
// init namespace for translations
const { t } = useTranslation('alerts');
const { loading, payload, error, errorMessage } = useFetch(getChannels);
const { notifications } = useNotifications();
const handleChange = (value: string[]): void => {
onSelectChannels(value);
};
if (error && errorMessage !== '') {
if (channels.error && channels.errorMessage !== '') {
notifications.error({
message: 'Error',
description: errorMessage,
description: channels.errorMessage,
});
}
const renderOptions = (): ReactNode[] => {
const children: ReactNode[] = [];
if (loading || payload === undefined || payload.length === 0) {
if (
channels.loading ||
channels.payload === undefined ||
channels.payload.length === 0
) {
return children;
}
payload.forEach((o) => {
channels.payload.forEach((o) => {
children.push(
<Select.Option key={o.id} value={o.name}>
{o.name}
@@ -55,7 +59,7 @@ function ChannelSelect({
return (
<StyledSelect
disabled={disabled}
status={error ? 'error' : ''}
status={channels.error ? 'error' : ''}
mode="multiple"
style={{ width: '100%' }}
placeholder={t('placeholder_channel_select')}

View File

@@ -262,7 +262,9 @@ function ChartPreview({
panelType={graphType}
data={chartData}
name={name || 'Chart Preview'}
panelData={queryResponse.data?.payload.data.newResult.data.result || []}
panelData={
queryResponse.data?.payload?.data?.newResult?.data?.result || []
}
query={query || initialQueriesMap.metrics}
yAxisUnit={yAxisUnit}
/>

View File

@@ -21,7 +21,28 @@
}
}
.info-help-btns {
display: grid;
grid-template-columns: auto auto;
gap: 12px;
margin-top: 20px;
.doc-redirection-btn {
color: var(--bg-aqua-500) !important;
border-color: var(--bg-aqua-500) !important;
}
.facing-issue-btn {
width: 100% !important;
}
}
.lightMode {
.main-container {
.plot-tag {
background: var(--bg-vanilla-300);
}
}
.ant-modal-content {
background-color: var(--bg-vanilla-100);
.ant-modal-confirm-title {
@@ -42,9 +63,15 @@
}
}
}
.info-help-btns {
.doc-redirection-btn {
color: var(--bg-aqua-600) !important;
border-color: var(--bg-aqua-600) !important;
}
}
}
.facing-issue-btn {
margin-top: 20px;
width: 100%;
.create-notification-btn {
box-shadow: none;
}

View File

@@ -2,6 +2,7 @@ import './FormAlertRules.styles.scss';
import { ExclamationCircleOutlined, SaveOutlined } from '@ant-design/icons';
import {
Button,
Col,
FormInstance,
Modal,
@@ -22,13 +23,13 @@ import PlotTag from 'container/NewWidget/LeftContainer/WidgetGraph/PlotTag';
import { BuilderUnitsFilter } from 'container/QueryBuilder/filters';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useShareBuilderUrl } from 'hooks/queryBuilder/useShareBuilderUrl';
import { updateStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { MESSAGE, useIsFeatureDisabled } from 'hooks/useFeatureFlag';
import { useNotifications } from 'hooks/useNotifications';
import useUrlQuery from 'hooks/useUrlQuery';
import history from 'lib/history';
import { mapQueryDataFromApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataFromApi';
import { mapQueryDataToApi } from 'lib/newQueryBuilder/queryBuilderMappers/mapQueryDataToApi';
import { isEqual } from 'lodash-es';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';
@@ -69,14 +70,15 @@ function FormAlertRules({
// init namespace for translations
const { t } = useTranslation('alerts');
const { minTime, maxTime, selectedTime: globalSelectedInterval } = useSelector<
const { selectedTime: globalSelectedInterval } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const urlQuery = useUrlQuery();
const panelType = urlQuery.get(QueryParams.panelTypes) as PANEL_TYPES | null;
// In case of alert the panel types should always be "Graph" only
const panelType = PANEL_TYPES.TIME_SERIES;
const {
currentQuery,
@@ -102,6 +104,13 @@ function FormAlertRules({
const [alertDef, setAlertDef] = useState<AlertDef>(initialValue);
const [yAxisUnit, setYAxisUnit] = useState<string>(currentQuery.unit || '');
useEffect(() => {
if (!isEqual(currentQuery.unit, yAxisUnit)) {
setYAxisUnit(currentQuery.unit || '');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentQuery.unit]);
// initQuery contains initial query when component was mounted
const initQuery = useMemo(() => initialValue.condition.compositeQuery, [
initialValue,
@@ -183,7 +192,9 @@ function FormAlertRules({
}
const query: Query = { ...currentQuery, queryType: val };
redirectWithQueryBuilderData(updateStepInterval(query, maxTime, minTime));
// update step interval is removed from here as if the user enters
// any value we will use that rather than auto update
redirectWithQueryBuilderData(query);
};
const { notifications } = useNotifications();
@@ -245,7 +256,7 @@ function FormAlertRules({
if (
!currentQuery.builder.queryData ||
currentQuery.builder.queryData.length === 0
currentQuery.builder.queryData?.length === 0
) {
notifications.error({
message: 'Error',
@@ -502,6 +513,31 @@ function FormAlertRules({
const isRuleCreated = !ruleId || ruleId === 0;
function handleRedirection(option: AlertTypes): void {
let url = '';
switch (option) {
case AlertTypes.METRICS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/metrics-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
break;
case AlertTypes.LOGS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/log-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
break;
case AlertTypes.TRACES_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/trace-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
break;
case AlertTypes.EXCEPTIONS_BASED_ALERT:
url =
'https://signoz.io/docs/alerts-management/exceptions-based-alerts/?utm_source=product&utm_medium=alert-creation-page#examples';
break;
default:
break;
}
window.open(url, '_blank');
}
return (
<>
{Element}
@@ -512,6 +548,7 @@ function FormAlertRules({
initialValues={initialValue}
layout="vertical"
form={formInstance}
className="main-container"
>
{currentQuery.queryType === EQueryType.QUERY_BUILDER &&
renderQBChartPreview()}
@@ -531,7 +568,7 @@ function FormAlertRules({
queryCategory={currentQuery.queryType}
setQueryCategory={onQueryCategoryChange}
alertType={alertType || AlertTypes.METRICS_BASED_ALERT}
runQuery={handleRunQuery}
runQuery={(): void => handleRunQuery(true)}
alertDef={alertDef}
panelType={panelType || PANEL_TYPES.TIME_SERIES}
key={currentQuery.queryType}
@@ -584,22 +621,33 @@ function FormAlertRules({
</StyledLeftContainer>
<Col flex="1 1 300px">
<UserGuide queryType={currentQuery.queryType} />
<FacingIssueBtn
attributes={{
alert: alertDef?.alert,
alertType: alertDef?.alertType,
id: ruleId,
ruleType: alertDef?.ruleType,
state: (alertDef as any)?.state,
panelType,
screen: isRuleCreated ? 'Edit Alert' : 'New Alert',
}}
className="facing-issue-btn"
eventName="Alert: Facing Issues in alert"
buttonText="Need help with this alert?"
message={alertHelpMessage(alertDef, ruleId)}
onHoverText="Click here to get help with this alert"
/>
<div className="info-help-btns">
<Button
style={{ height: 32 }}
onClick={(): void =>
handleRedirection(alertDef?.alertType as AlertTypes)
}
className="doc-redirection-btn"
>
Check an example alert
</Button>
<FacingIssueBtn
attributes={{
alert: alertDef?.alert,
alertType: alertDef?.alertType,
id: ruleId,
ruleType: alertDef?.ruleType,
state: (alertDef as any)?.state,
panelType,
screen: isRuleCreated ? 'Edit Alert' : 'New Alert',
}}
className="facing-issue-btn"
eventName="Alert: Facing Issues in alert"
buttonText="Need help with this alert?"
message={alertHelpMessage(alertDef, ruleId)}
onHoverText="Click here to get help with this alert"
/>
</div>
</Col>
</PanelContainer>
</>

View File

@@ -17,6 +17,10 @@
border-radius: 3px;
}
.height-widget {
height: calc(100% - 40px);
}
.list-graph-container {
height: calc(100% - 40px);
overflow-y: auto;

View File

@@ -27,5 +27,6 @@ export const PANEL_TYPES_VS_FULL_VIEW_TABLE: PanelTypeAndGraphManagerVisibilityP
TRACE: false,
BAR: true,
PIE: false,
HISTOGRAM: false,
EMPTY_WIDGET: false,
};

View File

@@ -15,7 +15,6 @@ import {
} from 'container/NewWidget/RightContainer/timeItems';
import PanelWrapper from 'container/PanelWrapper/PanelWrapper';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useChartMutable } from 'hooks/useChartMutable';
import useUrlQuery from 'hooks/useUrlQuery';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
@@ -71,7 +70,7 @@ function FullView({
enum: widget?.timePreferance || 'GLOBAL_TIME',
});
const updatedQuery = useStepInterval(widget?.query);
const updatedQuery = widget?.query;
const [requestData, setRequestData] = useState<GetQueryResultsProps>(() => {
if (widget.panelTypes !== PANEL_TYPES.LIST) {
@@ -204,6 +203,7 @@ function FullView({
<div
className={cx('graph-container', {
disabled: isDashboardLocked,
'height-widget': widget?.mergeAllActiveQueries || widget?.stackedBarChart,
'list-graph-container': isListView,
})}
ref={fullViewRef}

View File

@@ -131,15 +131,22 @@ function WidgetGraphComponent({
const uuid = v4();
// this is added to make sure the cloned panel is of the same dimensions as the original one
const originalPanelLayout = selectedDashboard.data.layout?.find(
(l) => l.i === widget.id,
);
// added the cloned panel on the top as it is given most priority when arranging
// in the layout. React_grid_layout assigns priority from top, hence no random position for cloned panel
const layout = [
...(selectedDashboard.data.layout || []),
{
i: uuid,
w: 6,
w: originalPanelLayout?.w || 6,
x: 0,
h: 6,
h: originalPanelLayout?.h || 6,
y: 0,
},
...(selectedDashboard.data.layout || []),
];
updateDashboardMutation.mutateAsync(
@@ -215,7 +222,7 @@ function WidgetGraphComponent({
const {
graphVisibilityStates: localStoredVisibilityState,
} = getLocalStorageGraphVisibilityState({
apiResponse: queryResponse.data.payload.data.result,
apiResponse: queryResponse.data?.payload?.data?.result,
name: widget.id,
});
setGraphVisibility(localStoredVisibilityState);

View File

@@ -3,7 +3,6 @@ import { QueryParams } from 'constants/query';
import { PANEL_TYPES } from 'constants/queryBuilder';
import { CustomTimeType } from 'container/TopNav/DateTimeSelectionV2/config';
import { useGetQueryRange } from 'hooks/queryBuilder/useGetQueryRange';
import { useStepInterval } from 'hooks/queryBuilder/useStepInterval';
import { useIntersectionObserver } from 'hooks/useIntersectionObserver';
import { getDashboardVariables } from 'lib/dashbaordVariables/getDashboardVariables';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
@@ -90,7 +89,7 @@ function GridCardGraph({
}
}, [toScrollWidgetId, setToScrollWidgetId, widget.id]);
const updatedQuery = useStepInterval(widget?.query);
const updatedQuery = widget?.query;
const isEmptyWidget =
widget?.id === PANEL_TYPES.EMPTY_WIDGET || isEmpty(widget);
@@ -109,6 +108,7 @@ function GridCardGraph({
query: updatedQuery,
globalSelectedInterval,
variables: getDashboardVariables(variables),
fillGaps: widget.fillSpans,
};
}
updatedQuery.builder.queryData[0].pageSize = 10;
@@ -123,6 +123,7 @@ function GridCardGraph({
limit: updatedQuery.builder.queryData[0].limit || 0,
},
},
fillGaps: widget.fillSpans,
};
});
@@ -153,6 +154,7 @@ function GridCardGraph({
widget?.query,
widget?.panelTypes,
widget.timePreferance,
widget.fillSpans,
requestData,
],
retry(failureCount, error): boolean {

View File

@@ -49,6 +49,7 @@ const GridPanelSwitch = forwardRef<
options,
ref,
},
[PANEL_TYPES.HISTOGRAM]: null,
[PANEL_TYPES.EMPTY_WIDGET]: null,
};

View File

@@ -43,5 +43,6 @@ export type PropsTypePropsMap = {
[PANEL_TYPES.BAR]: UplotProps & {
ref: ForwardedRef<ToggleGraphProps | undefined>;
};
[PANEL_TYPES.HISTOGRAM]: null;
[PANEL_TYPES.EMPTY_WIDGET]: null;
};

View File

@@ -1,12 +1,13 @@
import { ExclamationCircleFilled } from '@ant-design/icons';
import { Space, Tooltip } from 'antd';
import { getYAxisFormattedValue } from 'components/Graph/yAxisConfig';
import { Events } from 'constants/events';
import { QueryTable } from 'container/QueryTable';
import {
createTableColumnsFromQuery,
RowData,
} from 'lib/query/createTableColumnsFromQuery';
import { get, set } from 'lodash-es';
import { cloneDeep, get, isEmpty, set } from 'lodash-es';
import { memo, ReactNode, useCallback, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { eventEmitter } from 'utils/getEventEmitter';
@@ -19,11 +20,12 @@ function GridTableComponent({
data,
query,
thresholds,
columnUnits,
tableProcessedDataRef,
...props
}: GridTableComponentProps): JSX.Element {
const { t } = useTranslation(['valueGraph']);
const { columns, dataSource } = useMemo(
const { columns, dataSource: originalDataSource } = useMemo(
() =>
createTableColumnsFromQuery({
query,
@@ -31,7 +33,6 @@ function GridTableComponent({
}),
[data, query],
);
const createDataInCorrectFormat = useCallback(
(dataSource: RowData[]): RowData[] =>
dataSource.map((d) => {
@@ -52,6 +53,35 @@ function GridTableComponent({
[columns],
);
const applyColumnUnits = useCallback(
(dataSource: RowData[]): RowData[] => {
let mutateDataSource = cloneDeep(dataSource);
if (isEmpty(columnUnits)) {
return mutateDataSource;
}
mutateDataSource = mutateDataSource.map(
(val): RowData => {
const newValue = { ...val };
Object.keys(val).forEach((k) => {
if (columnUnits[k]) {
newValue[k] = getYAxisFormattedValue(String(val[k]), columnUnits[k]);
newValue[`${k}_without_unit`] = val[k];
}
});
return newValue;
},
);
return mutateDataSource;
},
[columnUnits],
);
const dataSource = useMemo(() => applyColumnUnits(originalDataSource), [
applyColumnUnits,
originalDataSource,
]);
useEffect(() => {
if (tableProcessedDataRef) {
// eslint-disable-next-line no-param-reassign

View File

@@ -5,11 +5,13 @@ import {
ThresholdProps,
} from 'container/NewWidget/RightContainer/Threshold/types';
import { RowData } from 'lib/query/createTableColumnsFromQuery';
import { ColumnUnit } from 'types/api/dashboard/getAll';
import { Query } from 'types/api/queryBuilder/queryBuilderData';
export type GridTableComponentProps = {
query: Query;
thresholds?: ThresholdProps[];
columnUnits?: ColumnUnit;
tableProcessedDataRef?: React.MutableRefObject<RowData[]>;
} & Pick<LogsExplorerTableProps, 'data'> &
Omit<TableProps<RowData>, 'columns' | 'dataSource'>;

View File

@@ -1,3 +1,942 @@
.ingestion-settings-container {
color: white;
}
.ingestion-key-container {
margin-top: 24px;
display: flex;
justify-content: center;
width: 100%;
.ingestion-key-content {
width: calc(100% - 30px);
max-width: 736px;
.title {
color: var(--bg-vanilla-100);
font-size: var(--font-size-lg);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 28px;
/* 155.556% */
letter-spacing: -0.09px;
}
.subtitle {
color: var(--bg-vanilla-400);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 20px;
/* 142.857% */
letter-spacing: -0.07px;
}
.ingestion-keys-search-add-new {
display: flex;
align-items: center;
gap: 12px;
padding: 16px 0;
.add-new-ingestion-key-btn {
display: flex;
align-items: center;
gap: 8px;
}
}
.ant-table-row {
.ant-table-cell {
padding: 0;
border: none;
background: var(--bg-ink-500);
}
.column-render {
margin: 8px 0 !important;
border-radius: 6px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
.title-with-action {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px;
.ingestion-key-data {
display: flex;
gap: 8px;
align-items: center;
.ingestion-key-title {
display: flex;
align-items: center;
gap: 6px;
.ant-typography {
color: var(--bg-vanilla-400);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: 20px;
letter-spacing: -0.07px;
}
}
.ingestion-key-value {
display: flex;
align-items: center;
gap: 12px;
border-radius: 20px;
padding: 0px 12px;
background: var(--bg-ink-200);
.ant-typography {
color: var(--bg-vanilla-400);
font-size: var(--font-size-xs);
font-family: 'Space Mono', monospace;
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: 20px;
letter-spacing: -0.07px;
}
.copy-key-btn {
cursor: pointer;
}
}
}
.action-btn {
display: flex;
align-items: center;
gap: 4px;
cursor: pointer;
}
.visibility-btn {
border: 1px solid rgba(113, 144, 249, 0.2);
background: rgba(113, 144, 249, 0.1);
}
}
.ant-collapse {
border: none;
.ant-collapse-header {
padding: 0px 8px;
display: flex;
align-items: center;
background-color: #121317;
}
.ant-collapse-content {
border-top: 1px solid var(--bg-slate-500);
}
.ant-collapse-item {
border-bottom: none;
}
.ant-collapse-expand-icon {
padding-inline-end: 0px;
}
}
.ingestion-key-details {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
border-top: 1px solid var(--bg-slate-500);
padding: 8px;
.ingestion-key-tag {
width: 14px;
height: 14px;
border-radius: 50px;
background: var(--bg-slate-300);
display: flex;
justify-content: center;
align-items: center;
.tag-text {
color: var(--bg-vanilla-400);
leading-trim: both;
text-edge: cap;
font-size: 10px;
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: normal;
letter-spacing: -0.05px;
}
}
.ingestion-key-created-by {
margin-left: 8px;
}
.ingestion-key-last-used-at {
display: flex;
align-items: center;
gap: 8px;
.ant-typography {
color: var(--bg-vanilla-400);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 18px;
/* 128.571% */
letter-spacing: -0.07px;
font-variant-numeric: lining-nums tabular-nums stacked-fractions
slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'cpsp' on, 'case' on;
}
}
.ingestion-key-expires-in {
font-style: normal;
font-weight: 400;
line-height: 18px;
display: flex;
align-items: center;
gap: 8px;
.dot {
height: 6px;
width: 6px;
border-radius: 50%;
}
&.warning {
color: var(--bg-amber-400);
.dot {
background: var(--bg-amber-400);
box-shadow: 0px 0px 6px 0px var(--bg-amber-400);
}
}
&.danger {
color: var(--bg-cherry-400);
.dot {
background: var(--bg-cherry-400);
box-shadow: 0px 0px 6px 0px var(--bg-cherry-400);
}
}
}
}
}
}
.ant-pagination-item {
display: flex;
justify-content: center;
align-items: center;
> a {
color: var(--bg-vanilla-400);
font-variant-numeric: lining-nums tabular-nums slashed-zero;
font-feature-settings: 'dlig' on, 'salt' on, 'case' on, 'cpsp' on;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 20px;
/* 142.857% */
}
}
.ant-pagination-item-active {
background-color: var(--bg-robin-500);
> a {
color: var(--bg-ink-500) !important;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: 20px;
}
}
}
}
.ingestion-key-info-container {
display: flex;
gap: 12px;
flex-direction: column;
.user-info {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
.user-avatar {
background-color: lightslategray;
vertical-align: middle;
}
}
.user-email {
display: inline-flex;
align-items: center;
gap: 12px;
border-radius: 20px;
padding: 0px 12px;
background: var(--bg-ink-200);
font-family: 'Space Mono', monospace;
}
.role {
display: flex;
align-items: center;
gap: 12px;
}
.ingestion-key-tags-container {
display: flex;
align-items: center;
gap: 16px;
}
.limits-data {
padding: 16px;
border: 1px solid var(--bg-slate-500);
.signals {
.signal {
margin-bottom: 24px;
.header {
display: flex;
justify-content: space-between;
align-items: center;
.actions {
display: flex;
align-items: center;
gap: 4px;
}
}
.signal-name {
font-size: 12px;
font-weight: 500;
text-transform: uppercase;
color: var(--bg-robin-500);
}
.signal-limit-values {
display: flex;
gap: 16px;
margin-top: 8px;
margin-bottom: 16px;
.edit-ingestion-key-limit-form {
width: 100%;
}
.ant-form-item {
margin-bottom: 12px;
}
.daily-limit,
.second-limit {
flex: 1;
.heading {
.title {
font-size: 12px;
}
.subtitle {
font-size: 11px;
}
padding: 4px 0px;
}
.ant-input-number {
width: 80%;
}
}
.signal-limit-view-mode {
display: flex;
width: 100%;
justify-content: space-between;
gap: 16px;
.signal-limit-value {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
.limit-type {
display: flex;
align-items: center;
gap: 8px;
}
.limit-value {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
font-weight: 600;
}
}
}
.signal-limit-edit-mode {
display: flex;
justify-content: space-between;
gap: 16px;
}
}
.signal-limit-save-discard {
display: flex;
gap: 8px;
}
}
}
}
}
.ingestion-key-modal {
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0;
.ant-modal-header {
background: none;
border-bottom: 1px solid var(--bg-slate-500);
padding: 16px;
}
.ant-modal-close-x {
font-size: 12px;
}
.ant-modal-body {
padding: 12px 16px;
}
.ant-modal-footer {
padding: 16px;
margin-top: 0;
display: flex;
justify-content: flex-end;
}
}
}
.ingestion-key-access-role {
display: flex;
.ant-radio-button-wrapper {
font-size: 12px;
text-transform: capitalize;
&.ant-radio-button-wrapper-checked {
color: #fff;
background: var(--bg-slate-400, #1d212d);
border-color: var(--bg-slate-400, #1d212d);
&:hover {
color: #fff;
background: var(--bg-slate-400, #1d212d);
border-color: var(--bg-slate-400, #1d212d);
&::before {
background-color: var(--bg-slate-400, #1d212d);
}
}
&:focus {
color: #fff;
background: var(--bg-slate-400, #1d212d);
border-color: var(--bg-slate-400, #1d212d);
}
}
}
.tab {
border: 1px solid var(--bg-slate-400);
flex: 1;
display: flex;
justify-content: center;
&::before {
background: var(--bg-slate-400);
}
&.selected {
background: var(--bg-slate-400, #1d212d);
}
}
.role {
display: flex;
align-items: center;
gap: 8px;
}
}
.delete-ingestion-key-modal {
width: calc(100% - 30px) !important;
/* Adjust the 20px as needed */
max-width: 384px;
.ant-modal-content {
padding: 0;
border-radius: 4px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
.ant-modal-header {
padding: 16px;
background: var(--bg-ink-400);
}
.ant-modal-body {
padding: 0px 16px 28px 16px;
.ant-typography {
color: var(--bg-vanilla-400);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 20px;
letter-spacing: -0.07px;
}
.ingestion-key-input {
margin-top: 8px;
display: flex;
gap: 8px;
}
.ant-color-picker-trigger {
padding: 6px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
width: 32px;
height: 32px;
.ant-color-picker-color-block {
border-radius: 50px;
width: 16px;
height: 16px;
flex-shrink: 0;
.ant-color-picker-color-block-inner {
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
.ant-modal-footer {
display: flex;
justify-content: flex-end;
padding: 16px 16px;
margin: 0;
.cancel-btn {
display: flex;
align-items: center;
border: none;
border-radius: 2px;
background: var(--bg-slate-500);
}
.delete-btn {
display: flex;
align-items: center;
border: none;
border-radius: 2px;
background: var(--bg-cherry-500);
margin-left: 12px;
}
.delete-btn:hover {
color: var(--bg-vanilla-100);
background: var(--bg-cherry-600);
}
}
}
.title {
color: var(--bg-vanilla-100);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-medium);
line-height: 20px;
/* 142.857% */
}
}
.expires-at {
.ant-picker {
border-color: var(--bg-slate-400) !important;
}
}
.expiration-selector {
.ant-select-selector {
border: 1px solid var(--bg-slate-400) !important;
}
}
.newAPIKeyDetails {
display: flex;
flex-direction: column;
gap: 8px;
}
.copyable-text {
display: inline-flex;
align-items: center;
gap: 12px;
border-radius: 20px;
padding: 0px 12px;
background: var(--bg-ink-200, #23262e);
.copy-key-btn {
cursor: pointer;
}
}
.ingestion-key-details-edit-drawer-title {
display: flex;
gap: 8px;
}
.ingestion-key-details-meta {
padding: 14px 16px;
border-radius: 3px;
border: 1px solid var(--Slate-500, #161922);
}
#edit-ingestion-key-form {
.ant-form-item:last-child {
margin-bottom: 0px;
}
}
.alert {
display: flex;
gap: 12px;
padding: 8px;
margin: 16px 0;
border-radius: 4px;
background: rgba(113, 144, 249, 0.1);
color: var(--Robin-300, #95acfb);
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 160%;
letter-spacing: 0.013px;
}
.error {
color: var(--bg-cherry-500);
margin-bottom: 8px;
}
.save-discard-changes {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.ingestion-details-edit-drawer {
.ant-drawer-header {
border-bottom: 1px solid var(--Slate-500, #161922);
padding: 8px;
}
.ant-drawer-footer {
border-top: 1px solid var(--Slate-500, #161922);
padding: 8px;
}
}
.ingestion-key-limits {
margin-top: 48px;
padding: 16px;
border-radius: 3px;
border: 1px solid var(--Slate-500, #161922);
.ant-tabs {
.ant-tabs-nav {
margin-top: -36px;
&::before {
border-bottom: none;
}
.ant-tabs-nav-list {
background: #121317;
border-radius: 2px;
border: 1px solid var(--Slate-400, #1d212d);
background: var(--Ink-400, #121317);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
.ant-tabs-tab {
display: inline-flex;
padding: 6px 36px;
justify-content: center;
align-items: center;
margin: 0px;
border-right: 1px solid #1d212d;
&.ant-tabs-tab-active {
border-bottom: 0px;
}
.tab-name {
display: flex;
align-items: center;
gap: 8px;
}
}
}
}
}
}
.ingestion-key-expires-at {
border-radius: 4px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.lightMode {
.ingestion-key-container {
.ingestion-key-content {
.title {
color: var(--bg-ink-500);
}
.ant-table-row {
.ant-table-cell {
background: var(--bg-vanilla-200);
}
&:hover {
.ant-table-cell {
background: var(--bg-vanilla-200) !important;
}
}
.column-render {
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-100);
.ant-collapse {
border: none;
.ant-collapse-header {
background: var(--bg-vanilla-100);
}
.ant-collapse-content {
border-top: 1px solid var(--bg-vanilla-300);
}
}
.title-with-action {
.ingestion-key-title {
.ant-typography {
color: var(--bg-ink-500);
}
}
.ingestion-key-value {
background: var(--bg-vanilla-200);
.ant-typography {
color: var(--bg-slate-400);
}
.copy-key-btn {
cursor: pointer;
}
}
.action-btn {
.ant-typography {
color: var(--bg-ink-500);
}
}
}
.ingestion-key-details {
border-top: 1px solid var(--bg-vanilla-200);
.ingestion-key-tag {
background: var(--bg-vanilla-200);
.tag-text {
color: var(--bg-ink-500);
}
}
.ingestion-key-created-by {
color: var(--bg-ink-500);
}
.ingestion-key-last-used-at {
.ant-typography {
color: var(--bg-ink-500);
}
}
}
}
}
}
}
.delete-ingestion-key-modal {
.ant-modal-content {
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-100);
.ant-modal-header {
background: var(--bg-vanilla-100);
.title {
color: var(--bg-ink-500);
}
}
.ant-modal-body {
.ant-typography {
color: var(--bg-ink-500);
}
.ingestion-key-input {
.ant-input {
background: var(--bg-vanilla-200);
color: var(--bg-ink-500);
}
}
}
.ant-modal-footer {
.cancel-btn {
background: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
}
}
}
.ingestion-key-info-container {
.user-email {
background: var(--bg-vanilla-200);
}
.limits-data {
border: 1px solid var(--bg-vanilla-300);
}
}
.ingestion-key-modal {
.ant-modal-content {
border-radius: 4px;
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-100);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
padding: 0;
.ant-modal-header {
background: none;
border-bottom: 1px solid var(--bg-vanilla-200);
padding: 16px;
}
}
}
.ingestion-key-access-role {
.ant-radio-button-wrapper {
&.ant-radio-button-wrapper-checked {
color: var(--bg-ink-400);
background: var(--bg-vanilla-300);
border-color: var(--bg-vanilla-300);
&:hover {
color: var(--bg-ink-400);
background: var(--bg-vanilla-300);
border-color: var(--bg-vanilla-300);
&::before {
background-color: var(--bg-vanilla-300);
}
}
&:focus {
color: var(--bg-ink-400);
background: var(--bg-vanilla-300);
border-color: var(--bg-vanilla-300);
}
}
}
.tab {
border: 1px solid var(--bg-vanilla-300);
&::before {
background: var(--bg-vanilla-300);
}
&.selected {
background: var(--bg-vanilla-300);
}
}
}
.copyable-text {
background: var(--bg-vanilla-200);
}
.ingestion-key-expires-at {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-200);
box-shadow: 0px -4px 16px 2px rgba(0, 0, 0, 0.2);
}
.expires-at .ant-picker {
border-color: var(--bg-vanilla-300) !important;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
import { ArrowRightOutlined } from '@ant-design/icons';
import { Typography } from 'antd';
interface AlertInfoCardProps {
header: string;
subheader: string;
link: string;
}
function AlertInfoCard({
header,
subheader,
link,
}: AlertInfoCardProps): JSX.Element {
return (
<div
className="alert-info-card"
onClick={(): void => {
window.open(link, '_blank');
}}
>
<div className="alert-card-text">
<Typography.Text className="alert-card-text-header">
{header}
</Typography.Text>
<Typography.Text className="alert-card-text-subheader">
{subheader}
</Typography.Text>
</div>
<ArrowRightOutlined />
</div>
);
}
export default AlertInfoCard;

View File

@@ -0,0 +1,251 @@
.alert-list-container {
margin-top: 104px;
margin-bottom: 30px;
display: flex;
justify-content: center;
width: 100%;
.alert-list-view-content {
width: calc(100% - 30px);
max-width: 836px;
.alert-list-title-container {
.title {
color: var(--bg-vanilla-100);
font-size: var(--font-size-lg);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 28px; /* 155.556% */
letter-spacing: -0.09px;
}
.subtitle {
color: var(--bg-vanilla-400);
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}
.empty-alert-info-container {
display: flex;
padding: 71px 193.5px;
justify-content: center;
align-items: center;
border-radius: 6px;
border: 1px dashed var(--bg-slate-500);
margin-top: 16px;
.alert-content {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
.heading {
display: flex;
flex-direction: column;
gap: 4px;
.icons {
color: white;
}
.empty-alert-action {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 171.429% */
letter-spacing: -0.07px;
}
.empty-info {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 24px;
letter-spacing: -0.07px;
}
}
.action-container {
display: flex;
gap: 24px;
align-items: center;
padding-top: 24px;
padding-bottom: 24px;
width: 100%;
}
}
}
.get-started-text {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin-top: 24px;
margin-bottom: 24px;
width: 100%;
.ant-divider::before,
.ant-divider::after {
border-bottom: 2px dotted var(--bg-slate-300);
border-top: 2px dotted var(--bg-slate-300);
height: 8px;
}
.ant-typography {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 166.667% */
letter-spacing: 0.48px;
text-transform: uppercase;
padding-top: 8px;
}
}
.alert-info-card {
display: flex;
padding: 16px;
justify-content: space-between;
align-items: center;
border-radius: 6px;
border: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
margin-bottom: 16px;
&:hover {
cursor: pointer;
}
.alert-card-text {
display: flex;
gap: 2px;
flex-direction: column;
.alert-card-text-header {
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.alert-card-text-subheader {
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 18px; /* 150% */
}
}
}
}
}
.info-text {
color: var(--bg-robin-400) !important;
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px; /* 133.333% */
letter-spacing: -0.06px;
}
.info-link-container {
.anticon {
color: var(--bg-robin-400);
}
:hover {
cursor: pointer;
}
}
.lightMode {
.alert-list-container {
.alert-list-view-content {
.alert-list-title-container {
.title {
color: var(--bg-slate-400);
}
.subtitle {
color: var(--bg-slate-100);
}
}
.empty-alert-info-container {
border: 1px dashed var(--bg-vanilla-400);
.alert-content {
.heading {
.icons {
color: white;
}
.empty-alert-action {
color: var(--bg-slate-100);
}
.empty-info {
color: var(--bg-slate-400);
}
}
}
}
.get-started-text {
.ant-divider::before,
.ant-divider::after {
border-bottom: 2px dotted var(--bg-vanilla-400);
border-top: 2px dotted var(--bg-vanilla-400);
}
.ant-typography {
color: var(--bg-slate-100);
}
}
.alert-info-card {
border: 1px solid var(--bg-vanilla-200);
background: var(--bg-vanilla-100);
.alert-card-text {
.alert-card-text-header {
color: var(--bg-slate-400);
}
.alert-card-text-subheader {
color: var(--bg-slate-100);
}
}
}
}
}
.info-text {
color: var(--bg-robin-600) !important;
}
.info-link-container {
.anticon {
color: var(--bg-robin-400);
}
}
}

View File

@@ -0,0 +1,127 @@
import './AlertsEmptyState.styles.scss';
import { PlusOutlined } from '@ant-design/icons';
import { Button, Divider, Typography } from 'antd';
import ROUTES from 'constants/routes';
import useComponentPermission from 'hooks/useComponentPermission';
import { useNotifications } from 'hooks/useNotifications';
import history from 'lib/history';
import { useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { AppState } from 'store/reducers';
import AppReducer from 'types/reducer/app';
import AlertInfoCard from './AlertInfoCard';
import { ALERT_CARDS, ALERT_INFO_LINKS } from './alertLinks';
import InfoLinkText from './InfoLinkText';
export function AlertsEmptyState(): JSX.Element {
const { t } = useTranslation('common');
const { role, featureResponse } = useSelector<AppState, AppReducer>(
(state) => state.app,
);
const [addNewAlert] = useComponentPermission(
['add_new_alert', 'action'],
role,
);
const { notifications: notificationsApi } = useNotifications();
const handleError = useCallback((): void => {
notificationsApi.error({
message: t('something_went_wrong'),
});
}, [notificationsApi, t]);
const [loading, setLoading] = useState(false);
const onClickNewAlertHandler = useCallback(() => {
setLoading(true);
featureResponse
.refetch()
.then(() => {
setLoading(false);
history.push(ROUTES.ALERTS_NEW);
})
.catch(handleError)
.finally(() => setLoading(false));
}, [featureResponse, handleError]);
return (
<div className="alert-list-container">
<div className="alert-list-view-content">
<div className="alert-list-title-container">
<Typography.Title className="title">Alert Rules</Typography.Title>
<Typography.Text className="subtitle">
Create and manage alert rules for your resources.
</Typography.Text>
</div>
<section className="empty-alert-info-container">
<div className="alert-content">
<section className="heading">
<img
src="/Icons/alert_emoji.svg"
alt="alert-header"
style={{ height: '32px', width: '32px' }}
/>
<div>
<Typography.Text className="empty-info">
No Alert rules yet.{' '}
</Typography.Text>
<Typography.Text className="empty-alert-action">
Create an Alert Rule to get started
</Typography.Text>
</div>
</section>
<div className="action-container">
<Button
className="add-alert-btn"
onClick={onClickNewAlertHandler}
icon={<PlusOutlined />}
disabled={!addNewAlert}
loading={loading}
type="primary"
data-testid="add-alert"
>
New Alert Rule
</Button>
<InfoLinkText
infoText="Watch a tutorial on creating a sample alert"
link="https://youtu.be/xjxNIqiv4_M"
leftIconVisible
rightIconVisible
/>
</div>
{ALERT_INFO_LINKS.map((info) => (
<InfoLinkText
key={info.link}
infoText={info.infoText}
link={info.link}
leftIconVisible={info.leftIconVisible}
rightIconVisible={info.rightIconVisible}
/>
))}
</div>
</section>
<div className="get-started-text">
<Divider>
<Typography.Text className="get-started-text">
Or get started with these sample alerts
</Typography.Text>
</Divider>
</div>
{ALERT_CARDS.map((card) => (
<AlertInfoCard
key={card.link}
header={card.header}
subheader={card.subheader}
link={card.link}
/>
))}
</div>
</div>
);
}

View File

@@ -0,0 +1,31 @@
import { ArrowRightOutlined, PlayCircleFilled } from '@ant-design/icons';
import { Flex, Typography } from 'antd';
interface InfoLinkTextProps {
infoText: string;
link: string;
leftIconVisible: boolean;
rightIconVisible: boolean;
}
function InfoLinkText({
infoText,
link,
leftIconVisible,
rightIconVisible,
}: InfoLinkTextProps): JSX.Element {
return (
<Flex
onClick={(): void => {
window.open(link, '_blank');
}}
className="info-link-container"
>
{leftIconVisible && <PlayCircleFilled />}
<Typography.Text className="info-text">{infoText}</Typography.Text>
{rightIconVisible && <ArrowRightOutlined rotate={315} />}
</Flex>
);
}
export default InfoLinkText;

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