Compare commits

..

160 Commits

Author SHA1 Message Date
aks07
a2d853daf5 Merge branch 'main' of github.com:SigNoz/signoz into feat/filter-search 2026-04-17 10:23:53 +05:30
aks07
3970619afa Merge remote-tracking branch 'origin/ns/waterfall-v3-2' into feat/filter-search 2026-04-17 10:11:31 +05:30
aks07
91f74144cb feat: old trace btn added 2026-04-16 20:19:27 +05:30
aks07
0863c5170b feat: no data screen 2026-04-16 18:14:18 +05:30
aks07
837cd2a463 feat: fix color duplications 2026-04-16 16:46:37 +05:30
Nikhil Soni
c9abc2cb30 chore: use sqlbuilder in clickhouse store where possible 2026-04-16 09:39:36 +05:30
Nikhil Soni
01824b0b62 chore: remove unused duration nano field 2026-04-16 09:39:36 +05:30
Nikhil Soni
d1b378992d refactor: use same method for cache key creation 2026-04-16 09:39:36 +05:30
Nikhil Soni
52ca921d2a refactor: return 404 on missing trace and other cleanup 2026-04-16 09:39:36 +05:30
Nikhil Soni
42f12dfef3 refactor: breakdown GetSelectedSpans for readability 2026-04-16 09:39:36 +05:30
Nikhil Soni
f2a694447e refactor: separate out store calls and computations 2026-04-16 09:39:36 +05:30
Nikhil Soni
2e7dfa739f refactor: move error to types as well 2026-04-16 09:39:36 +05:30
Nikhil Soni
0aa73580a3 refactor: extract ClickHouse queries into a store abstraction
Move GetTraceSummary and GetTraceSpans out of module.go into a
traceStore interface backed by clickhouseTraceStore in store.go.
The module struct now holds a traceStore instead of a raw
telemetrystore.TelemetryStore, keeping DB access separate from
business logic.
2026-04-16 09:39:36 +05:30
Nikhil Soni
2ff1a43bf8 refactor: move type creation and constants to types package
- Move DB/table/cache/windowing constants to tracedetailtypes package
- Add NewWaterfallTrace and NewWaterfallResponse constructors in types
- Use constructors in module.go instead of inline struct literals
- Reorder waterfall.go so public functions precede private ones
2026-04-16 09:39:36 +05:30
Nikhil Soni
c1477c78be chore: avoid returning nil, nil 2026-04-16 09:39:36 +05:30
Nikhil Soni
9807dd5295 refactor: break down GetWaterfall method for readability 2026-04-16 09:39:36 +05:30
Nikhil Soni
2c59eeff26 fix: update openapi specs 2026-04-16 09:39:36 +05:30
Nikhil Soni
8ccfb4efef fix: use int16 for status code as per db schema 2026-04-16 09:39:36 +05:30
Nikhil Soni
87d18160e8 fix: remove timeout since waterfall take longer 2026-04-16 09:39:36 +05:30
Nikhil Soni
bfa7ee96da chore: generate openapi spec for v3 waterfall 2026-04-16 09:39:33 +05:30
Nikhil Soni
5e3eb66d3a fix: use typed paramter field in logs 2026-04-16 09:38:12 +05:30
Nikhil Soni
3d8cdf18bd fix: add timeout to module context 2026-04-16 09:38:12 +05:30
Nikhil Soni
cb4e501047 fix: rename timestamp to milli for readability 2026-04-16 09:38:12 +05:30
Nikhil Soni
cb8b2137ba fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-16 09:38:12 +05:30
Nikhil Soni
998315a255 chore: avoid sorting on every traversal 2026-04-16 09:38:12 +05:30
Nikhil Soni
250657e46b chore: add same test cases as for old waterfall api 2026-04-16 09:38:12 +05:30
Nikhil Soni
795ae9ab18 refactor: convert waterfall api to modules format 2026-04-16 09:38:05 +05:30
Nikhil Soni
6a9ea8d9f8 fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-16 09:31:48 +05:30
Nikhil Soni
2723e18023 fix: update span.attributes to map of string to any
To support otel format of diffrent types of attributes
2026-04-16 09:31:48 +05:30
Nikhil Soni
6e89d5f6eb chore: add reason for using snake case in response 2026-04-16 09:31:48 +05:30
Nikhil Soni
4c2a815236 refactor: move type conversion logic to types pkg 2026-04-16 09:31:48 +05:30
Nikhil Soni
b1d66b2e5f feat: setup types and interface for waterfall v3
v3 is required for udpating the response json of
the waterfall api. There wont' be any logical change.
Using this requirement as an opportunity to move
waterfall api to provider codebase architecture from
older query-service
2026-04-16 09:31:48 +05:30
aks07
24128bd394 feat: show caution on sampled flamegraph 2026-04-15 13:35:55 +05:30
aks07
2118916a23 feat: fix flamegraph and waterfall bg color 2026-04-15 00:51:39 +05:30
aks07
52220412a1 fix: align timeline and span length in flamegraph and waterfall 2026-04-15 00:32:35 +05:30
aks07
85abee8476 feat: show child count in waterfall 2026-04-14 21:27:15 +05:30
aks07
650a29d184 feat: set limit to 100k for flamegraph 2026-04-14 21:17:26 +05:30
aks07
d9c7101d22 feat: reduce time 2026-04-14 18:45:14 +05:30
aks07
b1e7c25189 Merge branch 'main' of github.com:SigNoz/signoz into feat/filter-search 2026-04-14 15:33:19 +05:30
aks07
e9904a0558 Merge branch 'feat/filter-search' of github.com:SigNoz/signoz into feat/filter-search 2026-04-14 15:33:04 +05:30
aks07
5cd199f535 feat: automatically scroll left on vertical scroll 2026-04-14 15:32:23 +05:30
aks07
f6f48ca0bc feat: api integration 2026-04-14 14:46:49 +05:30
Aditya Singh
847f91e22e Merge branch 'main' into feat/filter-search 2026-04-14 13:14:46 +05:30
aks07
29d0abe5a8 Merge branch 'ns/waterfall-v3-2' of github.com:SigNoz/signoz into feat/filter-search 2026-04-14 13:11:26 +05:30
Nikhil Soni
c08840a827 fix: update openapi specs 2026-04-14 10:50:36 +05:30
Nikhil Soni
a3e7bb90b0 fix: use int16 for status code as per db schema 2026-04-14 10:50:36 +05:30
Nikhil Soni
8515d2f37c fix: remove timeout since waterfall take longer 2026-04-14 10:50:36 +05:30
Nikhil Soni
07c05ac3a6 chore: generate openapi spec for v3 waterfall 2026-04-14 10:50:36 +05:30
Nikhil Soni
6289f59ba3 fix: use typed paramter field in logs 2026-04-14 10:50:36 +05:30
Nikhil Soni
76371c9fa2 fix: add timeout to module context 2026-04-14 10:50:36 +05:30
Nikhil Soni
f082e396eb fix: rename timestamp to milli for readability 2026-04-14 10:50:36 +05:30
Nikhil Soni
840eb8f228 fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-14 10:50:36 +05:30
Nikhil Soni
2911baf6bb chore: avoid sorting on every traversal 2026-04-14 10:50:36 +05:30
Nikhil Soni
fc5be4eeb5 chore: add same test cases as for old waterfall api 2026-04-14 10:50:36 +05:30
Nikhil Soni
a1b92c79a4 refactor: convert waterfall api to modules format 2026-04-14 10:50:31 +05:30
Nikhil Soni
7a0acd5c8b fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-14 10:49:58 +05:30
Nikhil Soni
069cbe2c6f fix: update span.attributes to map of string to any
To support otel format of diffrent types of attributes
2026-04-14 10:49:58 +05:30
Nikhil Soni
4c821f9721 chore: add reason for using snake case in response 2026-04-14 10:49:58 +05:30
Nikhil Soni
4eccea92db refactor: move type conversion logic to types pkg 2026-04-14 10:49:58 +05:30
Nikhil Soni
c8d8966a5d feat: setup types and interface for waterfall v3
v3 is required for udpating the response json of
the waterfall api. There wont' be any logical change.
Using this requirement as an opportunity to move
waterfall api to provider codebase architecture from
older query-service
2026-04-14 10:49:58 +05:30
aks07
1e52a5603e Merge branch 'ns/waterfall-v3-2' of github.com:SigNoz/signoz into feat/filter-search 2026-04-14 10:33:46 +05:30
aks07
780ba1a359 feat: new filter ui 2026-04-14 10:27:21 +05:30
aks07
3b71abe820 feat: remove trace header 2026-04-13 18:49:38 +05:30
aks07
70b9d0ff02 feat: filter ui fix 2026-04-13 15:49:43 +05:30
aks07
f4657861e1 feat: make filter and search work with flamegraph 2026-04-13 15:49:09 +05:30
Nikhil Soni
66fe5b5240 fix: update openapi specs 2026-04-13 14:04:45 +05:30
Nikhil Soni
c333cecf43 fix: use int16 for status code as per db schema 2026-04-13 13:23:39 +05:30
Nikhil Soni
276e09853e fix: remove timeout since waterfall take longer 2026-04-13 12:31:08 +05:30
aks07
4defd41504 feat: prevent api call on closing span detail 2026-04-13 12:07:28 +05:30
aks07
ab53b29a14 feat: add span details loader 2026-04-13 10:25:49 +05:30
aks07
b58e82efbf feat: disable anaytics span tab for now 2026-04-12 21:33:07 +05:30
aks07
0a1a676877 feat: show total span count 2026-04-12 21:14:19 +05:30
aks07
bb2aa9f77c feat: auto scroll horizontally to span 2026-04-12 20:08:55 +05:30
aks07
04bef4ac06 feat: sync error and loading state for flamegraph for n/w and computation logic 2026-04-11 23:48:47 +05:30
aks07
3bcb2c2c41 feat: added loading to flamegraph and timeout to webworker 2026-04-11 22:37:10 +05:30
aks07
9e77b76122 feat: add icons 2026-04-11 21:28:00 +05:30
aks07
ff4a41d842 feat: analytics 2026-04-11 14:34:57 +05:30
aks07
387deb779d feat: span details ux 2026-04-10 21:31:40 +05:30
aks07
1ec2663d51 feat: lint fix 2026-04-10 13:40:43 +05:30
aks07
1b17370da0 feat: fix test 2026-04-10 13:10:28 +05:30
aks07
c6484a79e2 feat: fix test 2026-04-10 13:09:08 +05:30
Nikhil Soni
16a2c7a1af chore: generate openapi spec for v3 waterfall 2026-04-10 10:54:36 +05:30
aks07
3c4ac0e85e feat: supress click 2026-04-10 09:08:45 +05:30
aks07
87ba729a00 feat: minor change 2026-04-10 02:46:55 +05:30
aks07
f1ed7145e4 feat: add limit 2026-04-10 02:41:22 +05:30
aks07
bc15495e17 Merge branch 'main' of github.com:SigNoz/signoz into feat/span-details 2026-04-10 02:01:59 +05:30
aks07
f7d3012daf feat: api integration 2026-04-10 00:53:25 +05:30
Nikhil Soni
6ec9a2ec41 fix: use typed paramter field in logs 2026-04-09 21:39:29 +05:30
Nikhil Soni
9c056f809a fix: add timeout to module context 2026-04-09 20:03:47 +05:30
Nikhil Soni
c1d4273416 fix: rename timestamp to milli for readability 2026-04-09 16:54:44 +05:30
Nikhil Soni
618fe891d5 fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-09 14:02:44 +05:30
Nikhil Soni
549c7e7034 chore: avoid sorting on every traversal 2026-04-09 11:38:55 +05:30
Nikhil Soni
dd65f83c3d chore: add same test cases as for old waterfall api 2026-04-09 11:38:55 +05:30
Nikhil Soni
8463a131fc refactor: convert waterfall api to modules format 2026-04-09 11:38:55 +05:30
Nikhil Soni
2d42518440 fix: remove unused fields and rename span type
To avoid confusing with otel span
2026-04-09 11:38:55 +05:30
Nikhil Soni
43d75a3853 fix: update span.attributes to map of string to any
To support otel format of diffrent types of attributes
2026-04-09 11:38:55 +05:30
Nikhil Soni
c5bb34e385 chore: add reason for using snake case in response 2026-04-09 11:38:54 +05:30
Nikhil Soni
6fd129991d refactor: move type conversion logic to types pkg 2026-04-09 11:38:54 +05:30
Nikhil Soni
9c5cca426a feat: setup types and interface for waterfall v3
v3 is required for udpating the response json of
the waterfall api. There wont' be any logical change.
Using this requirement as an opportunity to move
waterfall api to provider codebase architecture from
older query-service
2026-04-09 11:38:54 +05:30
aks07
a467efb97d feat: style fixes 2026-04-09 08:31:12 +05:30
aks07
58e2718090 feat: linked spans 2026-04-08 22:04:42 +05:30
aks07
65fee725c9 feat: key value label style fixes 2026-04-08 14:21:54 +05:30
aks07
ea87174088 feat: fix span details headr 2026-04-08 11:39:47 +05:30
aks07
627c483d86 feat: copy link change and url clear on span close 2026-04-07 20:06:53 +05:30
aks07
2533137db4 feat: use resizable for waterfall table as well 2026-04-07 15:55:06 +05:30
aks07
a774f8a4fe feat: add collapsible sections in trace details 2026-04-07 13:28:59 +05:30
aks07
8487f6cf66 feat: add bound to drags while floating 2026-04-01 19:33:53 +05:30
aks07
6ebe51126e feat: fix pinning. fix drag on top 2026-04-01 16:48:59 +05:30
aks07
ed64d5cd9f feat: replace draggable package 2026-03-31 21:33:42 +05:30
aks07
c04076e664 feat: span details folder rename 2026-03-31 17:50:02 +05:30
aks07
3c129e2c7d feat: span details floating drawer added 2026-03-31 17:44:25 +05:30
aks07
0ba51e2058 feat: json viewer with select dropdown added 2026-03-31 15:17:15 +05:30
aks07
cdc2ab134c feat: style fix 2026-03-26 20:27:41 +05:30
aks07
fb0c05b553 feat: refactor 2026-03-26 20:25:25 +05:30
aks07
68e9707e3b feat: search in pretty view 2026-03-26 20:08:25 +05:30
aks07
17ffaf9ccf feat: minor change 2026-03-26 19:42:48 +05:30
aks07
efec669b76 feat: update yarn lock 2026-03-26 19:31:10 +05:30
aks07
17b9e14d34 feat: added pretty view 2026-03-26 19:25:06 +05:30
aks07
2db9f969c3 feat: key attr section added 2026-03-26 15:01:16 +05:30
aks07
9fa466b124 feat: added span percentile 2026-03-26 14:33:06 +05:30
aks07
0c7768ebff feat: details field component 2026-03-25 03:10:50 +05:30
aks07
58dd51e92f feat: span details header 2026-03-25 02:22:11 +05:30
aks07
870c9bf6dc Merge branch 'main' of github.com:SigNoz/signoz into feat/span-details 2026-03-25 01:08:22 +05:30
aks07
7604956bf0 feat: span details init 2026-03-25 00:34:56 +05:30
aks07
66510e4919 feat: waterfall resizable 2026-03-18 20:58:01 +05:30
aks07
a1bf0e67db feat: event dots in trace details 2026-03-18 18:54:19 +05:30
aks07
a06046612a feat: connector line ux 2026-03-17 18:45:11 +05:30
aks07
31c9d4309b feat: move to service worker 2026-03-17 17:06:05 +05:30
aks07
7bef8b86c4 feat: subtree segregated tree 2026-03-17 14:39:57 +05:30
aks07
d26acd36a3 feat: subtree segregated tree 2026-03-17 13:55:58 +05:30
aks07
1cee595135 feat: subtree segregated tree 2026-03-17 13:29:07 +05:30
aks07
dd1868fcbc feat: row based flamegraph 2026-03-16 23:27:31 +05:30
aks07
a20beb8ba2 feat: span hover popover sync 2026-03-16 14:18:12 +05:30
aks07
998d652feb feat: fix hover option overflow 2026-03-16 10:19:42 +05:30
aks07
3695d3c180 feat: match span style 2026-03-13 15:53:46 +05:30
aks07
da175bafbc feat: add TimelineV3 ruler to waterfall header with padding fix
Add the TimelineV3 component to the sticky header of the waterfall's
right panel so timeline tick marks are visible. Add horizontal padding
to both the timeline header and span duration bars to prevent label
overflow/clipping at the edges.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:12:49 +05:30
aks07
021b187cbc feat: decouple waterfall left (span tree) and right (timeline bars) panels
Split the waterfall into two independent panels with a shared virtualizer
so deeply nested span names are visible via horizontal scroll in the left
panel. Left panel uses useReactTable + <table> for future column
extensibility; right panel uses plain divs for timeline bars. A draggable
resize handle separates the two panels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 16:04:32 +05:30
aks07
f42b468597 feat: waterfall init 2026-03-11 17:15:27 +05:30
aks07
7e2cf57819 Merge branch 'main' of github.com:SigNoz/signoz into feat/trace-flamegraph 2026-03-11 12:24:06 +05:30
aks07
dc9ebc5b26 feat: add test utils 2026-03-11 03:31:10 +05:30
aks07
398ab6e9d9 feat: add test cases for flamegraph 2026-03-11 03:28:35 +05:30
aks07
fec60671d8 feat: minor comment added 2026-03-11 03:03:49 +05:30
aks07
99259cc4e8 feat: remove unnecessary props 2026-03-10 16:01:24 +05:30
aks07
ca311717c2 feat: bg color for selected and hover spans 2026-03-06 23:16:35 +05:30
aks07
a614da2c65 fix: update color 2026-03-06 20:22:23 +05:30
aks07
ce18709002 fix: style fix 2026-03-06 20:03:42 +05:30
aks07
2b6977e891 feat: reduce timeline intervals 2026-03-06 20:01:50 +05:30
aks07
3e6eedbcab feat: fix style 2026-03-06 19:57:19 +05:30
aks07
fd9e3f0411 feat: fix style 2026-03-06 19:26:23 +05:30
aks07
e99465e030 Merge branch 'main' of github.com:SigNoz/signoz into feat/trace-flamegraph 2026-03-06 18:45:18 +05:30
aks07
9ad2db4b99 feat: scroll to selected span 2026-03-06 16:00:48 +05:30
aks07
07fd5f70ef feat: fix timerange unit selection when zoomed 2026-03-06 12:55:34 +05:30
aks07
ba79121795 feat: temp change 2026-03-06 12:49:06 +05:30
aks07
6e4e419b5e Merge branch 'main' of github.com:SigNoz/signoz into feat/trace-flamegraph 2026-03-06 10:30:41 +05:30
aks07
2f06afaf27 feat: handle click and hover with tooltip 2026-03-05 23:25:10 +05:30
aks07
f77c3cb23c feat: update span colors 2026-03-05 22:40:06 +05:30
aks07
9e3a8efcfc feat: zoom and drag added 2026-03-05 18:22:55 +05:30
aks07
8e325ba8b3 feat: added timeline v3 2026-03-05 12:31:17 +05:30
aks07
884f516766 feat: add text to spans 2026-03-03 12:20:06 +05:30
aks07
4bcbb4ffc3 feat: flamegraph canvas init 2026-03-02 19:21:23 +05:30
991 changed files with 60291 additions and 34662 deletions

64
.github/CODEOWNERS vendored
View File

@@ -16,38 +16,38 @@ go.mod @therealpandey
# Scaffold Owners
/pkg/config/ @therealpandey
/pkg/errors/ @therealpandey
/pkg/factory/ @therealpandey
/pkg/types/ @therealpandey
/pkg/valuer/ @therealpandey
/cmd/ @therealpandey
.golangci.yml @therealpandey
/pkg/config/ @vikrantgupta25
/pkg/errors/ @vikrantgupta25
/pkg/factory/ @vikrantgupta25
/pkg/types/ @vikrantgupta25
/pkg/valuer/ @vikrantgupta25
/cmd/ @vikrantgupta25
.golangci.yml @vikrantgupta25
# Zeus Owners
/pkg/zeus/ @therealpandey
/ee/zeus/ @therealpandey
/pkg/licensing/ @therealpandey
/ee/licensing/ @therealpandey
/pkg/zeus/ @vikrantgupta25
/ee/zeus/ @vikrantgupta25
/pkg/licensing/ @vikrantgupta25
/ee/licensing/ @vikrantgupta25
# SQL Owners
/pkg/sqlmigration/ @therealpandey
/ee/sqlmigration/ @therealpandey
/pkg/sqlschema/ @therealpandey
/ee/sqlschema/ @therealpandey
/pkg/sqlmigration/ @vikrantgupta25
/ee/sqlmigration/ @vikrantgupta25
/pkg/sqlschema/ @vikrantgupta25
/ee/sqlschema/ @vikrantgupta25
# Analytics Owners
/pkg/analytics/ @therealpandey
/pkg/statsreporter/ @therealpandey
/pkg/analytics/ @vikrantgupta25
/pkg/statsreporter/ @vikrantgupta25
# Emailing Owners
/pkg/emailing/ @therealpandey
/pkg/types/emailtypes/ @therealpandey
/templates/email/ @therealpandey
/pkg/emailing/ @vikrantgupta25
/pkg/types/emailtypes/ @vikrantgupta25
/templates/email/ @vikrantgupta25
# Querier Owners
@@ -97,23 +97,23 @@ go.mod @therealpandey
# AuthN / AuthZ Owners
/pkg/authz/ @therealpandey
/ee/authz/ @therealpandey
/pkg/authn/ @therealpandey
/ee/authn/ @therealpandey
/pkg/modules/user/ @therealpandey
/pkg/modules/session/ @therealpandey
/pkg/modules/organization/ @therealpandey
/pkg/modules/authdomain/ @therealpandey
/pkg/modules/role/ @therealpandey
/pkg/authz/ @vikrantgupta25
/ee/authz/ @vikrantgupta25
/pkg/authn/ @vikrantgupta25
/ee/authn/ @vikrantgupta25
/pkg/modules/user/ @vikrantgupta25
/pkg/modules/session/ @vikrantgupta25
/pkg/modules/organization/ @vikrantgupta25
/pkg/modules/authdomain/ @vikrantgupta25
/pkg/modules/role/ @vikrantgupta25
# IdentN Owners
/pkg/identn/ @therealpandey
/pkg/http/middleware/identn.go @therealpandey
/pkg/identn/ @vikrantgupta25
/pkg/http/middleware/identn.go @vikrantgupta25
# Integration tests
/tests/integration/ @therealpandey
/tests/integration/ @vikrantgupta25
# OpenAPI types generator

View File

@@ -7,7 +7,6 @@ import (
"github.com/spf13/cobra"
"github.com/SigNoz/signoz/cmd"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn"
@@ -15,7 +14,6 @@ import (
"github.com/SigNoz/signoz/pkg/authz/openfgaauthz"
"github.com/SigNoz/signoz/pkg/authz/openfgaschema"
"github.com/SigNoz/signoz/pkg/authz/openfgaserver"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
@@ -28,20 +26,14 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
"github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/query-service/app"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
"github.com/SigNoz/signoz/pkg/zeus/noopzeus"
@@ -83,7 +75,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(),
signoz.NewWebProviderFactories(config.Global),
signoz.NewWebProviderFactories(),
func(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
return signoz.NewSQLSchemaProviderFactories(sqlstore)
},
@@ -115,9 +107,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
func(_ sqlstore.SQLStore, _ global.Global, _ zeus.Zeus, _ gateway.Gateway, _ licensing.Licensing, _ serviceaccount.Module, _ cloudintegration.Config) (cloudintegration.Module, error) {
return implcloudintegration.NewModule(), nil
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, nil, nil))
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

View File

@@ -22,17 +22,14 @@ import (
"github.com/SigNoz/signoz/ee/modules/dashboard/impldashboard"
eequerier "github.com/SigNoz/signoz/ee/querier"
enterpriseapp "github.com/SigNoz/signoz/ee/query-service/app"
eerules "github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/ee/sqlschema/postgressqlschema"
"github.com/SigNoz/signoz/ee/sqlstore/postgressqlstore"
enterprisezeus "github.com/SigNoz/signoz/ee/zeus"
"github.com/SigNoz/signoz/ee/zeus/httpzeus"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/analytics"
"github.com/SigNoz/signoz/pkg/auditor"
"github.com/SigNoz/signoz/pkg/authn"
"github.com/SigNoz/signoz/pkg/authz"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/gateway"
@@ -43,21 +40,15 @@ import (
"github.com/SigNoz/signoz/pkg/modules/dashboard"
pkgimpldashboard "github.com/SigNoz/signoz/pkg/modules/dashboard/impldashboard"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/modules/serviceaccount"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler"
"github.com/SigNoz/signoz/pkg/ruler/signozruler"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlschema"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/sqlstore/sqlstorehook"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/types/authtypes"
"github.com/SigNoz/signoz/pkg/types/cloudintegrationtypes"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/SigNoz/signoz/pkg/version"
"github.com/SigNoz/signoz/pkg/zeus"
)
@@ -105,7 +96,7 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
},
signoz.NewEmailingProviderFactories(),
signoz.NewCacheProviderFactories(),
signoz.NewWebProviderFactories(config.Global),
signoz.NewWebProviderFactories(),
func(sqlstore sqlstore.SQLStore) factory.NamedMap[factory.ProviderFactory[sqlschema.SQLSchema, sqlschema.Config]] {
existingFactories := signoz.NewSQLSchemaProviderFactories(sqlstore)
if err := existingFactories.Add(postgressqlschema.NewFactory(sqlstore)); err != nil {
@@ -175,9 +166,6 @@ func runServer(ctx context.Context, config signoz.Config, logger *slog.Logger) e
return implcloudintegration.NewModule(pkgcloudintegration.NewStore(sqlStore), global, zeus, gateway, licensing, serviceAccount, cloudProvidersMap, config)
},
func(c cache.Cache, am alertmanager.Alertmanager, ss sqlstore.SQLStore, ts telemetrystore.TelemetryStore, ms telemetrytypes.MetadataStore, p prometheus.Prometheus, og organization.Getter, rsh rulestatehistory.Module, q querier.Querier, qp queryparser.QueryParser) factory.NamedMap[factory.ProviderFactory[ruler.Ruler, ruler.Config]] {
return factory.MustNewNamedMap(signozruler.NewFactory(c, am, ss, ts, ms, p, og, rsh, q, qp, eerules.PrepareTaskFunc, eerules.TestNotification))
},
)
if err != nil {
logger.ErrorContext(ctx, "failed to create signoz", errors.Attr(err))

View File

@@ -6,8 +6,6 @@
##################### Global #####################
global:
# the url under which the signoz apiserver is externally reachable.
# the path component (e.g. /signoz in https://example.com/signoz) is used
# as the base path for all HTTP routes (both API and web frontend).
external_url: <unset>
# the url where the SigNoz backend receives telemetry data (traces, metrics, logs) from instrumented applications.
ingestion_url: <unset>
@@ -52,8 +50,8 @@ pprof:
web:
# Whether to enable the web frontend
enabled: true
# The index file to use as the SPA entrypoint.
index: index.html
# The prefix to serve web on
prefix: /
# The directory containing the static build files.
directory: /etc/signoz/web

File diff suppressed because it is too large Load Diff

View File

@@ -20,4 +20,3 @@ We **recommend** (almost enforce) reviewing these guides before contributing to
- [Packages](packages.md) - Naming, layout, and conventions for `pkg/` packages
- [Service](service.md) - Managed service lifecycle with `factory.Service`
- [SQL](sql.md) - Database and SQL patterns
- [Types](types.md) - Domain types, request/response bodies, and storage rows in `pkg/types/`

View File

@@ -1,152 +0,0 @@
# Types
Domain types in `pkg/types/<domain>/` live on three serialization boundaries — inbound HTTP, outbound HTTP, and SQL — on top of an in-memory domain representation. SigNoz's convention is **core-type-first**: every domain defines a single canonical type `X`, and specialized flavors (`PostableX`, `GettableX`, `UpdatableX`, `StorableX`) are introduced **only when they actually differ from `X`**. This guide spells out when each flavor is warranted and how they relate to each other.
Before reading, make sure you have read [abstractions.md](abstractions.md) — the rules here build on its guidance that every new type must earn its place.
## The core type is required
Every domain package in `pkg/types/<domain>/` defines exactly one core type `X`: `AuthDomain`, `Channel`, `Rule`, `Dashboard`, `Role`, `PlannedMaintenance`. This is the canonical in-memory representation of the domain object. Domain methods, validation invariants, and business logic hang off `X` — not off the flavor types.
Two rules shape how the core type behaves:
- **Conversions can be either `New<Output>From<Input>` or a receiver-style `(x *X) ToY()` method.** Either form is fine; pick whichever reads best at the call site:
```go
// Constructor form
func NewGettableAuthDomainFromAuthDomain(d *AuthDomain, info *AuthNProviderInfo) *GettableAuthDomain
// Receiver form
func (m *PlannedMaintenanceWithRules) ToPlannedMaintenance() *PlannedMaintenance
```
- **`X` can double as the storage row** when the DB shape would be identical. `Channel` embeds `bun.BaseModel` directly, and there is no `StorableChannel`. This is the preferred shape when it works.
Domain packages under `pkg/types/` must not import from other `pkg/` packages. Keep the core type's methods lightweight and push orchestration out to the module layer.
## Add a flavor only when it differs
For each of the four flavors, create it only if its shape diverges from `X`. If a flavor would have the same fields and tags as `X`, reuse `X` directly, or declare a type alias. Every flavor must earn its place per [abstractions.md](abstractions.md) rule 6 ("Wrappers must add semantics, not just rename").
| Flavor | Create it when it differs in… |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PostableX` | JSON shape differs from `X` — typically no `Id`, no audit fields, no server-computed fields. Often owns input validation via `Validate()` or a custom `UnmarshalJSON`. |
| `GettableX` | Response shape adds server-computed fields that are not persisted — e.g., `GettableAuthDomain` adds `AuthNProviderInfo`, which is resolved at read time. |
| `UpdatableX` | Only a strict subset of `PostableX` is replaceable on PUT. If the updatable shape equals `PostableX`, reuse `PostableX`. |
| `StorableX` | DB row shape differs from `X` — usually `X` carries nested typed config while `StorableX` carries a flat `Data string` JSON column, plus bun tags, audit mixins, and an `OrgID`. If `X` already has those, skip the flavor. |
The failure mode this rule exists to prevent: minting all four flavors on reflex for every new resource, even when two or three are structurally identical. Each unnecessary flavor is another type contributors must understand and another conversion that can drift.
## Worked examples
### Channel — core type only
```go
type Channels = []*Channel
type GettableChannels = []*Channel
type Channel struct {
bun.BaseModel `bun:"table:notification_channel"`
types.Identifiable
types.TimeAuditable
Name string `json:"name" required:"true" bun:"name"`
Type string `json:"type" required:"true" bun:"type"`
Data string `json:"data" required:"true" bun:"data"`
OrgID string `json:"orgId" required:"true" bun:"org_id"`
}
```
`Channel` is both the domain type and the bun row. `GettableChannels` is a **type alias** because `*Channel` already serializes correctly as a response. There is no `StorableChannel`, `PostableChannel`, or `UpdatableChannel` — those would be identical to `Channel` and so do not exist. Prefer this shape when it works.
### AuthDomain — all four flavors
```go
type AuthDomain struct {
storableAuthDomain *StorableAuthDomain
authDomainConfig *AuthDomainConfig
}
type StorableAuthDomain struct {
bun.BaseModel `bun:"table:auth_domain"`
types.Identifiable
Name string `bun:"name"`
Data string `bun:"data"` // AuthDomainConfig serialized as JSON
OrgID valuer.UUID `bun:"org_id"`
types.TimeAuditable
}
type PostableAuthDomain struct {
Config AuthDomainConfig `json:"config"`
Name string `json:"name"`
}
type UpdateableAuthDomain struct {
Config AuthDomainConfig `json:"config"` // Name intentionally absent
}
type GettableAuthDomain struct {
*StorableAuthDomain
*AuthDomainConfig
AuthNProviderInfo *AuthNProviderInfo `json:"authNProviderInfo"`
}
```
Each flavor exists for a concrete reason:
- `StorableAuthDomain` stores the typed config as an opaque `Data string` column, so the schema does not need to migrate every time a config field is added.
- `PostableAuthDomain` carries the config as a structured object (not a string) for the request.
- `UpdateableAuthDomain` excludes `Name` because a domain's name cannot change after creation.
- `GettableAuthDomain` adds `AuthNProviderInfo`, which is derived at read time and never persisted.
The core `AuthDomain` holds the two live halves — `storableAuthDomain` and `authDomainConfig` — and owns business methods such as `Update(config)`. Conversions use the `New<Output>From<Input>` form: `NewAuthDomainFromConfig`, `NewAuthDomainFromStorableAuthDomain`, `NewGettableAuthDomainFromAuthDomain`.
## Conventions that tie the flavors together
- **Conversions** use either a `New<Output>From<Input>` constructor — e.g. `NewChannelFromReceiver`, `NewGettableAuthDomainFromAuthDomain` — or a receiver-style `ToY()` method. Both forms coexist in the codebase; use whichever fits the call site.
- **Validation belongs on the core type `X`.** Putting it on `X` means every write path — HTTP create, HTTP update, in-process migration, replay — runs the same checks. `Validate()` on `PostableX` is reserved for checks that are specific to the request shape and do not apply to `X`. `UnmarshalJSON` on `PostableX` is a separate tool that lives there because decoding only happens at the HTTP boundary — `PostableAuthDomain.UnmarshalJSON` rejecting a malformed domain name at decode time is the canonical example.
```go
// Domain invariants: every write path re-runs these.
func (x *X) Validate() error { ... }
// Request-shape-only: checks that do not apply once the value is persisted.
func (p *PostableX) Validate() error { ... }
```
- **Type aliases, not wrappers**, when two shapes are identical. `type GettableChannels = []*Channel` is correct because it adds no semantics beyond the underlying type.
- **Serialization tags** follow [handler.md](handler.md): `required:"true"` means the JSON key must be present, `nullable:"true"` is required on any slice or map that may serialize as `null`, and types with a fixed value set must implement `Enum() []any`.
## A note on `UpdatableX` and `PatchableX`
- `UpdatableX` — the body for PUT (full replace) when the shape is a strict subset of `PostableX`. If the updatable shape equals `PostableX`, reuse `PostableX`.
- `PatchableX` — the body for PATCH (partial update); only the fields a client is allowed to patch. For example, `PatchableRole` carries a single `Description` field even though `Role` has many — clients may patch the description but not anything else.
```go
type PatchableRole struct {
Description string `json:"description"`
}
```
Both are optional. Do not introduce them if `PostableX` already covers the case.
## What to avoid
- **Do not mint a flavor that mirrors the core type.** If `StorableX` would have the same fields as `X`, use `X` directly with `bun.BaseModel` embedded. `Channel` is the canonical example.
- **Do not bolt domain methods onto `StorableX`.** Storage types are data carriers. Domain methods live on `X`.
- **Do not invent new suffixes** (`Creatable`, `Fetchable`, `Savable`). The core type plus `Postable` / `Gettable` / `Updatable` / `Patchable` / `Storable` covers every case that exists today.
- **Spelling — `Updatable`, not `Updateable`.** `Updateable` is a common typo. Prefer the shorter form when introducing new types, and rename any stragglers you come across.
- **Spelling — `Storable`, not `Storeable`.** `Storeable` is a common typo. Prefer the shorter form when introducing new types, and rename any stragglers you come across.
## What should I remember?
- Every domain package defines the core type `X`. Only `X` is mandatory.
- Add `PostableX` / `GettableX` / `UpdatableX` / `StorableX` one at a time, only when the shape actually diverges from `X`.
- Domain logic lives on `X`, not on the flavor types.
- Conversions can be a `New<Output>From<Input>` constructor or a receiver-style `ToY()` method — pick whichever reads best at the call site.
- Use a type alias when two shapes are truly identical.
- `pkg/types/<domain>/` must not import from other `pkg/` packages.
## Further reading
- [abstractions.md](abstractions.md) — when to introduce a new type at all.
- [handler.md](handler.md) — struct tag rules at the HTTP boundary.
- [packages.md](packages.md) — where types live under `pkg/types/`.
- [sql.md](sql.md) — star-schema requirements for `StorableX`.

View File

@@ -19,11 +19,11 @@ func NewAWSCloudProvider(defStore cloudintegrationtypes.ServiceDefinitionStore)
}
func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, account *cloudintegrationtypes.Account, req *cloudintegrationtypes.GetConnectionArtifactRequest) (*cloudintegrationtypes.ConnectionArtifact, error) {
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.AWS.DeploymentRegion)
baseURL := fmt.Sprintf(cloudintegrationtypes.CloudFormationQuickCreateBaseURL.StringValue(), req.Config.Aws.DeploymentRegion)
u, _ := url.Parse(baseURL)
q := u.Query()
q.Set("region", req.Config.AWS.DeploymentRegion)
q.Set("region", req.Config.Aws.DeploymentRegion)
u.Fragment = "/stacks/quickcreate"
u.RawQuery = q.Encode()
@@ -39,7 +39,9 @@ func (provider *awscloudprovider) GetConnectionArtifact(ctx context.Context, acc
q.Set("param_IngestionKey", req.Credentials.IngestionKey)
return &cloudintegrationtypes.ConnectionArtifact{
AWS: cloudintegrationtypes.NewAWSConnectionArtifact(u.String() + "?&" + q.Encode()), // this format is required by AWS
Aws: &cloudintegrationtypes.AWSConnectionArtifact{
ConnectionURL: u.String() + "?&" + q.Encode(), // this format is required by AWS
},
}, nil
}
@@ -122,6 +124,9 @@ func (provider *awscloudprovider) BuildIntegrationConfig(
}
return &cloudintegrationtypes.ProviderIntegrationConfig{
AWS: cloudintegrationtypes.NewAWSIntegrationConfig(account.Config.AWS.Regions, collectionStrategy),
AWS: &cloudintegrationtypes.AWSIntegrationConfig{
EnabledRegions: account.Config.AWS.Regions,
TelemetryCollectionStrategy: collectionStrategy,
},
}, nil
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/SigNoz/signoz/pkg/query-service/app/logparsingpipeline"
"github.com/SigNoz/signoz/pkg/query-service/interfaces"
basemodel "github.com/SigNoz/signoz/pkg/query-service/model"
rules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/version"
@@ -22,6 +23,7 @@ import (
type APIHandlerOptions struct {
DataConnector interfaces.Reader
RulesManager *rules.Manager
UsageManager *usage.Manager
IntegrationsController *integrations.Controller
CloudIntegrationsController *cloudintegrations.Controller
@@ -41,6 +43,7 @@ type APIHandler struct {
func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.Config) (*APIHandler, error) {
baseHandler, err := baseapp.NewAPIHandler(baseapp.APIHandlerOpts{
Reader: opts.DataConnector,
RuleManager: opts.RulesManager,
IntegrationsController: opts.IntegrationsController,
CloudIntegrationsController: opts.CloudIntegrationsController,
LogsParsingPipelineController: opts.LogsParsingPipelineController,
@@ -61,6 +64,10 @@ func NewAPIHandler(opts APIHandlerOptions, signoz *signoz.SigNoz, config signoz.
return ah, nil
}
func (ah *APIHandler) RM() *rules.Manager {
return ah.opts.RulesManager
}
func (ah *APIHandler) UM() *usage.Manager {
return ah.opts.UsageManager
}

View File

@@ -12,6 +12,10 @@ import (
"github.com/SigNoz/signoz/pkg/cache/memorycache"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/factory"
"github.com/SigNoz/signoz/pkg/queryparser"
"github.com/SigNoz/signoz/pkg/ruler/rulestore/sqlrulestore"
"github.com/SigNoz/signoz/pkg/types/telemetrytypes"
"github.com/gorilla/handlers"
@@ -19,10 +23,18 @@ import (
"github.com/soheilhy/cmux"
"github.com/SigNoz/signoz/ee/query-service/app/api"
"github.com/SigNoz/signoz/ee/query-service/rules"
"github.com/SigNoz/signoz/ee/query-service/usage"
"github.com/SigNoz/signoz/pkg/alertmanager"
"github.com/SigNoz/signoz/pkg/cache"
"github.com/SigNoz/signoz/pkg/http/middleware"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/modules/rulestatehistory"
"github.com/SigNoz/signoz/pkg/prometheus"
"github.com/SigNoz/signoz/pkg/querier"
"github.com/SigNoz/signoz/pkg/signoz"
"github.com/SigNoz/signoz/pkg/sqlstore"
"github.com/SigNoz/signoz/pkg/telemetrystore"
"github.com/SigNoz/signoz/pkg/web"
"log/slog"
@@ -37,6 +49,7 @@ import (
opAmpModel "github.com/SigNoz/signoz/pkg/query-service/app/opamp/model"
baseconst "github.com/SigNoz/signoz/pkg/query-service/constants"
"github.com/SigNoz/signoz/pkg/query-service/healthcheck"
baserules "github.com/SigNoz/signoz/pkg/query-service/rules"
"github.com/SigNoz/signoz/pkg/query-service/utils"
)
@@ -44,6 +57,7 @@ import (
type Server struct {
config signoz.Config
signoz *signoz.SigNoz
ruleManager *baserules.Manager
// public http router
httpConn net.Listener
@@ -83,6 +97,24 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
nil,
)
rm, err := makeRulesManager(
signoz.Cache,
signoz.Alertmanager,
signoz.SQLStore,
signoz.TelemetryStore,
signoz.TelemetryMetadataStore,
signoz.Prometheus,
signoz.Modules.OrgGetter,
signoz.Modules.RuleStateHistory,
signoz.Querier,
signoz.Instrumentation.ToProviderSettings(),
signoz.QueryParser,
)
if err != nil {
return nil, err
}
// initiate opamp
opAmpModel.Init(signoz.SQLStore, signoz.Instrumentation.Logger(), signoz.Modules.OrgGetter)
@@ -120,7 +152,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
}
// start the usagemanager
usageManager, err := usage.New(signoz.Licensing, signoz.TelemetryStore.ClickhouseDB(), signoz.Zeus, signoz.Modules.OrgGetter, signoz.Flagger)
usageManager, err := usage.New(signoz.Licensing, signoz.TelemetryStore.ClickhouseDB(), signoz.Zeus, signoz.Modules.OrgGetter)
if err != nil {
return nil, err
}
@@ -131,6 +163,7 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
apiOpts := api.APIHandlerOptions{
DataConnector: reader,
RulesManager: rm,
UsageManager: usageManager,
IntegrationsController: integrationsController,
CloudIntegrationsController: cloudIntegrationsController,
@@ -147,7 +180,8 @@ func NewServer(config signoz.Config, signoz *signoz.SigNoz) (*Server, error) {
s := &Server{
config: config,
signoz: signoz,
signoz: signoz,
ruleManager: rm,
httpHostPort: baseconst.HTTPHostPort,
unavailableChannel: make(chan healthcheck.Status),
usageManager: usageManager,
@@ -228,20 +262,6 @@ func (s *Server) createPublicServer(apiHandler *api.APIHandler, web web.Web) (*h
return nil, err
}
routePrefix := s.config.Global.ExternalPath()
if routePrefix != "" {
prefixed := http.StripPrefix(routePrefix, handler)
handler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/api/v1/health", "/api/v2/healthz", "/api/v2/readyz", "/api/v2/livez":
r.ServeHTTP(w, req)
return
}
prefixed.ServeHTTP(w, req)
})
}
return &http.Server{
Handler: handler,
}, nil
@@ -268,6 +288,8 @@ func (s *Server) initListeners() error {
// Start listening on http and private http port concurrently
func (s *Server) Start(ctx context.Context) error {
s.ruleManager.Start(ctx)
err := s.initListeners()
if err != nil {
return err
@@ -311,9 +333,47 @@ func (s *Server) Stop(ctx context.Context) error {
s.opampServer.Stop()
if s.ruleManager != nil {
s.ruleManager.Stop(ctx)
}
// stop usage manager
s.usageManager.Stop(ctx)
return nil
}
func makeRulesManager(cache cache.Cache, alertmanager alertmanager.Alertmanager, sqlstore sqlstore.SQLStore, telemetryStore telemetrystore.TelemetryStore, metadataStore telemetrytypes.MetadataStore, prometheus prometheus.Prometheus, orgGetter organization.Getter, ruleStateHistoryModule rulestatehistory.Module, querier querier.Querier, providerSettings factory.ProviderSettings, queryParser queryparser.QueryParser) (*baserules.Manager, error) {
ruleStore := sqlrulestore.NewRuleStore(sqlstore, queryParser, providerSettings)
maintenanceStore := sqlrulestore.NewMaintenanceStore(sqlstore)
// create manager opts
managerOpts := &baserules.ManagerOptions{
TelemetryStore: telemetryStore,
MetadataStore: metadataStore,
Prometheus: prometheus,
Context: context.Background(),
Querier: querier,
Logger: providerSettings.Logger,
Cache: cache,
EvalDelay: baseconst.GetEvalDelay(),
PrepareTaskFunc: rules.PrepareTaskFunc,
PrepareTestRuleFunc: rules.TestNotification,
Alertmanager: alertmanager,
OrgGetter: orgGetter,
RuleStore: ruleStore,
MaintenanceStore: maintenanceStore,
SQLStore: sqlstore,
QueryParser: queryParser,
RuleStateHistoryModule: ruleStateHistoryModule,
}
// create Manager
manager, err := baserules.NewManager(managerOpts)
if err != nil {
return nil, fmt.Errorf("rule manager error: %v", err)
}
slog.Info("rules manager is ready")
return manager, nil
}

View File

@@ -16,11 +16,9 @@ import (
"github.com/SigNoz/signoz/ee/query-service/model"
"github.com/SigNoz/signoz/pkg/errors"
"github.com/SigNoz/signoz/pkg/flagger"
"github.com/SigNoz/signoz/pkg/licensing"
"github.com/SigNoz/signoz/pkg/modules/organization"
"github.com/SigNoz/signoz/pkg/query-service/utils/encryption"
"github.com/SigNoz/signoz/pkg/types/featuretypes"
"github.com/SigNoz/signoz/pkg/zeus"
)
@@ -45,18 +43,15 @@ type Manager struct {
zeus zeus.Zeus
orgGetter organization.Getter
flagger flagger.Flagger
}
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, orgGetter organization.Getter, flagger flagger.Flagger) (*Manager, error) {
func New(licenseService licensing.Licensing, clickhouseConn clickhouse.Conn, zeus zeus.Zeus, orgGetter organization.Getter) (*Manager, error) {
m := &Manager{
clickhouseConn: clickhouseConn,
licenseService: licenseService,
scheduler: gocron.NewScheduler(time.UTC).Every(1).Day().At("00:00"), // send usage every at 00:00 UTC
zeus: zeus,
orgGetter: orgGetter,
flagger: flagger,
}
return m, nil
}
@@ -173,14 +168,7 @@ func (lm *Manager) UploadUsage(ctx context.Context) {
return
}
evalCtx := featuretypes.NewFlaggerEvaluationContext(organization.ID)
useZeus := lm.flagger.BooleanOrEmpty(ctx, flagger.FeaturePutMetersInZeus, evalCtx)
if useZeus {
errv2 = lm.zeus.PutMetersV2(ctx, payload.LicenseKey.String(), body)
} else {
errv2 = lm.zeus.PutMeters(ctx, payload.LicenseKey.String(), body)
}
errv2 = lm.zeus.PutMeters(ctx, payload.LicenseKey.String(), body)
if errv2 != nil {
slog.ErrorContext(ctx, "failed to upload usage", errors.Attr(errv2))
// not returning error here since it is captured in the failed count

View File

@@ -136,18 +136,6 @@ func (provider *Provider) PutMeters(ctx context.Context, key string, data []byte
return err
}
func (provider *Provider) PutMetersV2(ctx context.Context, key string, data []byte) error {
_, err := provider.do(
ctx,
provider.config.URL.JoinPath("/v1/meters"),
http.MethodPost,
key,
data,
)
return err
}
func (provider *Provider) PutProfile(ctx context.Context, key string, profile *zeustypes.PostableProfile) error {
body, err := json.Marshal(profile)
if err != nil {

View File

@@ -62,29 +62,6 @@
<link data-react-helmet="true" rel="shortcut icon" href="/favicon.ico" />
</head>
<body data-theme="default">
<script>
// Apply theme class synchronously before React renders to prevent flash.
// Mirrors the logic in ThemeProvider (hooks/useDarkMode/index.tsx).
(function () {
try {
var theme = localStorage.getItem('THEME');
var autoSwitch = localStorage.getItem('THEME_AUTO_SWITCH') === 'true';
if (autoSwitch) {
theme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
}
if (theme === 'light') {
document.body.classList.add('lightMode');
} else {
// Default to dark when no preference is stored
document.body.classList.add('dark', 'darkMode');
}
} catch (e) {
document.body.classList.add('dark', 'darkMode');
}
})();
</script>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>

View File

@@ -15,7 +15,6 @@ const config: Config.InitialOptions = {
'<rootDir>/__mocks__/fileMock.ts',
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.module\\.mjs$': '<rootDir>/__mocks__/cssMock.ts',
'\\.md$': '<rootDir>/__mocks__/cssMock.ts',
'^uplot$': '<rootDir>/__mocks__/uplotMock.ts',
'^@signozhq/resizable$': '<rootDir>/__mocks__/resizableMock.tsx',
@@ -45,7 +44,7 @@ const config: Config.InitialOptions = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
'node_modules/(?!(lodash-es|react-dnd|core-dnd|@react-dnd|dnd-core|react-dnd-html5-backend|axios|@signozhq/design-tokens|@signozhq/table|@signozhq/calendar|@signozhq/input|@signozhq/popover|@signozhq/button|@signozhq/sonner|@signozhq/*|date-fns|d3-interpolate|d3-color|api|@codemirror|@lezer|@marijn|@grafana|nuqs)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
testPathIgnorePatterns: ['/node_modules/', '/public/'],

View File

@@ -24,30 +24,6 @@ window.matchMedia =
};
};
// Patch getComputedStyle to handle CSS parsing errors from @signozhq/* packages.
// These packages inject CSS at import time via style-inject / vite-plugin-css-injected-by-js.
// jsdom's nwsapi cannot parse some of the injected selectors (e.g. Tailwind's :animate-in),
// causing SyntaxErrors during getComputedStyle / getByRole calls.
const _origGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function (
elt: Element,
pseudoElt?: string | null,
): CSSStyleDeclaration {
try {
return _origGetComputedStyle.call(window, elt, pseudoElt);
} catch {
// Return a minimal CSSStyleDeclaration so callers (testing-library, Radix UI)
// see the element as visible and without animations.
return ({
display: '',
visibility: '',
opacity: '1',
animationName: 'none',
getPropertyValue: () => '',
} as unknown) as CSSStyleDeclaration;
}
};
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());

View File

@@ -48,24 +48,27 @@
"@radix-ui/react-tooltip": "1.0.7",
"@sentry/react": "8.41.0",
"@sentry/vite-plugin": "2.22.6",
"@signozhq/button": "0.0.5",
"@signozhq/calendar": "0.1.1",
"@signozhq/callout": "0.0.4",
"@signozhq/checkbox": "0.0.4",
"@signozhq/combobox": "0.0.4",
"@signozhq/command": "0.0.2",
"@signozhq/design-tokens": "2.1.4",
"@signozhq/dialog": "0.0.4",
"@signozhq/drawer": "0.0.6",
"@signozhq/badge": "0.0.2",
"@signozhq/button": "0.0.2",
"@signozhq/calendar": "0.0.0",
"@signozhq/callout": "0.0.2",
"@signozhq/checkbox": "0.0.2",
"@signozhq/combobox": "0.0.2",
"@signozhq/command": "0.0.0",
"@signozhq/design-tokens": "2.1.1",
"@signozhq/dialog": "^0.0.2",
"@signozhq/drawer": "0.0.4",
"@signozhq/icons": "0.1.0",
"@signozhq/input": "0.0.4",
"@signozhq/popover": "0.1.2",
"@signozhq/radio-group": "0.0.4",
"@signozhq/resizable": "0.0.2",
"@signozhq/tabs": "0.0.11",
"@signozhq/table": "0.3.8",
"@signozhq/toggle-group": "0.0.3",
"@signozhq/ui": "0.0.5",
"@signozhq/input": "0.0.2",
"@signozhq/popover": "0.0.0",
"@signozhq/radio-group": "0.0.2",
"@signozhq/resizable": "0.0.0",
"@signozhq/sonner": "0.1.0",
"@signozhq/switch": "0.0.2",
"@signozhq/table": "0.3.7",
"@signozhq/toggle-group": "0.0.1",
"@signozhq/tooltip": "0.0.2",
"@signozhq/ui": "0.0.6",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",
@@ -137,10 +140,12 @@
"react-helmet-async": "1.3.0",
"react-hook-form": "7.71.2",
"react-i18next": "^11.16.1",
"react-json-tree": "^0.20.0",
"react-lottie": "1.2.10",
"react-markdown": "8.0.7",
"react-query": "3.39.3",
"react-redux": "^7.2.2",
"react-rnd": "^10.5.3",
"react-router-dom": "^5.2.0",
"react-router-dom-v5-compat": "6.27.0",
"react-syntax-highlighter": "15.5.0",

View File

@@ -65,6 +65,13 @@ export const TraceDetail = Loadable(
),
);
export const TraceDetailV3 = Loadable(
() =>
import(
/* webpackChunkName: "TraceDetailV3 Page" */ 'pages/TraceDetailV3Page/index'
),
);
export const UsageExplorerPage = Loadable(
() => import(/* webpackChunkName: "UsageExplorerPage" */ 'modules/Usage'),
);
@@ -244,18 +251,12 @@ export const ShortcutsPage = Loadable(
() => import(/* webpackChunkName: "ShortcutsPage" */ 'pages/Settings'),
);
export const Integrations = Loadable(
export const InstalledIntegrations = Loadable(
() =>
import(
/* webpackChunkName: "InstalledIntegrations" */ 'pages/IntegrationsModulePage'
),
);
export const IntegrationsDetailsPage = Loadable(
() =>
import(
/* webpackChunkName: "IntegrationsDetailsPage" */ 'pages/IntegrationsDetailsPage'
),
);
export const MessagingQueuesMainPage = Loadable(
() =>

View File

@@ -18,8 +18,7 @@ import {
ForgotPassword,
Home,
InfrastructureMonitoring,
Integrations,
IntegrationsDetailsPage,
InstalledIntegrations,
LicensePage,
ListAllALertsPage,
LiveLogs,
@@ -48,6 +47,7 @@ import {
StatusPage,
SupportPage,
TraceDetail,
TraceDetailV3,
TraceFilter,
TracesExplorer,
TracesFunnelDetails,
@@ -141,10 +141,17 @@ const routes: AppRoutes[] = [
{
path: ROUTES.TRACE_DETAIL,
exact: true,
component: TraceDetail,
component: TraceDetailV3,
isPrivate: true,
key: 'TRACE_DETAIL',
},
{
path: ROUTES.TRACE_DETAIL_OLD,
exact: true,
component: TraceDetail,
isPrivate: true,
key: 'TRACE_DETAIL_OLD',
},
{
path: ROUTES.SETTINGS,
exact: false,
@@ -390,17 +397,10 @@ const routes: AppRoutes[] = [
isPrivate: true,
key: 'WORKSPACE_ACCESS_RESTRICTED',
},
{
path: ROUTES.INTEGRATIONS_DETAIL,
exact: true,
component: IntegrationsDetailsPage,
isPrivate: true,
key: 'INTEGRATIONS_DETAIL',
},
{
path: ROUTES.INTEGRATIONS,
exact: true,
component: Integrations,
component: InstalledIntegrations,
isPrivate: true,
key: 'INTEGRATIONS',
},

View File

@@ -0,0 +1,19 @@
import axios from 'api';
import { ErrorResponse, SuccessResponse } from 'types/api';
const removeAwsIntegrationAccount = async (
accountId: string,
): Promise<SuccessResponse<Record<string, never>> | ErrorResponse> => {
const response = await axios.post(
`/cloud-integrations/aws/accounts/${accountId}/disconnect`,
);
return {
statusCode: 200,
error: null,
message: response.data.status,
payload: response.data.data,
};
};
export default removeAwsIntegrationAccount;

View File

@@ -1,496 +0,0 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useMutation, useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
CreateDowntimeSchedule201,
DeleteDowntimeScheduleByIDPathParameters,
GetDowntimeScheduleByID200,
GetDowntimeScheduleByIDPathParameters,
ListDowntimeSchedules200,
ListDowntimeSchedulesParams,
RenderErrorResponseDTO,
RuletypesPostablePlannedMaintenanceDTO,
UpdateDowntimeScheduleByIDPathParameters,
} from '../sigNoz.schemas';
/**
* This endpoint lists all planned maintenance / downtime schedules
* @summary List downtime schedules
*/
export const listDowntimeSchedules = (
params?: ListDowntimeSchedulesParams,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<ListDowntimeSchedules200>({
url: `/api/v1/downtime_schedules`,
method: 'GET',
params,
signal,
});
};
export const getListDowntimeSchedulesQueryKey = (
params?: ListDowntimeSchedulesParams,
) => {
return [`/api/v1/downtime_schedules`, ...(params ? [params] : [])] as const;
};
export const getListDowntimeSchedulesQueryOptions = <
TData = Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
params?: ListDowntimeSchedulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getListDowntimeSchedulesQueryKey(params);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof listDowntimeSchedules>>
> = ({ signal }) => listDowntimeSchedules(params, signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListDowntimeSchedulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listDowntimeSchedules>>
>;
export type ListDowntimeSchedulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List downtime schedules
*/
export function useListDowntimeSchedules<
TData = Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
params?: ListDowntimeSchedulesParams,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof listDowntimeSchedules>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListDowntimeSchedulesQueryOptions(params, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List downtime schedules
*/
export const invalidateListDowntimeSchedules = async (
queryClient: QueryClient,
params?: ListDowntimeSchedulesParams,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListDowntimeSchedulesQueryKey(params) },
options,
);
return queryClient;
};
/**
* This endpoint creates a new planned maintenance / downtime schedule
* @summary Create downtime schedule
*/
export const createDowntimeSchedule = (
ruletypesPostablePlannedMaintenanceDTO: BodyType<RuletypesPostablePlannedMaintenanceDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateDowntimeSchedule201>({
url: `/api/v1/downtime_schedules`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostablePlannedMaintenanceDTO,
signal,
});
};
export const getCreateDowntimeScheduleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
TContext
> => {
const mutationKey = ['createDowntimeSchedule'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
{ data: BodyType<RuletypesPostablePlannedMaintenanceDTO> }
> = (props) => {
const { data } = props ?? {};
return createDowntimeSchedule(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateDowntimeScheduleMutationResult = NonNullable<
Awaited<ReturnType<typeof createDowntimeSchedule>>
>;
export type CreateDowntimeScheduleMutationBody = BodyType<RuletypesPostablePlannedMaintenanceDTO>;
export type CreateDowntimeScheduleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create downtime schedule
*/
export const useCreateDowntimeSchedule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createDowntimeSchedule>>,
TError,
{ data: BodyType<RuletypesPostablePlannedMaintenanceDTO> },
TContext
> => {
const mutationOptions = getCreateDowntimeScheduleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes a downtime schedule by ID
* @summary Delete downtime schedule
*/
export const deleteDowntimeScheduleByID = ({
id,
}: DeleteDowntimeScheduleByIDPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'DELETE',
});
};
export const getDeleteDowntimeScheduleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
> => {
const mutationKey = ['deleteDowntimeScheduleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteDowntimeScheduleByID(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteDowntimeScheduleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>
>;
export type DeleteDowntimeScheduleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete downtime schedule
*/
export const useDeleteDowntimeScheduleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteDowntimeScheduleByID>>,
TError,
{ pathParams: DeleteDowntimeScheduleByIDPathParameters },
TContext
> => {
const mutationOptions = getDeleteDowntimeScheduleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns a downtime schedule by ID
* @summary Get downtime schedule by ID
*/
export const getDowntimeScheduleByID = (
{ id }: GetDowntimeScheduleByIDPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetDowntimeScheduleByID200>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'GET',
signal,
});
};
export const getGetDowntimeScheduleByIDQueryKey = ({
id,
}: GetDowntimeScheduleByIDPathParameters) => {
return [`/api/v1/downtime_schedules/${id}`] as const;
};
export const getGetDowntimeScheduleByIDQueryOptions = <
TData = Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ?? getGetDowntimeScheduleByIDQueryKey({ id });
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>
> = ({ signal }) => getDowntimeScheduleByID({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetDowntimeScheduleByIDQueryResult = NonNullable<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>
>;
export type GetDowntimeScheduleByIDQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get downtime schedule by ID
*/
export function useGetDowntimeScheduleByID<
TData = Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getDowntimeScheduleByID>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetDowntimeScheduleByIDQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get downtime schedule by ID
*/
export const invalidateGetDowntimeScheduleByID = async (
queryClient: QueryClient,
{ id }: GetDowntimeScheduleByIDPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetDowntimeScheduleByIDQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint updates a downtime schedule by ID
* @summary Update downtime schedule
*/
export const updateDowntimeScheduleByID = (
{ id }: UpdateDowntimeScheduleByIDPathParameters,
ruletypesPostablePlannedMaintenanceDTO: BodyType<RuletypesPostablePlannedMaintenanceDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v1/downtime_schedules/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostablePlannedMaintenanceDTO,
});
};
export const getUpdateDowntimeScheduleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
},
TContext
> => {
const mutationKey = ['updateDowntimeScheduleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateDowntimeScheduleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateDowntimeScheduleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>
>;
export type UpdateDowntimeScheduleByIDMutationBody = BodyType<RuletypesPostablePlannedMaintenanceDTO>;
export type UpdateDowntimeScheduleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update downtime schedule
*/
export const useUpdateDowntimeScheduleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateDowntimeScheduleByID>>,
TError,
{
pathParams: UpdateDowntimeScheduleByIDPathParameters;
data: BodyType<RuletypesPostablePlannedMaintenanceDTO>;
},
TContext
> => {
const mutationOptions = getUpdateDowntimeScheduleByIDMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -6,24 +6,17 @@
*/
import type {
InvalidateOptions,
MutationFunction,
QueryClient,
QueryFunction,
QueryKey,
UseMutationOptions,
UseMutationResult,
UseQueryOptions,
UseQueryResult,
} from 'react-query';
import { useMutation, useQuery } from 'react-query';
import { useQuery } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import type { ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
CreateRule201,
DeleteRuleByIDPathParameters,
GetRuleByID200,
GetRuleByIDPathParameters,
GetRuleHistoryFilterKeys200,
GetRuleHistoryFilterKeysParams,
GetRuleHistoryFilterKeysPathParameters,
@@ -42,548 +35,9 @@ import type {
GetRuleHistoryTopContributors200,
GetRuleHistoryTopContributorsParams,
GetRuleHistoryTopContributorsPathParameters,
ListRules200,
PatchRuleByID200,
PatchRuleByIDPathParameters,
RenderErrorResponseDTO,
RuletypesPostableRuleDTO,
TestRule200,
UpdateRuleByIDPathParameters,
} from '../sigNoz.schemas';
/**
* This endpoint lists all alert rules with their current evaluation state
* @summary List alert rules
*/
export const listRules = (signal?: AbortSignal) => {
return GeneratedAPIInstance<ListRules200>({
url: `/api/v2/rules`,
method: 'GET',
signal,
});
};
export const getListRulesQueryKey = () => {
return [`/api/v2/rules`] as const;
};
export const getListRulesQueryOptions = <
TData = Awaited<ReturnType<typeof listRules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listRules>>, TError, TData>;
}) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getListRulesQueryKey();
const queryFn: QueryFunction<Awaited<ReturnType<typeof listRules>>> = ({
signal,
}) => listRules(signal);
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listRules>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type ListRulesQueryResult = NonNullable<
Awaited<ReturnType<typeof listRules>>
>;
export type ListRulesQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary List alert rules
*/
export function useListRules<
TData = Awaited<ReturnType<typeof listRules>>,
TError = ErrorType<RenderErrorResponseDTO>
>(options?: {
query?: UseQueryOptions<Awaited<ReturnType<typeof listRules>>, TError, TData>;
}): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getListRulesQueryOptions(options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary List alert rules
*/
export const invalidateListRules = async (
queryClient: QueryClient,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getListRulesQueryKey() },
options,
);
return queryClient;
};
/**
* This endpoint creates a new alert rule
* @summary Create alert rule
*/
export const createRule = (
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<CreateRule201>({
url: `/api/v2/rules`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
signal,
});
};
export const getCreateRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationKey = ['createRule'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof createRule>>,
{ data: BodyType<RuletypesPostableRuleDTO> }
> = (props) => {
const { data } = props ?? {};
return createRule(data);
};
return { mutationFn, ...mutationOptions };
};
export type CreateRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof createRule>>
>;
export type CreateRuleMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type CreateRuleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Create alert rule
*/
export const useCreateRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof createRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationOptions = getCreateRuleMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint deletes an alert rule by ID
* @summary Delete alert rule
*/
export const deleteRuleByID = ({ id }: DeleteRuleByIDPathParameters) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/rules/${id}`,
method: 'DELETE',
});
};
export const getDeleteRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
> => {
const mutationKey = ['deleteRuleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof deleteRuleByID>>,
{ pathParams: DeleteRuleByIDPathParameters }
> = (props) => {
const { pathParams } = props ?? {};
return deleteRuleByID(pathParams);
};
return { mutationFn, ...mutationOptions };
};
export type DeleteRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof deleteRuleByID>>
>;
export type DeleteRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Delete alert rule
*/
export const useDeleteRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof deleteRuleByID>>,
TError,
{ pathParams: DeleteRuleByIDPathParameters },
TContext
> => {
const mutationOptions = getDeleteRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint returns an alert rule by ID
* @summary Get alert rule by ID
*/
export const getRuleByID = (
{ id }: GetRuleByIDPathParameters,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetRuleByID200>({
url: `/api/v2/rules/${id}`,
method: 'GET',
signal,
});
};
export const getGetRuleByIDQueryKey = ({ id }: GetRuleByIDPathParameters) => {
return [`/api/v2/rules/${id}`] as const;
};
export const getGetRuleByIDQueryOptions = <
TData = Awaited<ReturnType<typeof getRuleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
>;
},
) => {
const { query: queryOptions } = options ?? {};
const queryKey = queryOptions?.queryKey ?? getGetRuleByIDQueryKey({ id });
const queryFn: QueryFunction<Awaited<ReturnType<typeof getRuleByID>>> = ({
signal,
}) => getRuleByID({ id }, signal);
return {
queryKey,
queryFn,
enabled: !!id,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
> & { queryKey: QueryKey };
};
export type GetRuleByIDQueryResult = NonNullable<
Awaited<ReturnType<typeof getRuleByID>>
>;
export type GetRuleByIDQueryError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get alert rule by ID
*/
export function useGetRuleByID<
TData = Awaited<ReturnType<typeof getRuleByID>>,
TError = ErrorType<RenderErrorResponseDTO>
>(
{ id }: GetRuleByIDPathParameters,
options?: {
query?: UseQueryOptions<
Awaited<ReturnType<typeof getRuleByID>>,
TError,
TData
>;
},
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
const queryOptions = getGetRuleByIDQueryOptions({ id }, options);
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & {
queryKey: QueryKey;
};
query.queryKey = queryOptions.queryKey;
return query;
}
/**
* @summary Get alert rule by ID
*/
export const invalidateGetRuleByID = async (
queryClient: QueryClient,
{ id }: GetRuleByIDPathParameters,
options?: InvalidateOptions,
): Promise<QueryClient> => {
await queryClient.invalidateQueries(
{ queryKey: getGetRuleByIDQueryKey({ id }) },
options,
);
return queryClient;
};
/**
* This endpoint applies a partial update to an alert rule by ID
* @summary Patch alert rule
*/
export const patchRuleByID = (
{ id }: PatchRuleByIDPathParameters,
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
) => {
return GeneratedAPIInstance<PatchRuleByID200>({
url: `/api/v2/rules/${id}`,
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
});
};
export const getPatchRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationKey = ['patchRuleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof patchRuleByID>>,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return patchRuleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type PatchRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof patchRuleByID>>
>;
export type PatchRuleByIDMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type PatchRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Patch alert rule
*/
export const usePatchRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof patchRuleByID>>,
TError,
{
pathParams: PatchRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationOptions = getPatchRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* This endpoint updates an alert rule by ID
* @summary Update alert rule
*/
export const updateRuleByID = (
{ id }: UpdateRuleByIDPathParameters,
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
) => {
return GeneratedAPIInstance<void>({
url: `/api/v2/rules/${id}`,
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
});
};
export const getUpdateRuleByIDMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationKey = ['updateRuleByID'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof updateRuleByID>>,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return updateRuleByID(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type UpdateRuleByIDMutationResult = NonNullable<
Awaited<ReturnType<typeof updateRuleByID>>
>;
export type UpdateRuleByIDMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type UpdateRuleByIDMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Update alert rule
*/
export const useUpdateRuleByID = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof updateRuleByID>>,
TError,
{
pathParams: UpdateRuleByIDPathParameters;
data: BodyType<RuletypesPostableRuleDTO>;
},
TContext
> => {
const mutationOptions = getUpdateRuleByIDMutationOptions(options);
return useMutation(mutationOptions);
};
/**
* Returns distinct label keys from rule history entries for the selected range.
* @summary Get rule history filter keys
@@ -1288,87 +742,3 @@ export const invalidateGetRuleHistoryTopContributors = async (
return queryClient;
};
/**
* This endpoint fires a test notification for the given rule definition
* @summary Test alert rule
*/
export const testRule = (
ruletypesPostableRuleDTO: BodyType<RuletypesPostableRuleDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<TestRule200>({
url: `/api/v2/rules/test`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: ruletypesPostableRuleDTO,
signal,
});
};
export const getTestRuleMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationKey = ['testRule'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof testRule>>,
{ data: BodyType<RuletypesPostableRuleDTO> }
> = (props) => {
const { data } = props ?? {};
return testRule(data);
};
return { mutationFn, ...mutationOptions };
};
export type TestRuleMutationResult = NonNullable<
Awaited<ReturnType<typeof testRule>>
>;
export type TestRuleMutationBody = BodyType<RuletypesPostableRuleDTO>;
export type TestRuleMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Test alert rule
*/
export const useTestRule = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof testRule>>,
TError,
{ data: BodyType<RuletypesPostableRuleDTO> },
TContext
> => {
const mutationOptions = getTestRuleMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -4529,20 +4529,6 @@ export interface RulestatehistorytypesGettableRuleStateWindowDTO {
state: RuletypesAlertStateDTO;
}
export interface RuletypesAlertCompositeQueryDTO {
panelType: RuletypesPanelTypeDTO;
/**
* @type array
* @nullable true
*/
queries: Querybuildertypesv5QueryEnvelopeDTO[] | null;
queryType: RuletypesQueryTypeDTO;
/**
* @type string
*/
unit?: string;
}
export enum RuletypesAlertStateDTO {
inactive = 'inactive',
pending = 'pending',
@@ -4551,513 +4537,6 @@ export enum RuletypesAlertStateDTO {
nodata = 'nodata',
disabled = 'disabled',
}
export enum RuletypesAlertTypeDTO {
METRIC_BASED_ALERT = 'METRIC_BASED_ALERT',
TRACES_BASED_ALERT = 'TRACES_BASED_ALERT',
LOGS_BASED_ALERT = 'LOGS_BASED_ALERT',
EXCEPTIONS_BASED_ALERT = 'EXCEPTIONS_BASED_ALERT',
}
export interface RuletypesBasicRuleThresholdDTO {
/**
* @type array
* @nullable true
*/
channels?: string[] | null;
matchType: RuletypesMatchTypeDTO;
/**
* @type string
*/
name: string;
op: RuletypesCompareOperatorDTO;
/**
* @type number
* @nullable true
*/
recoveryTarget?: number | null;
/**
* @type number
* @nullable true
*/
target: number | null;
/**
* @type string
*/
targetUnit?: string;
}
/**
* @nullable
*/
export type RuletypesBasicRuleThresholdsDTO =
| RuletypesBasicRuleThresholdDTO[]
| null;
export enum RuletypesCompareOperatorDTO {
above = 'above',
below = 'below',
equal = 'equal',
not_equal = 'not_equal',
outside_bounds = 'outside_bounds',
}
export interface RuletypesCumulativeScheduleDTO {
/**
* @type integer
* @nullable true
*/
day?: number | null;
/**
* @type integer
* @nullable true
*/
hour?: number | null;
/**
* @type integer
* @nullable true
*/
minute?: number | null;
type: RuletypesScheduleTypeDTO;
/**
* @type integer
* @nullable true
*/
weekday?: number | null;
}
export interface RuletypesCumulativeWindowDTO {
/**
* @type string
*/
frequency: string;
schedule: RuletypesCumulativeScheduleDTO;
/**
* @type string
*/
timezone: string;
}
export interface RuletypesEvaluationCumulativeDTO {
kind?: RuletypesEvaluationKindDTO;
spec?: RuletypesCumulativeWindowDTO;
}
export type RuletypesEvaluationEnvelopeDTO =
| (RuletypesEvaluationRollingDTO & {
kind: RuletypesEvaluationKindDTO;
spec: unknown;
})
| (RuletypesEvaluationCumulativeDTO & {
kind: RuletypesEvaluationKindDTO;
spec: unknown;
});
export enum RuletypesEvaluationKindDTO {
rolling = 'rolling',
cumulative = 'cumulative',
}
export interface RuletypesEvaluationRollingDTO {
kind?: RuletypesEvaluationKindDTO;
spec?: RuletypesRollingWindowDTO;
}
export interface RuletypesGettableTestRuleDTO {
/**
* @type integer
*/
alertCount?: number;
/**
* @type string
*/
message?: string;
}
export enum RuletypesMaintenanceKindDTO {
fixed = 'fixed',
recurring = 'recurring',
}
export enum RuletypesMaintenanceStatusDTO {
active = 'active',
upcoming = 'upcoming',
expired = 'expired',
}
export enum RuletypesMatchTypeDTO {
at_least_once = 'at_least_once',
all_the_times = 'all_the_times',
on_average = 'on_average',
in_total = 'in_total',
last = 'last',
}
export interface RuletypesNotificationSettingsDTO {
/**
* @type array
*/
groupBy?: string[];
/**
* @type string
*/
newGroupEvalDelay?: string;
renotify?: RuletypesRenotifyDTO;
/**
* @type boolean
*/
usePolicy?: boolean;
}
export enum RuletypesPanelTypeDTO {
value = 'value',
table = 'table',
graph = 'graph',
}
export interface RuletypesPlannedMaintenanceDTO {
/**
* @type array
* @nullable true
*/
alertIds?: string[] | null;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
id: string;
kind: RuletypesMaintenanceKindDTO;
/**
* @type string
*/
name: string;
schedule: RuletypesScheduleDTO;
status: RuletypesMaintenanceStatusDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
}
export interface RuletypesPostablePlannedMaintenanceDTO {
/**
* @type array
* @nullable true
*/
alertIds?: string[] | null;
/**
* @type string
*/
description?: string;
/**
* @type string
*/
name: string;
schedule: RuletypesScheduleDTO;
}
export type RuletypesPostableRuleDTOAnnotations = { [key: string]: string };
export type RuletypesPostableRuleDTOLabels = { [key: string]: string };
export interface RuletypesPostableRuleDTO {
/**
* @type string
*/
alert: string;
alertType?: RuletypesAlertTypeDTO;
/**
* @type object
*/
annotations?: RuletypesPostableRuleDTOAnnotations;
condition: RuletypesRuleConditionDTO;
/**
* @type string
*/
description?: string;
/**
* @type boolean
*/
disabled?: boolean;
/**
* @type string
*/
evalWindow?: string;
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
*/
frequency?: string;
/**
* @type object
*/
labels?: RuletypesPostableRuleDTOLabels;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array
*/
preferredChannels?: string[];
ruleType: RuletypesRuleTypeDTO;
/**
* @type string
*/
schemaVersion?: string;
/**
* @type string
*/
source?: string;
/**
* @type string
*/
version?: string;
}
export enum RuletypesQueryTypeDTO {
builder = 'builder',
clickhouse_sql = 'clickhouse_sql',
promql = 'promql',
}
export interface RuletypesRecurrenceDTO {
/**
* @type string
*/
duration: string;
/**
* @type string
* @format date-time
* @nullable true
*/
endTime?: Date | null;
/**
* @type array
* @nullable true
*/
repeatOn?: RuletypesRepeatOnDTO[] | null;
repeatType: RuletypesRepeatTypeDTO;
/**
* @type string
* @format date-time
*/
startTime: Date;
}
export interface RuletypesRenotifyDTO {
/**
* @type array
*/
alertStates?: RuletypesAlertStateDTO[];
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type string
*/
interval?: string;
}
export enum RuletypesRepeatOnDTO {
sunday = 'sunday',
monday = 'monday',
tuesday = 'tuesday',
wednesday = 'wednesday',
thursday = 'thursday',
friday = 'friday',
saturday = 'saturday',
}
export enum RuletypesRepeatTypeDTO {
daily = 'daily',
weekly = 'weekly',
monthly = 'monthly',
}
export interface RuletypesRollingWindowDTO {
/**
* @type string
*/
evalWindow: string;
/**
* @type string
*/
frequency: string;
}
export type RuletypesRuleDTOAnnotations = { [key: string]: string };
export type RuletypesRuleDTOLabels = { [key: string]: string };
export interface RuletypesRuleDTO {
/**
* @type string
*/
alert: string;
alertType?: RuletypesAlertTypeDTO;
/**
* @type object
*/
annotations?: RuletypesRuleDTOAnnotations;
condition: RuletypesRuleConditionDTO;
/**
* @type string
* @format date-time
*/
createdAt?: Date;
/**
* @type string
*/
createdBy?: string;
/**
* @type string
*/
description?: string;
/**
* @type boolean
*/
disabled?: boolean;
/**
* @type string
*/
evalWindow?: string;
evaluation?: RuletypesEvaluationEnvelopeDTO;
/**
* @type string
*/
frequency?: string;
/**
* @type string
*/
id: string;
/**
* @type object
*/
labels?: RuletypesRuleDTOLabels;
notificationSettings?: RuletypesNotificationSettingsDTO;
/**
* @type array
*/
preferredChannels?: string[];
ruleType: RuletypesRuleTypeDTO;
/**
* @type string
*/
schemaVersion?: string;
/**
* @type string
*/
source?: string;
state: RuletypesAlertStateDTO;
/**
* @type string
* @format date-time
*/
updatedAt?: Date;
/**
* @type string
*/
updatedBy?: string;
/**
* @type string
*/
version?: string;
}
export interface RuletypesRuleConditionDTO {
/**
* @type integer
* @minimum 0
*/
absentFor?: number;
/**
* @type boolean
*/
alertOnAbsent?: boolean;
/**
* @type string
*/
algorithm?: string;
compositeQuery: RuletypesAlertCompositeQueryDTO;
matchType: RuletypesMatchTypeDTO;
op: RuletypesCompareOperatorDTO;
/**
* @type boolean
*/
requireMinPoints?: boolean;
/**
* @type integer
*/
requiredNumPoints?: number;
seasonality?: RuletypesSeasonalityDTO;
/**
* @type string
*/
selectedQueryName?: string;
/**
* @type number
* @nullable true
*/
target?: number | null;
/**
* @type string
*/
targetUnit?: string;
thresholds?: RuletypesRuleThresholdDataDTO;
}
export type RuletypesRuleThresholdDataDTO = RuletypesThresholdBasicDTO & {
kind: RuletypesThresholdKindDTO;
spec: unknown;
};
export enum RuletypesRuleTypeDTO {
threshold_rule = 'threshold_rule',
promql_rule = 'promql_rule',
anomaly_rule = 'anomaly_rule',
}
export interface RuletypesScheduleDTO {
/**
* @type string
* @format date-time
*/
endTime?: Date;
recurrence?: RuletypesRecurrenceDTO;
/**
* @type string
* @format date-time
*/
startTime?: Date;
/**
* @type string
*/
timezone: string;
}
export enum RuletypesScheduleTypeDTO {
hourly = 'hourly',
daily = 'daily',
weekly = 'weekly',
monthly = 'monthly',
}
export enum RuletypesSeasonalityDTO {
hourly = 'hourly',
daily = 'daily',
weekly = 'weekly',
}
export interface RuletypesThresholdBasicDTO {
kind?: RuletypesThresholdKindDTO;
spec?: RuletypesBasicRuleThresholdsDTO;
}
export enum RuletypesThresholdKindDTO {
basic = 'basic',
}
export interface ServiceaccounttypesGettableFactorAPIKeyDTO {
/**
* @type string
@@ -5332,6 +4811,248 @@ export interface TelemetrytypesTelemetryFieldValuesDTO {
export type TimeDurationDTO = number;
export type TracedetailtypesEventDTOAttributeMap = { [key: string]: unknown };
export interface TracedetailtypesEventDTO {
/**
* @type object
*/
attributeMap?: TracedetailtypesEventDTOAttributeMap;
/**
* @type boolean
*/
isError?: boolean;
/**
* @type string
*/
name?: string;
/**
* @type integer
* @minimum 0
*/
timeUnixNano?: number;
}
export interface TracedetailtypesWaterfallRequestDTO {
/**
* @type integer
* @minimum 0
*/
limit?: number;
/**
* @type string
*/
selectedSpanId?: string;
/**
* @type array
* @nullable true
*/
uncollapsedSpans?: string[] | null;
}
/**
* @nullable
*/
export type TracedetailtypesWaterfallResponseDTOServiceNameToTotalDurationMap = {
[key: string]: number;
} | null;
export interface TracedetailtypesWaterfallResponseDTO {
/**
* @type integer
* @minimum 0
*/
endTimestampMillis?: number;
/**
* @type boolean
*/
hasMissingSpans?: boolean;
/**
* @type boolean
*/
hasMore?: boolean;
/**
* @type string
*/
rootServiceEntryPoint?: string;
/**
* @type string
*/
rootServiceName?: string;
/**
* @type object
* @nullable true
*/
serviceNameToTotalDurationMap?: TracedetailtypesWaterfallResponseDTOServiceNameToTotalDurationMap;
/**
* @type array
* @nullable true
*/
spans?: TracedetailtypesWaterfallSpanDTO[] | null;
/**
* @type integer
* @minimum 0
*/
startTimestampMillis?: number;
/**
* @type integer
* @minimum 0
*/
totalErrorSpansCount?: number;
/**
* @type integer
* @minimum 0
*/
totalSpansCount?: number;
/**
* @type array
* @nullable true
*/
uncollapsedSpans?: string[] | null;
}
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOAttributes = {
[key: string]: unknown;
} | null;
/**
* @nullable
*/
export type TracedetailtypesWaterfallSpanDTOResource = {
[key: string]: string;
} | null;
export interface TracedetailtypesWaterfallSpanDTO {
/**
* @type object
* @nullable true
*/
attributes?: TracedetailtypesWaterfallSpanDTOAttributes;
/**
* @type string
*/
db_name?: string;
/**
* @type string
*/
db_operation?: string;
/**
* @type integer
* @minimum 0
*/
duration_nano?: number;
/**
* @type array
* @nullable true
*/
events?: TracedetailtypesEventDTO[] | null;
/**
* @type string
*/
external_http_method?: string;
/**
* @type string
*/
external_http_url?: string;
/**
* @type integer
* @minimum 0
*/
flags?: number;
/**
* @type boolean
*/
has_children?: boolean;
/**
* @type boolean
*/
has_error?: boolean;
/**
* @type string
*/
http_host?: string;
/**
* @type string
*/
http_method?: string;
/**
* @type string
*/
http_url?: string;
/**
* @type string
*/
is_remote?: string;
/**
* @type integer
* @format int32
*/
kind?: number;
/**
* @type string
*/
kind_string?: string;
/**
* @type integer
* @minimum 0
*/
level?: number;
/**
* @type string
*/
name?: string;
/**
* @type string
*/
parent_span_id?: string;
/**
* @type object
* @nullable true
*/
resource?: TracedetailtypesWaterfallSpanDTOResource;
/**
* @type string
*/
response_status_code?: string;
/**
* @type string
*/
span_id?: string;
/**
* @type integer
*/
status_code?: number;
/**
* @type string
*/
status_code_string?: string;
/**
* @type string
*/
status_message?: string;
/**
* @type integer
* @minimum 0
*/
sub_tree_node_count?: number;
/**
* @type integer
* @minimum 0
*/
timestamp?: number;
/**
* @type string
*/
trace_id?: string;
/**
* @type string
*/
trace_state?: string;
}
export interface TypesAlertStatusDTO {
/**
* @type array
@@ -5977,57 +5698,6 @@ export type DeleteAuthDomainPathParameters = {
export type UpdateAuthDomainPathParameters = {
id: string;
};
export type ListDowntimeSchedulesParams = {
/**
* @type boolean
* @nullable true
* @description undefined
*/
active?: boolean | null;
/**
* @type boolean
* @nullable true
* @description undefined
*/
recurring?: boolean | null;
};
export type ListDowntimeSchedules200 = {
/**
* @type array
*/
data: RuletypesPlannedMaintenanceDTO[];
/**
* @type string
*/
status: string;
};
export type CreateDowntimeSchedule201 = {
data: RuletypesPlannedMaintenanceDTO;
/**
* @type string
*/
status: string;
};
export type DeleteDowntimeScheduleByIDPathParameters = {
id: string;
};
export type GetDowntimeScheduleByIDPathParameters = {
id: string;
};
export type GetDowntimeScheduleByID200 = {
data: RuletypesPlannedMaintenanceDTO;
/**
* @type string
*/
status: string;
};
export type UpdateDowntimeScheduleByIDPathParameters = {
id: string;
};
export type HandleExportRawDataPOSTParams = {
/**
* @enum csv,jsonl
@@ -6825,53 +6495,6 @@ export type GetUsersByRoleID200 = {
status: string;
};
export type ListRules200 = {
/**
* @type array
*/
data: RuletypesRuleDTO[];
/**
* @type string
*/
status: string;
};
export type CreateRule201 = {
data: RuletypesRuleDTO;
/**
* @type string
*/
status: string;
};
export type DeleteRuleByIDPathParameters = {
id: string;
};
export type GetRuleByIDPathParameters = {
id: string;
};
export type GetRuleByID200 = {
data: RuletypesRuleDTO;
/**
* @type string
*/
status: string;
};
export type PatchRuleByIDPathParameters = {
id: string;
};
export type PatchRuleByID200 = {
data: RuletypesRuleDTO;
/**
* @type string
*/
status: string;
};
export type UpdateRuleByIDPathParameters = {
id: string;
};
export type GetRuleHistoryFilterKeysPathParameters = {
id: string;
};
@@ -7142,14 +6765,6 @@ export type GetRuleHistoryTopContributors200 = {
status: string;
};
export type TestRule200 = {
data: RuletypesGettableTestRuleDTO;
/**
* @type string
*/
status: string;
};
export type GetSessionContext200 = {
data: AuthtypesSessionContextDTO;
/**
@@ -7258,6 +6873,17 @@ export type GetHosts200 = {
status: string;
};
export type GetWaterfallPathParameters = {
traceID: string;
};
export type GetWaterfall200 = {
data: TracedetailtypesWaterfallResponseDTO;
/**
* @type string
*/
status: string;
};
export type QueryRangeV5200 = {
data: Querybuildertypesv5QueryRangeResponseDTO;
/**

View File

@@ -0,0 +1,121 @@
/**
* ! Do not edit manually
* * The file has been auto-generated using Orval for SigNoz
* * regenerate with 'yarn generate:api'
* SigNoz
*/
import type {
MutationFunction,
UseMutationOptions,
UseMutationResult,
} from 'react-query';
import { useMutation } from 'react-query';
import type { BodyType, ErrorType } from '../../../generatedAPIInstance';
import { GeneratedAPIInstance } from '../../../generatedAPIInstance';
import type {
GetWaterfall200,
GetWaterfallPathParameters,
RenderErrorResponseDTO,
TracedetailtypesWaterfallRequestDTO,
} from '../sigNoz.schemas';
/**
* Returns the waterfall view of spans for a given trace ID with tree structure, metadata, and windowed pagination
* @summary Get waterfall view for a trace
*/
export const getWaterfall = (
{ traceID }: GetWaterfallPathParameters,
tracedetailtypesWaterfallRequestDTO: BodyType<TracedetailtypesWaterfallRequestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<GetWaterfall200>({
url: `/api/v3/traces/${traceID}/waterfall`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: tracedetailtypesWaterfallRequestDTO,
signal,
});
};
export const getGetWaterfallMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
},
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
},
TContext
> => {
const mutationKey = ['getWaterfall'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof getWaterfall>>,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
}
> = (props) => {
const { pathParams, data } = props ?? {};
return getWaterfall(pathParams, data);
};
return { mutationFn, ...mutationOptions };
};
export type GetWaterfallMutationResult = NonNullable<
Awaited<ReturnType<typeof getWaterfall>>
>;
export type GetWaterfallMutationBody = BodyType<TracedetailtypesWaterfallRequestDTO>;
export type GetWaterfallMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Get waterfall view for a trace
*/
export const useGetWaterfall = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
},
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof getWaterfall>>,
TError,
{
pathParams: GetWaterfallPathParameters;
data: BodyType<TracedetailtypesWaterfallRequestDTO>;
},
TContext
> => {
const mutationOptions = getGetWaterfallMutationOptions(options);
return useMutation(mutationOptions);
};

View File

@@ -2,7 +2,7 @@ import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError, AxiosResponse } from 'axios';
import { baseAutoCompleteIdKeysOrder } from 'constants/queryBuilder';
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8s/constants';
import { K8sCategory } from 'container/InfraMonitoringK8s/constants';
import { createIdFromObjectFields } from 'lib/createIdFromObjectFields';
import { ErrorResponse, SuccessResponse } from 'types/api';
import {
@@ -12,7 +12,7 @@ import {
export const getHostAttributeKeys = async (
searchText = '',
entity: InfraMonitoringEntity,
entity: K8sCategory,
): Promise<SuccessResponse<IQueryAutocompleteResponse> | ErrorResponse> => {
try {
const response: AxiosResponse<{

View File

@@ -33,8 +33,6 @@ export interface HostData {
hostName: string;
active: boolean;
os: string;
/** Present when the list API returns grouped rows or extra resource attributes. */
meta?: Record<string, string>;
cpu: number;
cpuTimeSeries: TimeSeries;
memory: number;

View File

@@ -1,14 +1,13 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sNodesListPayload {
export interface K8sClustersListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
@@ -19,24 +18,23 @@ export interface K8sNodesListPayload {
};
}
export interface K8sNodeData {
nodeUID: string;
nodeCPUUsage: number;
nodeCPUAllocatable: number;
nodeMemoryUsage: number;
nodeMemoryAllocatable: number;
export interface K8sClustersData {
clusterUID: string;
cpuUsage: number;
cpuAllocatable: number;
memoryUsage: number;
memoryAllocatable: number;
meta: {
k8s_node_name: string;
k8s_node_uid: string;
k8s_cluster_name: string;
k8s_cluster_uid: string;
};
}
export interface K8sNodesListResponse {
export interface K8sClustersListResponse {
status: string;
data: {
type: string;
records: K8sNodeData[];
records: K8sClustersData[];
groups: null;
total: number;
sentAnyHostMetricsData: boolean;
@@ -44,32 +42,30 @@ export interface K8sNodesListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const nodesMetaMap = [
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.node.uid', under: 'k8s_node_uid' },
export const clustersMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.cluster.uid', under: 'k8s_cluster_uid' },
] as const;
export function mapNodesMeta(
export function mapClustersMeta(
raw: Record<string, unknown>,
): K8sNodeData['meta'] {
): K8sClustersData['meta'] {
const out: Record<string, unknown> = { ...raw };
nodesMetaMap.forEach(({ dot, under }) => {
clustersMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sNodeData['meta'];
return out as K8sClustersData['meta'];
}
export const getK8sNodesList = async (
props: K8sBaseFilters,
export const getK8sClustersList = async (
props: K8sClustersListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
): Promise<SuccessResponse<K8sClustersListResponse> | ErrorResponse> => {
try {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
@@ -104,16 +100,16 @@ export const getK8sNodesList = async (
}
: props;
const response = await axios.post('/nodes/list', requestProps, {
const response = await axios.post('/clusters/list', requestProps, {
signal,
headers,
});
const payload: K8sNodesListResponse = response.data;
const payload: K8sClustersListResponse = response.data;
// one-liner to map dot→underscore
// one-liner meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapNodesMeta(record.meta as Record<string, unknown>),
meta: mapClustersMeta(record.meta as Record<string, unknown>),
}));
return {

View File

@@ -1,10 +1,22 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sDaemonSetsListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: {
columnName: string;
order: 'asc' | 'desc';
};
}
export interface K8sDaemonSetsData {
daemonSetName: string;
@@ -36,7 +48,6 @@ export interface K8sDaemonSetsListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const daemonSetsMetaMap = [
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
@@ -57,43 +68,45 @@ export function mapDaemonSetsMeta(
}
export const getK8sDaemonSetsList = async (
props: K8sBaseFilters,
props: K8sDaemonSetsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sDaemonSetsListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
// filter prep (unchanged)…
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/daemonsets/list', requestProps, {
signal,
@@ -101,6 +114,7 @@ export const getK8sDaemonSetsList = async (
});
const payload: K8sDaemonSetsListResponse = response.data;
// single-line meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapDaemonSetsMeta(record.meta as Record<string, unknown>),

View File

@@ -1,12 +1,11 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sDeploymentsListPayload {
filters: TagFilter;
@@ -49,7 +48,6 @@ export interface K8sDeploymentsListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const deploymentsMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
@@ -70,43 +68,44 @@ export function mapDeploymentsMeta(
}
export const getK8sDeploymentsList = async (
props: K8sBaseFilters,
props: K8sDeploymentsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sDeploymentsListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/deployments/list', requestProps, {
signal,
@@ -114,6 +113,7 @@ export const getK8sDeploymentsList = async (
});
const payload: K8sDeploymentsListResponse = response.data;
// single-line mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapDeploymentsMeta(record.meta as Record<string, unknown>),

View File

@@ -1,10 +1,22 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sJobsListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: {
columnName: string;
order: 'asc' | 'desc';
};
}
export interface K8sJobsData {
jobName: string;
@@ -38,7 +50,6 @@ export interface K8sJobsListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const jobsMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
@@ -57,43 +68,44 @@ export function mapJobsMeta(raw: Record<string, unknown>): K8sJobsData['meta'] {
}
export const getK8sJobsList = async (
props: K8sBaseFilters,
props: K8sJobsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sJobsListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters?.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/jobs/list', requestProps, {
signal,
@@ -101,6 +113,7 @@ export const getK8sJobsList = async (
});
const payload: K8sJobsListResponse = response.data;
// one-liner meta mapping
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapJobsMeta(record.meta as Record<string, unknown>),

View File

@@ -1,12 +1,11 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sNamespacesListPayload {
filters: TagFilter;
@@ -41,7 +40,6 @@ export interface K8sNamespacesListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const namespacesMetaMap = [
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
@@ -61,43 +59,44 @@ export function mapNamespacesMeta(
}
export const getK8sNamespacesList = async (
props: K8sBaseFilters,
props: K8sNamespacesListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sNamespacesListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters?.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/namespaces/list', requestProps, {
signal,

View File

@@ -0,0 +1,127 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { UnderscoreToDotMap } from '../utils';
export interface K8sNodesListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: {
columnName: string;
order: 'asc' | 'desc';
};
}
export interface K8sNodesData {
nodeUID: string;
nodeCPUUsage: number;
nodeCPUAllocatable: number;
nodeMemoryUsage: number;
nodeMemoryAllocatable: number;
meta: {
k8s_node_name: string;
k8s_node_uid: string;
k8s_cluster_name: string;
};
}
export interface K8sNodesListResponse {
status: string;
data: {
type: string;
records: K8sNodesData[];
groups: null;
total: number;
sentAnyHostMetricsData: boolean;
isSendingK8SAgentMetrics: boolean;
};
}
export const nodesMetaMap = [
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.node.uid', under: 'k8s_node_uid' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapNodesMeta(
raw: Record<string, unknown>,
): K8sNodesData['meta'] {
const out: Record<string, unknown> = { ...raw };
nodesMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sNodesData['meta'];
}
export const getK8sNodesList = async (
props: K8sNodesListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sNodesListResponse> | ErrorResponse> => {
try {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/nodes/list', requestProps, {
signal,
headers,
});
const payload: K8sNodesListResponse = response.data;
// one-liner to map dot→underscore
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapNodesMeta(record.meta as Record<string, unknown>),
}));
return {
statusCode: 200,
error: null,
message: 'Success',
payload,
params: requestProps,
};
} catch (error) {
return ErrorResponseHandler(error as AxiosError);
}
};

View File

@@ -1,36 +1,21 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
// TODO(H4ad): Erase this whole file when migrating to openapi
export const podsMetaMap = [
{ dot: 'k8s.cronjob.name', under: 'k8s_cronjob_name' },
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapPodsMeta(raw: Record<string, unknown>): K8sPodsData['meta'] {
// clone everything
const out: Record<string, unknown> = { ...raw };
// overlay only the dot→under mappings
podsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sPodsData['meta'];
export interface K8sPodsListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: {
columnName: string;
order: 'asc' | 'desc';
};
}
export interface TimeSeriesValue {
@@ -86,44 +71,72 @@ export interface K8sPodsListResponse {
};
}
export const podsMetaMap = [
{ dot: 'k8s.cronjob.name', under: 'k8s_cronjob_name' },
{ dot: 'k8s.daemonset.name', under: 'k8s_daemonset_name' },
{ dot: 'k8s.deployment.name', under: 'k8s_deployment_name' },
{ dot: 'k8s.job.name', under: 'k8s_job_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
{ dot: 'k8s.node.name', under: 'k8s_node_name' },
{ dot: 'k8s.pod.name', under: 'k8s_pod_name' },
{ dot: 'k8s.pod.uid', under: 'k8s_pod_uid' },
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
{ dot: 'k8s.cluster.name', under: 'k8s_cluster_name' },
] as const;
export function mapPodsMeta(raw: Record<string, unknown>): K8sPodsData['meta'] {
// clone everything
const out: Record<string, unknown> = { ...raw };
// overlay only the dot→under mappings
podsMetaMap.forEach(({ dot, under }) => {
if (dot in raw) {
const v = raw[dot];
out[under] = typeof v === 'string' ? v : raw[under];
}
});
return out as K8sPodsData['meta'];
}
// getK8sPodsList
export const getK8sPodsList = async (
props: K8sBaseFilters,
props: K8sPodsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sPodsListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters?.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({
...item,
key: { ...item.key, key: mappedKey },
});
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/pods/list', requestProps, {
signal,

View File

@@ -1,10 +1,22 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sVolumesListPayload {
filters: TagFilter;
groupBy?: BaseAutocompleteData[];
offset?: number;
limit?: number;
orderBy?: {
columnName: string;
order: 'asc' | 'desc';
};
}
export interface K8sVolumesData {
persistentVolumeClaimName: string;
@@ -37,7 +49,6 @@ export interface K8sVolumesListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const volumesMetaMap: Array<{
dot: keyof Record<string, unknown>;
under: keyof K8sVolumesData['meta'];
@@ -57,8 +68,10 @@ export const volumesMetaMap: Array<{
export function mapVolumesMeta(
rawMeta: Record<string, unknown>,
): K8sVolumesData['meta'] {
// start with everything that was already there
const out: Record<string, unknown> = { ...rawMeta };
// for each dot→under rule, if the raw has the dot, overwrite the underscore
volumesMetaMap.forEach(({ dot, under }) => {
if (dot in rawMeta) {
const val = rawMeta[dot];
@@ -70,47 +83,42 @@ export function mapVolumesMeta(
}
export const getK8sVolumesList = async (
props: K8sBaseFilters,
props: K8sVolumesListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sVolumesListResponse> | ErrorResponse> => {
try {
const { orderBy, ...rest } = props;
const basePayload = {
...rest,
filters: props.filters ?? { items: [], op: 'and' },
...(orderBy != null ? { orderBy } : {}),
};
const requestProps = dotMetricsEnabled
? {
...basePayload,
filters: {
...basePayload.filters,
items: basePayload.filters.items.reduce<typeof basePayload.filters.items>(
(acc, item) => {
if (item.value === undefined) {
// Prepare filters
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
},
[] as typeof basePayload.filters.items,
),
},
}
: basePayload;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/pvcs/list', requestProps, {
signal,

View File

@@ -1,12 +1,11 @@
import axios from 'api';
import { ErrorResponseHandler } from 'api/ErrorResponseHandler';
import { UnderscoreToDotMap } from 'api/utils';
import { AxiosError } from 'axios';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { BaseAutocompleteData } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { K8sBaseFilters } from '../Base/types';
import { UnderscoreToDotMap } from '../utils';
export interface K8sStatefulSetsListPayload {
filters: TagFilter;
@@ -48,7 +47,6 @@ export interface K8sStatefulSetsListResponse {
};
}
// TODO(H4ad): Erase this whole file when migrating to openapi
export const statefulSetsMetaMap = [
{ dot: 'k8s.statefulset.name', under: 'k8s_statefulset_name' },
{ dot: 'k8s.namespace.name', under: 'k8s_namespace_name' },
@@ -68,37 +66,42 @@ export function mapStatefulSetsMeta(
}
export const getK8sStatefulSetsList = async (
props: K8sBaseFilters,
props: K8sStatefulSetsListPayload,
signal?: AbortSignal,
headers?: Record<string, string>,
dotMetricsEnabled = false,
): Promise<SuccessResponse<K8sStatefulSetsListResponse> | ErrorResponse> => {
try {
const requestProps = dotMetricsEnabled
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<TagFilter['items']>((acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
}, [] as TagFilter['items']),
},
}
: props;
// Prepare filters
const requestProps =
dotMetricsEnabled && Array.isArray(props.filters?.items)
? {
...props,
filters: {
...props.filters,
items: props.filters.items.reduce<typeof props.filters.items>(
(acc, item) => {
if (item.value === undefined) {
return acc;
}
if (
item.key &&
typeof item.key === 'object' &&
'key' in item.key &&
typeof item.key.key === 'string'
) {
const mappedKey = UnderscoreToDotMap[item.key.key] ?? item.key.key;
acc.push({ ...item, key: { ...item.key, key: mappedKey } });
} else {
acc.push(item);
}
return acc;
},
[] as typeof props.filters.items,
),
},
}
: props;
const response = await axios.post('/statefulsets/list', requestProps, {
signal,
@@ -106,6 +109,7 @@ export const getK8sStatefulSetsList = async (
});
const payload: K8sStatefulSetsListResponse = response.data;
// apply our helper
payload.data.records = payload.data.records.map((record) => ({
...record,
meta: mapStatefulSetsMeta(record.meta as Record<string, unknown>),

View File

@@ -0,0 +1,88 @@
import axios from 'api';
import {
CloudAccount,
Service,
ServiceData,
UpdateServiceConfigPayload,
UpdateServiceConfigResponse,
} from 'container/CloudIntegrationPage/ServicesSection/types';
import {
AccountConfigPayload,
AccountConfigResponse,
ConnectionParams,
ConnectionUrlResponse,
} from 'types/api/integrations/aws';
export const getAwsAccounts = async (): Promise<CloudAccount[]> => {
const response = await axios.get('/cloud-integrations/aws/accounts');
return response.data.data.accounts;
};
export const getAwsServices = async (
cloudAccountId?: string,
): Promise<Service[]> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get('/cloud-integrations/aws/services', {
params,
});
return response.data.data.services;
};
export const getServiceDetails = async (
serviceId: string,
cloudAccountId?: string,
): Promise<ServiceData> => {
const params = cloudAccountId
? { cloud_account_id: cloudAccountId }
: undefined;
const response = await axios.get(
`/cloud-integrations/aws/services/${serviceId}`,
{ params },
);
return response.data.data;
};
export const generateConnectionUrl = async (params: {
agent_config: { region: string };
account_config: { regions: string[] };
account_id?: string;
}): Promise<ConnectionUrlResponse> => {
const response = await axios.post(
'/cloud-integrations/aws/accounts/generate-connection-url',
params,
);
return response.data.data;
};
export const updateAccountConfig = async (
accountId: string,
payload: AccountConfigPayload,
): Promise<AccountConfigResponse> => {
const response = await axios.post<AccountConfigResponse>(
`/cloud-integrations/aws/accounts/${accountId}/config`,
payload,
);
return response.data;
};
export const updateServiceConfig = async (
serviceId: string,
payload: UpdateServiceConfigPayload,
): Promise<UpdateServiceConfigResponse> => {
const response = await axios.post<UpdateServiceConfigResponse>(
`/cloud-integrations/aws/services/${serviceId}/config`,
payload,
);
return response.data;
};
export const getConnectionParams = async (): Promise<ConnectionParams> => {
const response = await axios.get(
'/cloud-integrations/aws/accounts/generate-connection-params',
);
return response.data.data;
};

View File

@@ -0,0 +1,115 @@
/* eslint-disable sonarjs/cognitive-complexity */
import { ApiV3Instance as axios } from 'api';
import { omit } from 'lodash-es';
import { ErrorResponse, SuccessResponse } from 'types/api';
import { Event, Span } from 'types/api/trace/getTraceV2';
import {
GetTraceV3PayloadProps,
GetTraceV3SuccessResponse,
} from 'types/api/trace/getTraceV3';
// Transform a V3 snake_case span to the V2 camelCase Span shape
// V3 WaterfallSpan uses snake_case JSON keys (see pkg/types/tracedetailtypes/waterfall.go)
function transformSpan(raw: any): Span {
const resource: Record<string, string> = raw.resource || {};
const attributes: Record<string, any> = raw.attributes || {};
// Build tagMap from attributes (flattened string representation)
const tagMap: Record<string, string> = {};
Object.entries(attributes).forEach(([k, v]) => {
tagMap[k] = String(v);
});
// Transform events (already camelCase from backend)
const events: Event[] = (raw.events || []).map((e: any) => ({
name: e.name || '',
timeUnixNano: e.timeUnixNano || 0,
attributeMap: e.attributeMap || {},
isError: e.isError || false,
}));
return {
timestamp: raw.timestamp || 0,
durationNano: raw.duration_nano ?? raw.durationNano ?? 0,
spanId: raw.span_id ?? raw.spanId ?? '',
rootSpanId: raw.root_span_id ?? raw.rootSpanId ?? '',
parentSpanId: raw.parent_span_id ?? raw.parentSpanId ?? '',
traceId: raw.trace_id ?? raw.traceId ?? '',
hasError: raw.has_error ?? raw.hasError ?? false,
kind: raw.kind || 0,
serviceName: resource['service.name'] || raw.serviceName || '',
name: raw.name || '',
references: raw.references || null,
tagMap,
event: events,
rootName: raw.root_name ?? raw.rootName ?? '',
statusMessage: raw.status_message ?? raw.statusMessage ?? '',
statusCodeString: raw.status_code_string ?? raw.statusCodeString ?? '',
spanKind: raw.kind_string ?? raw.spanKind ?? '',
hasChildren: raw.has_children ?? raw.hasChildren ?? false,
hasSibling: raw.has_sibling ?? raw.hasSibling ?? false,
subTreeNodeCount: raw.sub_tree_node_count ?? raw.subTreeNodeCount ?? 0,
level: raw.level || 0,
// V3 format fields
attributes: tagMap,
resources: resource,
// Snake_case passthrough fields
http_method: raw.http_method,
http_url: raw.http_url,
http_host: raw.http_host,
db_name: raw.db_name,
db_operation: raw.db_operation,
external_http_method: raw.external_http_method,
external_http_url: raw.external_http_url,
response_status_code: raw.response_status_code,
is_remote: raw.is_remote,
};
}
const getTraceV3 = async (
props: GetTraceV3PayloadProps,
): Promise<SuccessResponse<GetTraceV3SuccessResponse> | ErrorResponse> => {
let uncollapsedSpans = [...props.uncollapsedSpans];
if (!props.isSelectedSpanIDUnCollapsed) {
uncollapsedSpans = uncollapsedSpans.filter(
(node) => node !== props.selectedSpanId,
);
}
const postData: GetTraceV3PayloadProps = {
...props,
uncollapsedSpans,
};
const response = await axios.post<GetTraceV3SuccessResponse>(
`/traces/${props.traceId}/waterfall`,
omit(postData, 'traceId'),
);
// V3 API wraps response in { status, data }
const rawPayload = (response.data as any).data || response.data;
const spans = (rawPayload.spans || []).map(transformSpan);
// V3 API returns startTimestampMillis/endTimestampMillis as relative durations (ms from epoch offset),
// not absolute unix millis like V2. The span timestamps are absolute unix millis.
// Convert by using the first span's timestamp as the base if there's a mismatch.
let { startTimestampMillis, endTimestampMillis } = rawPayload;
if (
spans.length > 0 &&
spans[0].timestamp > 0 &&
startTimestampMillis < spans[0].timestamp / 10
) {
// V3 times are relative — derive absolute times from span data
const durationMillis = endTimestampMillis - startTimestampMillis;
startTimestampMillis = spans[0].timestamp;
endTimestampMillis = startTimestampMillis + durationMillis;
}
return {
statusCode: 200,
error: null,
message: 'Success',
payload: { ...rawPayload, spans, startTimestampMillis, endTimestampMillis },
};
};
export default getTraceV3;

View File

@@ -1,9 +0,0 @@
<svg width="929" height="8" viewBox="0 0 929 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="dotted-double-line-pattern" x="0" y="0" width="6" height="8" patternUnits="userSpaceOnUse">
<rect width="2" height="2" rx="1" fill="#242834" />
<rect y="6" width="2" height="2" rx="1" fill="#242834" />
</pattern>
</defs>
<rect width="929" height="8" fill="url(#dotted-double-line-pattern)" />
</svg>

Before

Width:  |  Height:  |  Size: 442 B

View File

@@ -10,6 +10,7 @@
// PR for reference: https://github.com/SigNoz/signoz/pull/9694
// -------------------------------------------------------------------------
import '@signozhq/badge';
import '@signozhq/button';
import '@signozhq/calendar';
import '@signozhq/callout';
@@ -24,7 +25,9 @@ import '@signozhq/input';
import '@signozhq/popover';
import '@signozhq/radio-group';
import '@signozhq/resizable';
import '@signozhq/tabs';
import '@signozhq/sonner';
import '@signozhq/switch';
import '@signozhq/table';
import '@signozhq/toggle-group';
import '@signozhq/tooltip';
import '@signozhq/ui';

View File

@@ -2,6 +2,13 @@
display: flex;
justify-content: space-between;
align-items: center;
background: var(--l3-background);
border: 1px solid var(--l1-border);
background: var(--bg-ink-300);
border: 1px solid var(--bg-slate-400);
}
.lightMode {
.reset-button {
background: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
}

View File

@@ -3,7 +3,7 @@
justify-content: center;
align-items: center;
height: 100vh;
background-color: var(--l2-background); // Background
background-color: var(--bg-ink-400, #121317); // Dark theme background
.app-loading-content {
display: flex;
@@ -28,7 +28,7 @@
.brand-title {
font-size: 20px;
font-weight: 600;
color: var(--l1-foreground); // Primary text
color: var(--bg-vanilla-100, #ffffff); // White text for dark theme
margin: 0;
}
}
@@ -37,7 +37,7 @@
margin-bottom: 24px;
.ant-typography {
color: var(--l2-foreground); // Secondary text
color: var(--bg-vanilla-400, #c0c1c3); // Light gray text for dark theme
}
}
@@ -46,7 +46,7 @@
width: 150px;
height: 12px;
border-radius: 2px;
color: var(--primary-background); // Primary blue color
color: var(--bg-robin-500, #4e74f8); // Primary blue color
border: 2px solid;
position: relative;
}
@@ -67,6 +67,38 @@
}
}
// Light theme styles - more specific selector
.app-loading-container.lightMode {
background-color: var(
--bg-vanilla-100,
#ffffff
) !important; // White background for light theme
.app-loading-content {
.brand {
.brand-title {
color: var(--bg-ink-400, #121317) !important; // Dark text for light theme
}
}
.brand-tagline {
.ant-typography {
color: var(
--bg-ink-300,
#6b7280
) !important; // Dark gray text for light theme
}
}
.loader {
color: var(
--bg-robin-500,
#4e74f8
) !important; // Keep primary blue color for consistency
}
}
}
.perilin-bg {
position: absolute;
top: 0;
@@ -80,12 +112,41 @@
mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
rgba(11, 12, 14, 0.1) 0,
rgba(11, 12, 14, 0) 100%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0,
color-mix(in srgb, var(--background) 10%, transparent) 0,
transparent 100%
rgba(11, 12, 14, 0.1) 0,
rgba(11, 12, 14, 0) 100%
);
}
// Dark theme styles - ensure dark theme is properly applied
.app-loading-container.dark {
background-color: var(--bg-ink-400, #121317) !important; // Dark background
.app-loading-content {
.brand {
.brand-title {
color: var(
--bg-vanilla-100,
#ffffff
) !important; // White text for dark theme
}
}
.brand-tagline {
.ant-typography {
color: var(
--bg-vanilla-400,
#c0c1c3
) !important; // Light gray text for dark theme
}
}
.loader {
color: var(--bg-robin-500, #4e74f8) !important; // Primary blue color
}
}
}

View File

@@ -4,13 +4,12 @@
animation: horizontal-shaking 300ms ease-out;
.error-content {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: rgba(229, 72, 77, 0.1);
border: 1px solid rgba(229, 72, 77, 0.2);
border-radius: 4px;
&__summary-section {
border-bottom: 1px solid
color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
border-bottom: 1px solid rgba(229, 72, 77, 0.2);
}
&__summary {
@@ -32,7 +31,7 @@
}
&__error-code {
color: var(--bg-cherry-100);
color: #fadadb;
font-size: 13px;
font-weight: 500;
line-height: 1;
@@ -40,7 +39,7 @@
}
&__error-message {
color: var(--bg-cherry-300);
color: #f5b6b8;
font-size: 13px;
font-weight: 400;
line-height: 20px;
@@ -52,13 +51,13 @@
}
&__message-badge-label-text {
color: var(--bg-cherry-100);
color: #fadadb;
}
&__message-badge-line {
background-image: radial-gradient(
circle,
color-mix(in srgb, var(--bg-cherry-500) 30%, transparent) 1px,
rgba(229, 72, 77, 0.3) 1px,
transparent 2px
);
}
@@ -72,23 +71,23 @@
}
&__message-item {
color: var(--bg-cherry-300);
color: #f5b6b8;
font-size: 13px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.065px;
&::before {
background: var(--bg-cherry-300);
background: #f5b6b8;
}
}
&__scroll-hint {
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: rgba(229, 72, 77, 0.2);
}
&__scroll-hint-text {
color: var(--bg-cherry-100);
color: #fadadb;
}
}
@@ -101,28 +100,27 @@
.lightMode {
.auth-error-container {
.error-content {
background: rgba(229, 72, 77, 0.1);
border-color: rgba(229, 72, 77, 0.2);
&__error-code {
color: var(--l2-foreground);
color: var(--bg-ink-100);
}
&__error-message {
color: var(--l1-foreground);
}
&__message-badge-label-text {
color: var(--l2-foreground);
color: var(--bg-ink-400);
}
&__message-item {
color: var(--l1-foreground);
color: var(--bg-ink-400);
&::before {
background: var(--l3-background);
background: var(--bg-ink-400);
}
}
&__scroll-hint-text {
color: var(--l2-foreground);
color: var(--bg-ink-100);
}
}
}

View File

@@ -16,8 +16,8 @@
justify-content: center;
gap: 16px;
padding: 12px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
background: var(--bg-ink-400);
border: 1px solid var(--bg-ink-200);
border-radius: 4px;
}
@@ -32,7 +32,7 @@
width: 6px;
height: 6px;
border-radius: 9999px;
background: var(--bg-forest-500);
background: #25e192;
flex-shrink: 0;
}
@@ -49,7 +49,7 @@
font-size: 11px;
font-weight: 400;
line-height: 1;
color: var(--l2-foreground);
color: var(--text-neutral-dark-100);
text-align: center;
}
@@ -67,16 +67,16 @@
.auth-footer-link-icon {
flex-shrink: 0;
color: var(--l1-foreground);
color: var(--text-neutral-dark-50);
}
.auth-footer-link-status {
.auth-footer-text {
color: var(--bg-forest-500);
color: #25e192;
}
.auth-footer-link-icon {
color: var(--bg-forest-500);
color: #25e192;
}
}
@@ -84,12 +84,14 @@
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--l3-background);
background: var(--bg-ink-200);
flex-shrink: 0;
}
.lightMode {
.auth-footer-content {
background: var(--bg-base-white);
border-color: var(--bg-vanilla-300);
box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.08);
}
@@ -106,4 +108,8 @@
.auth-footer-link-icon {
color: var(--text-neutral-light-100);
}
.auth-footer-separator {
background: var(--bg-vanilla-300);
}
}

View File

@@ -30,7 +30,7 @@
font-size: 15.4px;
font-weight: 500;
line-height: 17.5px;
color: var(--l1-foreground);
color: var(--text-neutral-dark-50);
white-space: nowrap;
}
@@ -41,8 +41,7 @@
gap: 8px;
height: 32px;
padding: 10px 16px;
background: var(--l2-background);
color: var(--l2-foreground);
background: var(--bg-ink-400);
border: none;
border-radius: 2px;
cursor: pointer;
@@ -53,16 +52,31 @@
font-size: 11px;
font-weight: 500;
line-height: 1;
color: var(--l2-foreground);
color: var(--text-neutral-dark-100);
text-align: center;
}
svg {
flex-shrink: 0;
color: var(--l2-foreground);
color: var(--text-neutral-dark-100);
}
&:hover {
opacity: 0.8;
}
}
.lightMode {
.auth-header-logo-text {
color: var(--text-neutral-light-100);
}
.auth-header-help-button {
background: var(--bg-vanilla-200);
span,
svg {
color: var(--text-neutral-light-200);
}
}
}

View File

@@ -4,7 +4,7 @@
position: relative;
min-height: 100vh;
width: 100%;
background: var(--l1-background);
background: var(--bg-neutral-dark-1000);
display: flex;
flex-direction: column;
}
@@ -43,13 +43,13 @@
.masked-dots {
mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
transparent 56.77%
rgba(11, 12, 14, 0.1) 0%,
rgba(11, 12, 14, 0) 56.77%
);
-webkit-mask-image: radial-gradient(
circle at 50% 0%,
color-mix(in srgb, var(--background) 10%, transparent) 0%,
transparent 56.77%
rgba(11, 12, 14, 0.1) 0%,
rgba(11, 12, 14, 0) 56.77%
);
}
@@ -65,7 +65,7 @@
border-radius: 956px;
background: radial-gradient(
ellipse at center -500px,
color-mix(in srgb, var(--primary-background) 30%, transparent) 0%,
rgba(78, 116, 248, 0.3) 0%,
transparent 70%
);
opacity: 0.3;
@@ -85,8 +85,8 @@
height: 100%;
background-image: repeating-linear-gradient(
to bottom,
var(--l1-border) 0px,
var(--l1-border) 4px,
var(--bg-ink-200) 0px,
var(--bg-ink-200) 4px,
transparent 4px,
transparent 8px
);
@@ -145,19 +145,19 @@
}
.lightMode {
.auth-page-wrapper {
background: var(--bg-base-white);
}
.bg-dot-pattern {
background: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 1px
);
background: radial-gradient(circle, rgba(35, 38, 46, 1) 1px, transparent 1px);
background-size: 12px 12px;
}
.auth-page-gradient {
background: radial-gradient(
ellipse at center top,
color-mix(in srgb, var(--primary-background) 12%, transparent) 0%,
rgba(78, 116, 248, 0.12) 0%,
transparent 60%
);
opacity: 0.8;
@@ -167,4 +167,15 @@
filter: blur(300px);
}
}
.auth-page-line-left,
.auth-page-line-right {
background-image: repeating-linear-gradient(
to bottom,
var(--bg-vanilla-300) 0px,
var(--bg-vanilla-300) 4px,
transparent 4px,
transparent 8px
);
}
}

View File

@@ -21,8 +21,8 @@
min-width: 164px;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
}
@@ -31,3 +31,16 @@
flex-shrink: 0;
}
}
.lightMode {
.celery-overview-filters {
.celery-filters {
.config-select-option {
.ant-select-selector {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
}
}

View File

@@ -27,10 +27,10 @@
font-size: 12px;
line-height: 18px;
background: var(--l1-background);
background: var(--bg-ink-500);
border-bottom: none;
color: var(--l2-foreground);
color: var(--Vanilla-400, #c0c1c3);
font-family: Inter;
font-size: 11px;
font-style: normal;
@@ -47,8 +47,8 @@
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--l1-foreground);
background: var(--l1-background);
color: var(--bg-vanilla-100);
background: var(--bg-ink-500);
}
.progress-container {
@@ -59,7 +59,7 @@
}
.ant-table-tbody > tr:hover > td {
background: color-mix(in srgb, var(--l1-foreground) 4%, transparent);
background: rgba(255, 255, 255, 0.04);
}
.ant-table-cell:first-child {
@@ -89,7 +89,7 @@
position: relative;
bottom: 0;
width: 100%;
background: var(--l1-background);
background: var(--bg-ink-500);
padding: 4px;
margin: 0;
@@ -100,11 +100,48 @@
border-radius: 4px;
&-active {
background: var(--primary-background);
border-color: var(--primary-background);
background: var(--bg-robin-500);
border-color: var(--bg-robin-500);
a {
color: var(--primary-foreground) !important;
color: var(--bg-ink-500) !important;
}
}
}
}
}
}
.lightMode {
.celery-overview-table-container {
.celery-overview-table {
.ant-table {
.ant-table-thead > tr > th {
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
}
.ant-table-cell {
background: var(--bg-vanilla-100);
color: var(--bg-ink-500);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
.ant-pagination {
background: var(--bg-vanilla-100);
.ant-pagination-item {
&-active {
background: var(--bg-robin-500);
border-color: var(--bg-robin-500);
a {
color: var(--bg-vanilla-100) !important;
}
}
}
}

View File

@@ -18,8 +18,21 @@
min-width: 164px;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
}
}
}
}
.lightMode {
.celery-task-filters {
.celery-filters {
.config-select-option {
.ant-select-selector {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
}
}
}
}

View File

@@ -1,7 +1,7 @@
.celery-task-detail-drawer {
.ant-drawer-wrapper-body {
background: var(--l1-background);
border: 1px solid var(--l1-border);
background: var(--bg-ink-500);
border: 1px solid var(--bg-ink-300);
}
.ant-drawer-body {
@@ -11,17 +11,17 @@
border: none;
.ant-card-body {
height: 100%;
background: var(--l1-background);
background: var(--bg-ink-500);
.ant-table {
background: var(--l1-background);
background: var(--bg-ink-500);
}
}
}
}
.ant-drawer-header {
border-bottom: 1px solid var(--l1-border);
border-bottom: 1px solid var(--bg-ink-300);
.ant-drawer-header-title {
.ant-drawer-close {
position: absolute;
@@ -29,7 +29,7 @@
}
button > svg {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
}
.ant-drawer-title {
@@ -38,7 +38,7 @@
align-items: flex-start;
.title {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-family: Inter;
font-size: 18px;
font-style: normal;
@@ -48,7 +48,7 @@
}
.subtitle {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -60,10 +60,10 @@
}
.ant-drawer-footer {
border-top: 1px solid var(--l1-border);
border-top: 1px solid var(--bg-ink-300);
.footer-text {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -72,3 +72,51 @@
}
}
}
.lightMode {
.celery-task-detail-drawer {
.ant-drawer-wrapper-body {
background: var(--bg-vanilla-100);
border: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-body {
.ant-card {
.ant-card-body {
background: var(--bg-vanilla-100);
.ant-table {
background: var(--bg-vanilla-100);
}
}
}
}
.ant-drawer-header {
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-drawer-header-title {
button > svg {
color: var(--bg-ink-500);
}
.ant-drawer-title {
.title {
color: var(--bg-ink-400);
}
.subtitle {
color: var(--bg-ink-300);
}
}
}
}
.ant-drawer-footer {
border-top: 1px solid var(--bg-vanilla-300);
.footer-text {
color: var(--bg-ink-400);
}
}
}
}

View File

@@ -40,8 +40,13 @@
display: flex;
justify-content: center;
align-items: center;
border: 1px solid var(--l1-border);
background: linear-gradient(0deg, transparent 0%, transparent 100%), #0b0c0e;
border: 1px solid var(--bg-slate-500);
background: linear-gradient(
0deg,
rgba(171, 189, 255, 0) 0%,
rgba(171, 189, 255, 0) 100%
),
#0b0c0e;
.ant-card-body {
height: 100%;
@@ -142,13 +147,13 @@
gap: 16px;
align-items: center;
border: 1px dashed var(--l1-border);
border: 1px dashed var(--bg-slate-50);
border-radius: 4px;
padding: 6px 24px 6px 12px;
width: max-content;
.configure-option-Info-text {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
}
@@ -156,7 +161,7 @@
.row-panel {
border-radius: 4px;
background: color-mix(in srgb, var(--card) 40%, transparent);
background: rgba(18, 19, 23, 0.4);
padding: 8px;
display: flex;
gap: 6px;
@@ -175,12 +180,12 @@
align-items: center;
.row-icon {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
cursor: pointer;
}
.section-title {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -193,7 +198,7 @@
}
.celery-task-states {
border-bottom: 1px solid var(--l1-border);
border-bottom: 1px solid var(--bg-ink-200);
&__tab {
min-width: 140px;
@@ -202,11 +207,11 @@
position: relative;
&:not([data-last-tab='true']) {
border-right: 1px solid var(--l1-border);
border-right: 1px solid var(--bg-ink-200);
}
&--selected {
background-color: var(--l3-background);
background-color: rgba(38, 38, 38, 0.5);
}
}
@@ -220,7 +225,7 @@
&__label {
font-family: 'Inter';
font-size: 14px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
line-height: 20px;
font-weight: 500;
}
@@ -228,7 +233,7 @@
&__value {
font-family: 'Geist Mono';
font-size: 24px;
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
line-height: 32px;
}
@@ -238,15 +243,46 @@
height: 2px;
bottom: 0;
left: 0;
background-color: var(--l1-foreground);
background-color: var(--bg-vanilla-100);
}
}
.lightMode {
.celery-task-graph-grid-container {
.celery-task-graph-worker-count {
border: 1px solid var(--bg-vanilla-300);
background: unset;
}
.row-panel .row-panel-section .section-title {
color: var(--bg-ink-400);
}
}
.celery-task-states {
border-bottom: 1px solid var(--bg-vanilla-300);
&__tab {
&:not([data-last-tab='true']) {
border-right: 1px solid var(--bg-vanilla-300);
}
&--selected {
background-color: var(--bg-vanilla-200);
}
}
&__label {
color: var(--bg-ink-500);
}
&__value {
color: var(--bg-slate-100);
}
&__indicator {
background-color: var(--bg-ink-400);
}
}
.configure-option-Info {

View File

@@ -26,7 +26,7 @@
}
}
&--negative {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
background: rgba(229, 72, 77, 0.1);
.change-percentage-pill {
&__icon {

View File

@@ -1,7 +1,7 @@
.changelog-modal {
.ant-modal-content {
padding: unset;
background-color: var(--l2-background);
background-color: var(--bg-ink-400, #121317);
.ant-modal-header {
margin-bottom: unset;
@@ -16,12 +16,12 @@
display: flex;
align-items: center;
gap: 8px;
background-color: var(--l2-background);
background-color: var(--bg-ink-400, #121317);
padding: 16px;
font-size: 14px;
line-height: 20px;
color: var(--l1-foreground);
border-bottom: 1px solid var(--l1-border);
color: var(--text-vanilla-100, #fff);
border-bottom: 1px solid var(--bg-slate-500, #161922);
}
&-footer.scroll-available {
@@ -32,14 +32,14 @@
&-footer {
position: relative;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-500, #161922);
padding: 12px;
display: flex;
align-items: center;
justify-content: space-between;
&-label {
color: var(--text-robin-400);
color: var(--text-robin-400, #7190f9);
font-size: 14px;
line-height: 24px;
position: relative;
@@ -54,7 +54,7 @@
width: 6px;
height: 6px;
border-radius: 100%;
background-color: var(--primary-background);
background-color: var(--bg-robin-500, #7190f9);
}
}
@@ -77,7 +77,7 @@
.scroll-btn {
all: unset;
padding: 4px 12px 4px 10px;
background-color: var(--l1-border);
background-color: var(--bg-slate-400, #1d212d);
border-radius: 20px;
cursor: pointer;
display: flex;
@@ -87,17 +87,17 @@
transition: background-color 0.1s;
&:hover {
background-color: var(--l1-border);
background-color: var(--bg-slate-200, #2c3140);
}
&:active {
background-color: var(--l1-border);
background-color: var(--bg-slate-600, #1c1f2a);
}
span {
font-size: 12px;
line-height: 18px;
color: var(--l2-foreground);
color: var(--text-vanilla-400, #c0c1c3);
}
// add animation to the chevrons down icon
@@ -112,7 +112,7 @@
max-height: calc(100vh - 300px);
overflow-y: auto;
padding: 16px 16px 18px 16px;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-500, #161922);
border-top-width: 0;
border-bottom-width: 0;
}
@@ -127,3 +127,36 @@
opacity: 0.5;
}
}
.lightMode {
.changelog-modal {
.ant-modal-content {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-300);
}
&-title {
background: var(--bg-vanilla-100);
color: var(--bg-ink-500);
border-color: var(--bg-vanilla-300);
}
&-content {
border-color: var(--bg-vanilla-300);
}
&-footer {
border-color: var(--bg-vanilla-300);
.scroll-btn-container {
.scroll-btn {
background-color: var(--bg-vanilla-300);
span {
color: var(--text-ink-500);
}
}
}
}
}
}

View File

@@ -15,13 +15,13 @@
&-section-title {
font-size: 14px;
line-height: 20px;
color: var(--l1-foreground);
color: var(--text-vanilla-400, #c0c1c3);
}
.changelog-release-date {
font-size: 14px;
line-height: 20px;
color: var(--l1-foreground);
color: var(--text-vanilla-400, #c0c1c3);
display: block;
margin-bottom: 12px;
}
@@ -38,7 +38,7 @@
top: 6px;
bottom: -30px;
width: 1px;
background-color: var(--l1-border);
background-color: var(--bg-slate-400, #1d212d);
.inner-ball {
position: absolute;
@@ -47,7 +47,7 @@
height: 6px;
border-radius: 100%;
transform: translateX(-50%);
background-color: var(--primary-background);
background-color: var(--bg-robin-500, #7190f9);
}
}
@@ -69,7 +69,7 @@
top: 10px;
width: 20px;
height: 2px;
background-color: var(--primary-background);
background-color: var(--bg-robin-500, #7190f9);
transform: translate(-100%, -50%);
}
}
@@ -79,20 +79,19 @@
p {
font-size: 14px;
line-height: 20px;
color: var(--l1-foreground);
color: var(--text-vanilla-400, #c0c1c3);
}
code {
padding: 2px 4px;
background-color: var(--l1-border);
background-color: var(--bg-slate-500, #161922);
border-radius: 6px;
font-size: 95%;
vertical-align: middle;
border: 1px solid var(--l1-border);
color: var(--l1-foreground);
border: 1px solid var(--bg-slate-600, #1c1f2a);
}
a {
color: var(--text-robin-500);
color: var(--text-robin-500, #7190f9);
font-weight: 600;
text-decoration: underline;
@@ -103,7 +102,7 @@
& :is(h1, h2, h3, h4, h5, h6, &-section-title) {
font-weight: 600;
color: var(--l1-foreground);
color: var(--text-vanilla-100, #fff);
}
h1 {
@@ -123,7 +122,7 @@
width: 100%;
overflow: hidden;
border-radius: 4px;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-400, #1d212d);
margin-bottom: 28px;
}
@@ -131,3 +130,25 @@
margin: 12px 0;
}
}
.lightMode {
.changelog-renderer {
.changelog-release-date {
color: var(--text-ink-500);
}
&-line {
background-color: var(--bg-vanilla-300);
}
& :is(h1, h2, h3, h4, h5, h6, p, li, &-section-title) {
color: var(--text-ink-500);
}
code {
background-color: var(--bg-vanilla-300);
border: 1px solid var(--bg-vanilla-300);
color: var(--text-ink-500);
}
}
}

View File

@@ -1,34 +0,0 @@
.cloud-service-data-collected {
display: flex;
flex-direction: column;
gap: 16px;
.cloud-service-data-collected-table {
display: flex;
flex-direction: column;
gap: 8px;
.cloud-service-data-collected-table-heading {
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
color: var(--l2-foreground);
/* Bifrost (Ancient)/Content/sm */
font-family: Inter;
font-size: 13px;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.cloud-service-data-collected-table-logs {
border-radius: 6px;
border: 1px solid var(--l3-background);
background: var(--l1-background);
}
}
}

View File

@@ -18,7 +18,7 @@
font-weight: var(--label-base-400-font-weight);
line-height: var(--label-base-400-line-height);
letter-spacing: -0.065px;
color: var(--l1-foreground);
color: var(--bg-base-white);
margin: 0;
}
@@ -54,7 +54,7 @@
height: 32px;
color: var(--l1-foreground);
background-color: var(--l2-background);
border-color: var(--l1-border);
border-color: var(--border);
font-size: var(--paragraph-base-400-font-size);
border-radius: 2px;
width: 100%;
@@ -96,3 +96,11 @@
gap: var(--spacing-4);
flex-shrink: 0;
}
.lightMode {
.create-sa-modal {
[data-slot='dialog-title'] {
color: var(--bg-base-black);
}
}
}

View File

@@ -4,7 +4,7 @@ import { Button } from '@signozhq/button';
import { DialogFooter, DialogWrapper } from '@signozhq/dialog';
import { X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,

View File

@@ -1,12 +1,11 @@
import { toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { rest, server } from 'mocks-server/server';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
jest.mock('@signozhq/sonner', () => ({
toast: { success: jest.fn(), error: jest.fn() },
}));

View File

@@ -9,17 +9,16 @@
align-items: center;
justify-content: center;
flex-shrink: 0;
color: var(--secondary-foreground);
background-color: var(--secondary-background);
border: 1px solid var(--secondary-border);
color: var(--foreground);
border: 1px solid var(--border);
border-radius: 2px;
box-shadow: none;
padding: 10px;
height: 33px;
&:hover:not(:disabled) {
color: var(--primary-foreground);
background: var(--primary-background);
color: var(--bg-vanilla-100);
background: var(--primary);
}
&:disabled {
@@ -30,7 +29,7 @@
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
border-color: #1d212d !important;
}
}
@@ -99,7 +98,7 @@
justify-content: center;
padding: 4px;
cursor: default;
color: var(--l2-foreground) !important;
color: var(--bg-vanilla-400, #c0c1c3) !important;
font-size: 14px;
font-style: normal;
font-weight: 400;
@@ -147,10 +146,14 @@
color: rgba($color: #000000, $alpha: 0.4);
}
}
.info-text {
color: var(--bg-slate-400) !important;
}
}
.date-time-popover__footer {
border-top: 1px solid var(--l1-border);
border-top: 1px solid var(--bg-ink-200);
padding: 8px 14px;
.timezone-container {
display: flex;
@@ -202,7 +205,7 @@
display: flex;
align-items: center;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
gap: 6px;
.timezone {
@@ -210,10 +213,10 @@
align-items: center;
gap: 4px;
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
background: rgba(171, 189, 255, 0.04);
cursor: pointer;
padding: 0px 4px;
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
border: none;
}
}
@@ -225,8 +228,8 @@
justify-content: center;
padding: 0 4px;
border-radius: 2px;
color: var(--l2-foreground);
background-color: var(--l3-background);
color: var(--bg-vanilla-400);
background-color: var(--bg-ink-200);
font-size: 9px;
font-weight: 400;
line-height: 12px;
@@ -246,8 +249,8 @@
width: 36px;
font-size: 11px;
color: var(--l2-foreground);
background-color: var(--l3-background);
color: var(--bg-vanilla-400);
background-color: var(--bg-ink-200);
cursor: pointer;
&.is-live {
@@ -269,14 +272,13 @@
@keyframes ripple {
0% {
box-shadow: 0 0 0 0
color-mix(in srgb, var(--warning-background) 40%, transparent);
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4);
}
60% {
box-shadow: 0 0 0 6px transparent;
box-shadow: 0 0 0 6px rgba(245, 158, 11, 0);
}
100% {
box-shadow: 0 0 0 0 transparent;
box-shadow: 0 0 0 0 rgba(245, 158, 11, 0);
}
}
@@ -286,8 +288,8 @@
justify-content: center;
padding: 0 4px;
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
color: var(--l1-foreground);
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 400;
line-height: 16px;
@@ -297,16 +299,23 @@
width: 20px;
&:hover {
background: color-mix(in srgb, var(--bg-robin-200) 8%, transparent);
background: rgba(171, 189, 255, 0.08);
}
}
.lightMode {
.date-time-popover__footer {
border-color: var(--bg-vanilla-400);
}
.timezone-container {
color: var(--bg-ink-400);
.timezone {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);
&__icon {
stroke: var(--l1-foreground);
stroke: var(--bg-ink-100);
}
}
}
@@ -314,16 +323,23 @@
.custom-time-picker {
.timeSelection-input {
&:hover {
border-color: var(--l1-border) !important;
border-color: var(--bg-vanilla-300) !important;
}
}
}
.timezone-badge {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);
}
.time-input-prefix {
background-color: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
.time-input-suffix-icon-badge {
color: var(--bg-ink-100);
background: rgb(179 179 179 / 15%);
&:hover {

View File

@@ -656,13 +656,15 @@ function CustomTimePicker({
zoomOutDisabled ? 'Zoom out time range is limited to 1 month' : 'Zoom out'
}
>
<Button
className="zoom-out-btn"
onClick={handleZoomOut}
disabled={zoomOutDisabled}
data-testid="zoom-out-btn"
prefixIcon={<ZoomOut size={14} />}
/>
<span>
<Button
className="zoom-out-btn"
onClick={handleZoomOut}
disabled={zoomOutDisabled}
data-testid="zoom-out-btn"
prefixIcon={<ZoomOut size={14} />}
/>
</span>
</Tooltip>
)}
</div>

View File

@@ -3,7 +3,11 @@ $font-family: 'Inter';
$item-spacing: 8px;
:root {
--border-color: var(--l1-border);
--border-color: var(--bg-slate-400);
}
.lightMode {
--border-color: var(--bg-vanilla-400);
}
// Mixins
@@ -20,7 +24,7 @@ $item-spacing: 8px;
.timezone-picker {
width: 532px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: $font-family;
&__search {
@@ -42,7 +46,7 @@ $item-spacing: 8px;
background: transparent;
border: none;
outline: none;
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
@@ -52,19 +56,19 @@ $item-spacing: 8px;
}
&::placeholder {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
}
}
&__esc-key {
@include text-style-base;
font-size: 8px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
letter-spacing: -0.04px;
border-radius: 2.286px;
border: 1.143px solid var(--l1-border);
border: 1.143px solid var(--bg-ink-200);
border-bottom-width: 2.286px;
background: var(--l2-background);
background: var(--bg-ink-400);
padding: 0 1px;
}
@@ -82,14 +86,14 @@ $item-spacing: 8px;
background: transparent;
border: none;
width: -webkit-fill-available;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: $font-family;
&:hover,
&.selected {
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
color: var(--l1-foreground);
background: rgba(171, 189, 255, 0.04);
color: var(--bg-vanilla-100);
}
&.has-divider {
@@ -113,7 +117,7 @@ $item-spacing: 8px;
}
&__offset {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 12px;
line-height: 16px;
letter-spacing: -0.06px;
@@ -134,14 +138,28 @@ $item-spacing: 8px;
.timezone-picker {
&__search {
.search-icon {
stroke: var(--l1-foreground);
stroke: var(--bg-ink-400);
}
}
&__input {
color: var(--bg-ink-100);
}
&__esc-key {
background-color: var(--bg-vanilla-100);
border-color: var(--bg-vanilla-400);
color: var(--bg-ink-400);
}
&__item {
color: var(--bg-ink-400);
}
&__offset {
color: var(--bg-ink-100);
}
}
.timezone-name-wrapper {
&__selected-icon {
.check-icon {
stroke: var(--l1-foreground);
stroke: var(--bg-ink-100);
}
}
}

View File

@@ -1,10 +1,10 @@
.details-drawer {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--bg-slate-500);
}
.ant-drawer-header {
background: var(--l2-background);
border-bottom: 1px solid var(--l1-border);
background: var(--bg-ink-400);
border-bottom: 1px solid var(--bg-slate-500);
.ant-drawer-header-title {
display: flex;
@@ -14,12 +14,12 @@
margin-inline-end: 0px;
padding: 0px;
padding-right: 16px;
border-right: 1px solid var(--l1-border);
border-right: 1px solid var(--bg-slate-500);
}
.ant-drawer-title {
padding-left: 16px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: Inter;
font-size: 14px;
font-style: normal;
@@ -31,7 +31,7 @@
}
.ant-drawer-body {
padding: 16px;
background: var(--l2-background);
background: var(--bg-ink-400);
&::-webkit-scrollbar {
width: 0.1rem;
@@ -50,11 +50,11 @@
flex-shrink: 0;
padding: 7px 20px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l2-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-400);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
color: var(--l1-foreground);
color: #fff;
font-family: Inter;
font-size: 12px;
font-style: normal;
@@ -75,7 +75,7 @@
}
.ant-tabs-tab-active {
background: var(--l3-background);
background: var(--bg-slate-400);
}
.ant-tabs-tab + .ant-tabs-tab {
@@ -91,3 +91,44 @@
}
}
}
.lightMode {
.details-drawer {
.ant-drawer-wrapper-body {
border-left: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-header {
background: var(--bg-vanilla-200);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-drawer-header-title {
.ant-drawer-close {
border-right: 1px solid var(--bg-vanilla-300);
}
.ant-drawer-title {
color: var(--bg-ink-400);
}
}
}
.ant-drawer-body {
background: var(--bg-vanilla-200);
}
.details-drawer-tabs {
.ant-tabs-tab {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-300);
color: var(--bg-ink-400);
}
.ant-tabs-tab-active {
background: var(--bg-vanilla-200);
}
.ant-tabs-tab + .ant-tabs-tab {
border-left: none;
}
}
}
}

View File

@@ -0,0 +1,40 @@
.details-header {
// ghost + secondary missing hover bg token in @signozhq/button
--button-ghost-hover-background: var(--l3-background);
display: flex;
align-items: center;
gap: 8px;
padding: 12px;
border-bottom: 1px solid var(--l1-border);
height: 36px;
background: var(--l2-background);
&__icon-btn {
flex-shrink: 0;
}
&__title {
font-size: 13px;
font-weight: 500;
color: var(--l1-foreground);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
&__nav {
display: flex;
align-items: center;
gap: 2px;
}
}

View File

@@ -0,0 +1,59 @@
import { ReactNode } from 'react';
import { Button } from '@signozhq/button';
import { X } from '@signozhq/icons';
import './DetailsHeader.styles.scss';
export interface HeaderAction {
key: string;
component: ReactNode; // check later if we can use direct btn itself or not.
}
export interface DetailsHeaderProps {
title: string;
onClose: () => void;
actions?: HeaderAction[];
closePosition?: 'left' | 'right';
className?: string;
}
function DetailsHeader({
title,
onClose,
actions,
closePosition = 'right',
className,
}: DetailsHeaderProps): JSX.Element {
const closeButton = (
<Button
variant="ghost"
size="icon"
color="secondary"
onClick={onClose}
aria-label="Close"
className="details-header__icon-btn"
>
<X size={14} />
</Button>
);
return (
<div className={`details-header ${className || ''}`}>
{closePosition === 'left' && closeButton}
<span className="details-header__title">{title}</span>
{actions && (
<div className="details-header__actions">
{actions.map((action) => (
<div key={action.key}>{action.component}</div>
))}
</div>
)}
{closePosition === 'right' && closeButton}
</div>
);
}
export default DetailsHeader;

View File

@@ -0,0 +1,7 @@
.details-panel-drawer {
&__body {
display: flex;
flex-direction: column;
height: 100%;
}
}

View File

@@ -0,0 +1,36 @@
import { DrawerWrapper } from '@signozhq/drawer';
import './DetailsPanelDrawer.styles.scss';
interface DetailsPanelDrawerProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
className?: string;
}
function DetailsPanelDrawer({
isOpen,
onClose,
children,
className,
}: DetailsPanelDrawerProps): JSX.Element {
return (
<DrawerWrapper
open={isOpen}
onOpenChange={(open): void => {
if (!open) {
onClose();
}
}}
direction="right"
type="panel"
showOverlay={false}
allowOutsideClick
className={`details-panel-drawer ${className || ''}`}
content={<div className="details-panel-drawer__body">{children}</div>}
/>
);
}
export default DetailsPanelDrawer;

View File

@@ -0,0 +1,8 @@
export type {
DetailsHeaderProps,
HeaderAction,
} from './DetailsHeader/DetailsHeader';
export { default as DetailsHeader } from './DetailsHeader/DetailsHeader';
export { default as DetailsPanelDrawer } from './DetailsPanelDrawer';
export type { DetailsPanelState, UseDetailsPanelOptions } from './types';
export { default as useDetailsPanel } from './useDetailsPanel';

View File

@@ -0,0 +1,10 @@
export interface DetailsPanelState {
isOpen: boolean;
open: () => void;
close: () => void;
}
export interface UseDetailsPanelOptions {
entityId: string | undefined;
onClose?: () => void;
}

View File

@@ -0,0 +1,29 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { DetailsPanelState, UseDetailsPanelOptions } from './types';
function useDetailsPanel({
entityId,
onClose,
}: UseDetailsPanelOptions): DetailsPanelState {
const [isOpen, setIsOpen] = useState<boolean>(false);
const prevEntityIdRef = useRef<string>('');
useEffect(() => {
const currentId = entityId || '';
if (currentId && currentId !== prevEntityIdRef.current) {
setIsOpen(true);
}
prevEntityIdRef.current = currentId;
}, [entityId]);
const open = useCallback(() => setIsOpen(true), []);
const close = useCallback(() => {
setIsOpen(false);
onClose?.();
}, [onClose]);
return { isOpen, open, close };
}
export default useDetailsPanel;

View File

@@ -1,7 +1,7 @@
.download-popover {
.ant-popover-inner {
border-radius: 4px;
border: 1px solid var(--l1-border);
border: 1px solid var(--l3-border);
background: linear-gradient(
139deg,
var(--l2-background) 0%,
@@ -46,7 +46,7 @@
.horizontal-line {
height: 1px;
background: var(--l1-border);
background: var(--l3-border);
}
.export-button {
@@ -57,3 +57,30 @@
}
}
}
.lightMode {
.download-popover {
.ant-popover-inner {
border: 1px solid var(--l2-border);
background: linear-gradient(
139deg,
var(--background) 0%,
var(--l1-background) 98.68%
);
box-shadow: 4px 10px 16px 2px rgba(255, 255, 255, 0.2);
}
.export-options-container {
.title {
color: var(--l2-foreground);
}
:global(.ant-radio-wrapper) {
color: var(--foreground);
}
.horizontal-line {
background: var(--l2-border);
}
}
}
}

View File

@@ -32,7 +32,7 @@
&__input {
height: 32px;
background: var(--l2-background);
border-color: var(--l1-border);
border-color: var(--border);
color: var(--l1-foreground);
box-shadow: none;
@@ -49,7 +49,7 @@
padding: var(--padding-1) var(--padding-2);
border-radius: 2px;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--border);
&--disabled {
cursor: not-allowed;
@@ -122,7 +122,7 @@
width: 100%;
height: 56px;
padding: 0 var(--padding-4);
border-top: 1px solid var(--l1-border);
border-top: 1px solid var(--border);
flex-shrink: 0;
background: var(--card);
}
@@ -142,7 +142,7 @@
&__footer-divider {
width: 1px;
height: 21px;
background: var(--l1-border);
background: var(--border);
flex-shrink: 0;
}
@@ -175,7 +175,7 @@
}
&--danger {
color: var(--accent-cherry);
color: var(--destructive);
}
&--warning {
@@ -186,7 +186,7 @@
.delete-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
[data-slot='dialog-title'] {
color: var(--l1-foreground);
@@ -216,10 +216,10 @@
.reset-link-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--l2-border);
[data-slot='dialog-header'] {
border-color: var(--l1-border);
border-color: var(--l2-border);
color: var(--l1-foreground);
}
@@ -250,7 +250,7 @@
height: 32px;
overflow: hidden;
background: var(--l2-background);
border: 1px solid var(--l1-border);
border: 1px solid var(--border);
border-radius: 2px;
}
@@ -280,7 +280,7 @@
border-top: none;
border-right: none;
border-bottom: none;
border-left: 1px solid var(--l1-border);
border-left: 1px solid var(--border);
min-width: 64px;
}
}

View File

@@ -1,10 +1,11 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCopyToClipboard } from 'react-use';
import { Badge } from '@signozhq/badge';
import { Button } from '@signozhq/button';
import { DrawerWrapper } from '@signozhq/drawer';
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
import { Input } from '@signozhq/input';
import { Badge, toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';

View File

@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
@@ -64,8 +64,7 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
convertToApiError: jest.fn(),
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
jest.mock('@signozhq/sonner', () => ({
toast: {
success: jest.fn(),
error: jest.fn(),

View File

@@ -5,7 +5,7 @@
align-items: center;
gap: 4px;
border-radius: 20px;
background: color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: rgba(229, 72, 77, 0.2);
padding-left: 3px;
padding-right: 8px;
cursor: pointer;
@@ -21,11 +21,11 @@
&__wrap {
background: linear-gradient(
180deg,
color-mix(in srgb, var(--background) 12%, transparent) 0.07%,
color-mix(in srgb, var(--bg-sakura-950) 24%, transparent) 50.04%,
color-mix(in srgb, var(--bg-sakura-800) 36%, transparent) 75.02%,
color-mix(in srgb, var(--bg-sakura-600) 48%, transparent) 87.51%,
color-mix(in srgb, var(--bg-sakura-500) 60%, transparent) 100%
rgba(11, 12, 14, 0.12) 0.07%,
rgba(39, 8, 14, 0.24) 50.04%,
rgba(106, 29, 44, 0.36) 75.02%,
rgba(197, 57, 85, 0.48) 87.51%,
rgba(242, 71, 105, 0.6) 100%
);
-webkit-backdrop-filter: blur(10px);
backdrop-filter: blur(10px);
@@ -42,7 +42,7 @@
}
&__body {
padding: 0;
background: var(--l2-background);
background: var(--bg-ink-400);
overflow: hidden;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
@@ -72,21 +72,21 @@
text-transform: uppercase;
&,
&:hover {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
}
}
&__value {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
pointer-events: none;
}
}
.close-button {
padding: 3px 7px;
background: var(--l2-background);
background: var(--bg-ink-400);
display: inline-flex;
align-items: center;
border-radius: 4px;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-500);
box-shadow: none;
}
}
@@ -102,3 +102,17 @@
background: none !important;
}
}
.lightMode {
.error-modal {
&__body,
&__header .close-button {
background: var(--bg-vanilla-100);
}
&__header .close-button {
svg {
fill: var(--bg-vanilla-100);
}
}
}
}

View File

@@ -5,7 +5,7 @@
&__summary-section {
display: flex;
flex-direction: column;
border-bottom: 1px solid var(--l1-border);
border-bottom: 1px solid var(--bg-slate-400);
}
&__summary {
@@ -27,7 +27,7 @@
}
&__error-code {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
margin: 0;
font-size: 16px;
font-weight: 500;
@@ -37,7 +37,7 @@
&__error-message {
margin: 0;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-size: 14px;
font-weight: 400;
line-height: 20px; /* 142.857% */
@@ -49,14 +49,14 @@
align-items: center;
gap: 6px;
padding: 9px 12.5px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: 0.12px;
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: none;
}
@@ -68,7 +68,7 @@
.key-value-label {
width: fit-content;
border-color: var(--l1-border);
border-color: var(--bg-slate-400);
border-radius: 20px;
overflow: hidden;
&__key {
@@ -77,7 +77,7 @@
}
&__value {
padding-right: 10px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-size: 12px;
font-weight: 500;
line-height: 18px; /* 150% */
@@ -96,7 +96,7 @@
border-radius: 50%;
}
&-text {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 10px;
font-weight: 500;
line-height: 18px; /* 180% */
@@ -106,11 +106,7 @@
&-line {
flex: 1;
height: 8px;
background-image: radial-gradient(
circle,
var(--l3-background) 1px,
transparent 2px
);
background-image: radial-gradient(circle, #444c63 1px, transparent 2px);
background-size: 8px 11px;
background-position: top left;
padding: 6px;
@@ -133,11 +129,12 @@
&__message-item {
position: relative;
margin-bottom: 4px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-family: Geist Mono;
font-size: 12px;
font-weight: 400;
line-height: 18px;
color: var(--bg-vanilla-400);
padding: 3px 12px;
padding-left: 26px;
}
@@ -152,7 +149,7 @@
width: 2px;
height: 4px;
border-radius: 50px;
background: var(--l1-border);
background: var(--bg-slate-400);
}
&__scroll-hint {
@@ -167,7 +164,7 @@
justify-content: center;
align-items: center;
gap: 3px;
background: var(--l1-border);
background: var(--bg-slate-200);
border-radius: 20px;
box-shadow: 0px 103px 12px 0px rgba(0, 0, 0, 0.01),
0px 66px 18px 0px rgba(0, 0, 0, 0.01), 0px 37px 22px 0px rgba(0, 0, 0, 0.03),
@@ -175,7 +172,7 @@
}
&__scroll-hint-text {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 12px;
font-weight: 400;
@@ -183,3 +180,29 @@
letter-spacing: -0.06px;
}
}
.lightMode {
.error-content {
&__error-code {
color: var(--bg-ink-100);
}
&__error-message {
color: var(--bg-ink-400);
}
&__message-item {
color: var(--bg-ink-400);
}
&__message-badge {
&-label-text {
color: var(--bg-ink-400);
}
.key-value-label__value {
color: var(--bg-ink-400);
}
}
&__docs-button {
background: var(--bg-vanilla-100);
color: var(--bg-ink-100);
}
}
}

View File

@@ -5,8 +5,9 @@
padding: 8px 16px;
height: 48px;
box-sizing: border-box;
background: var(--l1-background);
background: rgba(11, 12, 14, 0.9);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--Slate-500, #161922);
}
.header-left {
@@ -18,3 +19,11 @@
display: flex;
align-items: center;
}
.lightMode {
.header-container {
background: var(--bg-vanilla-100);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--bg-vanilla-300);
}
}

View File

@@ -1,10 +1,10 @@
import { useCallback, useEffect, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { Button, Input, Radio, RadioChangeEvent, Typography } from 'antd';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
const [activeTab, setActiveTab] = useState('feedback');

View File

@@ -23,7 +23,7 @@
justify-content: space-between;
.absolute-relative-time-toggler-label {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 13px;
font-style: normal;
font-weight: 500;
@@ -40,7 +40,7 @@
.absolute-relative-time-error {
font-size: 12px;
color: var(--accent-amber);
color: var(--bg-amber-600);
}
.share-link {
@@ -57,7 +57,7 @@
.url-share-title,
.url-share-sub-title {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
font-size: 13px;
font-style: normal;
font-weight: 500;
@@ -67,7 +67,7 @@
.url-share-sub-title {
font-size: 12px;
color: var(--l3-foreground);
color: var(--bg-vanilla-300);
font-weight: 400;
line-height: 18px;
letter-spacing: -0.06px;
@@ -86,14 +86,14 @@
flex: 1;
margin: 0px !important;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-400);
&:before {
display: none;
}
.ant-radio-button-checked {
background-color: var(--l3-background);
background-color: var(--bg-slate-400);
}
}
@@ -108,15 +108,15 @@
}
.feedback-tab {
background-color: var(--danger-background);
background-color: var(--bg-sakura-500);
}
.bug-tab {
background-color: var(--warning-background);
background-color: var(--bg-amber-500);
}
.feature-tab {
background-color: var(--primary-background);
background-color: var(--bg-robin-500);
}
}
@@ -125,9 +125,9 @@
padding: 6px 16px;
border-radius: 2px;
background: var(--l2-background);
background: var(--bg-ink-400);
box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.1);
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-400);
margin: 0 !important;
@@ -140,13 +140,13 @@
}
&-active {
background: var(--l3-background);
color: var(--l1-foreground);
background: var(--bg-slate-400);
color: var(--bg-vanilla-100);
border-bottom: none !important;
.ant-tabs-tab-btn {
color: var(--l1-foreground);
color: var(--bg-vanilla-100);
}
}
}
@@ -181,7 +181,7 @@
.feedback-modal-content-footer-info-text {
font-size: 12px;
color: var(--l2-foreground);
color: var(--bg-vanilla-400, #c0c1c3);
text-align: center;
/* button/ small */
@@ -200,3 +200,54 @@
}
}
}
.lightMode {
.share-modal-content,
.feedback-modal-container {
.absolute-relative-time-toggler-container {
.absolute-relative-time-toggler-label {
color: var(--bg-ink-400);
}
}
.share-link {
.url-share-container {
.url-share-container-header {
.url-share-title,
.url-share-sub-title {
color: var(--bg-ink-400);
}
.url-share-sub-title {
color: var(--bg-ink-300);
}
}
}
}
}
.feedback-modal-container {
.feedback-modal-tabs {
.ant-radio-button-wrapper {
flex: 1;
margin: 0px !important;
border: 1px solid var(--bg-vanilla-300);
&:before {
display: none;
}
.ant-radio-button-checked {
background-color: var(--bg-vanilla-300);
}
}
}
.feedback-modal-content-footer {
.feedback-modal-content-footer-info-text {
color: var(--bg-slate-400);
}
}
}
}

View File

@@ -1,11 +1,11 @@
// Mock dependencies before imports
import { useLocation } from 'react-router-dom';
import { toast } from '@signozhq/ui';
import { toast } from '@signozhq/sonner';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
import { useGetTenantLicense } from 'hooks/useGetTenantLicense';
import { handleContactSupport } from 'pages/Integrations/utils';
import FeedbackModal from '../FeedbackModal';
@@ -19,8 +19,7 @@ jest.mock('react-router-dom', () => ({
useLocation: jest.fn(),
}));
jest.mock('@signozhq/ui', () => ({
...jest.requireActual('@signozhq/ui'),
jest.mock('@signozhq/sonner', () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
@@ -31,7 +30,7 @@ jest.mock('hooks/useGetTenantLicense', () => ({
useGetTenantLicense: jest.fn(),
}));
jest.mock('container/Integrations/utils', () => ({
jest.mock('pages/Integrations/utils', () => ({
handleContactSupport: jest.fn(),
}));

View File

@@ -28,7 +28,7 @@
.infra-container-card-text {
font-size: var(--font-size-sm);
color: var(--l2-foreground);
color: var(--text-vanilla-400);
line-height: 20px;
letter-spacing: -0.07px;
width: 400px;
@@ -44,7 +44,7 @@
align-items: flex-start;
gap: 12px;
border-radius: 4px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
background: rgba(171, 189, 255, 0.04);
.ant-space {
align-items: flex-start;
@@ -58,3 +58,9 @@
margin: auto;
}
}
.lightMode {
.infra-container-card-text {
color: var(--text-ink-200);
}
}

View File

@@ -1,15 +1,16 @@
import { useTranslation } from 'react-i18next';
import { Space, Typography } from 'antd';
import WaitlistFragment from 'components/HostMetricsDetail/WaitlistFragment/WaitlistFragment';
import broomUrl from '@/assets/Icons/broom.svg';
import infraContainersUrl from '@/assets/Icons/infraContainers.svg';
import 'components/HostMetricsDetail/Containers/Containers.styles.scss';
import WaitlistFragment from '../WaitlistFragment/WaitlistFragment';
import './Containers.styles.scss';
const { Text } = Typography;
function EntityContainers(): JSX.Element {
function Containers(): JSX.Element {
const { t } = useTranslation(['infraMonitoring']);
return (
@@ -43,4 +44,4 @@ function EntityContainers(): JSX.Element {
);
}
export default EntityContainers;
export default Containers;

View File

@@ -0,0 +1,7 @@
import { HostData } from 'api/infraMonitoring/getHostLists';
export type HostDetailProps = {
host: HostData | null;
isModalTimeSelection: boolean;
onClose: () => void;
};

View File

@@ -0,0 +1,193 @@
.host-metric-traces {
margin-top: 1rem;
.host-metric-traces-header {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
gap: 8px;
padding: 12px;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
.filter-section {
flex: 1;
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--bg-slate-400) !important;
background-color: var(--bg-ink-300) !important;
input {
font-size: 12px;
}
.ant-tag .ant-typography {
font-size: 12px;
}
}
}
}
.host-metric-traces-table {
.ant-table-content {
overflow: hidden !important;
}
.ant-table {
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
.ant-table-thead > tr > th {
padding: 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
background: rgba(171, 189, 255, 0.01);
border-bottom: none;
color: var(--Vanilla-400, #c0c1c3);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
&::before {
background-color: transparent;
}
}
.ant-table-thead > tr > th:has(.hostname-column-header) {
background: var(--bg-ink-400);
}
.ant-table-cell {
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--bg-vanilla-100);
background: rgba(171, 189, 255, 0.01);
}
.ant-table-cell:has(.hostname-column-value) {
background: var(--bg-ink-400);
}
.hostname-column-value {
color: var(--bg-vanilla-100);
font-family: 'Geist Mono';
font-style: normal;
font-weight: 600;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.status-cell {
.active-tag {
color: var(--bg-forest-500);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
}
.progress-container {
.ant-progress-bg {
height: 8px !important;
border-radius: 4px;
}
}
.ant-table-tbody > tr:hover > td {
background: rgba(255, 255, 255, 0.04);
}
.ant-table-cell:first-child {
text-align: justify;
}
.ant-table-cell:nth-child(2) {
padding-left: 16px;
padding-right: 16px;
}
.ant-table-cell:nth-child(n + 3) {
padding-right: 24px;
}
.column-header-right {
text-align: right;
}
.ant-table-tbody > tr > td {
border-bottom: none;
}
.ant-table-thead
> tr
> th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before {
background-color: transparent;
}
.ant-empty-normal {
visibility: hidden;
}
}
.ant-table-container::after {
content: none;
}
}
}
.lightMode {
.host-metric-traces-header {
.filter-section {
border-top: 1px solid var(--bg-vanilla-300);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-select-selector {
border-color: var(--bg-vanilla-300) !important;
background-color: var(--bg-vanilla-100) !important;
color: var(--bg-ink-200);
}
}
}
.host-metric-traces-table {
.ant-table {
border-radius: 3px;
border: 1px solid var(--bg-vanilla-300);
.ant-table-thead > tr > th {
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
}
.ant-table-thead > tr > th:has(.hostname-column-header) {
background: var(--bg-vanilla-100);
}
.ant-table-cell {
background: var(--bg-vanilla-100);
color: var(--bg-ink-500);
}
.ant-table-cell:has(.hostname-column-value) {
background: var(--bg-vanilla-100);
}
.hostname-column-value {
color: var(--bg-ink-300);
}
.ant-table-tbody > tr:hover > td {
background: rgba(0, 0, 0, 0.04);
}
}
}
}

View File

@@ -0,0 +1,222 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useQuery } from 'react-query';
import logEvent from 'api/common/logEvent';
import { ResizeTable } from 'components/ResizeTable';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import { InfraMonitoringEvents } from 'constants/events';
import { QueryParams } from 'constants/query';
import EmptyLogsSearch from 'container/EmptyLogsSearch/EmptyLogsSearch';
import NoLogs from 'container/NoLogs/NoLogs';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import { ErrorText } from 'container/TimeSeriesView/styles';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import TraceExplorerControls from 'container/TracesExplorer/Controls';
import { PER_PAGE_OPTIONS } from 'container/TracesExplorer/ListView/configs';
import { TracesLoading } from 'container/TracesExplorer/TraceLoading/TraceLoading';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { Pagination } from 'hooks/queryPagination';
import useUrlQueryData from 'hooks/useUrlQueryData';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { VIEWS } from '../constants';
import { getHostTracesQueryPayload, selectedColumns } from './constants';
import { getListColumns } from './utils';
import './HostMetricTraces.styles.scss';
interface Props {
timeRange: {
startTime: number;
endTime: number;
};
isModalTimeSelection: boolean;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
handleChangeTracesFilters: (
value: IBuilderQuery['filters'],
view: VIEWS,
) => void;
tracesFilters: IBuilderQuery['filters'];
selectedInterval: Time;
}
function HostMetricTraces({
timeRange,
isModalTimeSelection,
handleTimeChange,
handleChangeTracesFilters,
tracesFilters,
selectedInterval,
}: Props): JSX.Element {
const [traces, setTraces] = useState<any[]>([]);
const [offset] = useState<number>(0);
const { currentQuery } = useQueryBuilder();
const updatedCurrentQuery = useMemo(
() => ({
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
dataSource: DataSource.TRACES,
aggregateOperator: 'noop',
aggregateAttribute: {
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items:
tracesFilters?.items?.filter((item) => item.key?.key !== 'host.name') ||
[],
op: 'AND',
},
},
],
},
}),
[currentQuery, tracesFilters?.items],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
const { queryData: paginationQueryData } = useUrlQueryData<Pagination>(
QueryParams.pagination,
);
const queryPayload = useMemo(
() =>
getHostTracesQueryPayload(
timeRange.startTime,
timeRange.endTime,
paginationQueryData?.offset || offset,
tracesFilters,
),
[
timeRange.startTime,
timeRange.endTime,
offset,
tracesFilters,
paginationQueryData,
],
);
const { data, isLoading, isFetching, isError } = useQuery({
queryKey: [
'hostMetricTraces',
timeRange.startTime,
timeRange.endTime,
offset,
tracesFilters,
DEFAULT_ENTITY_VERSION,
paginationQueryData,
],
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
enabled: !!queryPayload,
});
const traceListColumns = getListColumns(selectedColumns);
useEffect(() => {
if (data?.payload?.data?.newResult?.data?.result) {
const currentData = data.payload.data.newResult.data.result;
if (currentData.length > 0 && currentData[0].list) {
if (offset === 0) {
setTraces(currentData[0].list ?? []);
} else {
setTraces((prev) => [...prev, ...(currentData[0].list ?? [])]);
}
}
}
}, [data, offset]);
const isDataEmpty =
!isLoading && !isFetching && !isError && traces.length === 0;
const hasAdditionalFilters =
tracesFilters?.items && tracesFilters?.items?.length > 1;
const totalCount =
data?.payload?.data?.newResult?.data?.result?.[0]?.list?.length || 0;
const handleRowClick = useCallback(() => {
logEvent(InfraMonitoringEvents.ItemClicked, {
entity: InfraMonitoringEvents.HostEntity,
view: InfraMonitoringEvents.TracesView,
});
}, []);
return (
<div className="host-metric-traces">
<div className="host-metric-traces-header">
<div className="filter-section">
{query && (
<QueryBuilderSearch
query={query as IBuilderQuery}
onChange={(value): void =>
handleChangeTracesFilters(value, VIEWS.TRACES)
}
disableNavigationShortcuts
/>
)}
</div>
<div className="datetime-section">
<DateTimeSelectionV2
showAutoRefresh
showRefreshText={false}
hideShareModal
isModalTimeSelection={isModalTimeSelection}
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>
{isError && <ErrorText>{data?.error || 'Something went wrong'}</ErrorText>}
{isLoading && traces.length === 0 && <TracesLoading />}
{isDataEmpty && !hasAdditionalFilters && (
<NoLogs dataSource={DataSource.TRACES} />
)}
{isDataEmpty && hasAdditionalFilters && (
<EmptyLogsSearch dataSource={DataSource.TRACES} panelType="LIST" />
)}
{!isError && traces.length > 0 && (
<div className="host-metric-traces-table">
<TraceExplorerControls
isLoading={isFetching && traces.length === 0}
totalCount={totalCount}
perPageOptions={PER_PAGE_OPTIONS}
showSizeChanger={false}
/>
<ResizeTable
tableLayout="fixed"
pagination={false}
scroll={{ x: true }}
loading={isFetching && traces.length === 0}
dataSource={traces}
columns={traceListColumns}
onRow={(): Record<string, unknown> => ({
onClick: (): void => handleRowClick(),
})}
/>
</div>
)}
</div>
);
}
export default HostMetricTraces;

View File

@@ -0,0 +1,183 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import {
BaseAutocompleteData,
DataTypes,
} from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { nanoToMilli } from 'utils/timeUtils';
export const columns = [
{
dataIndex: 'timestamp',
key: 'timestamp',
title: 'Timestamp',
width: 200,
render: (timestamp: string): string => new Date(timestamp).toLocaleString(),
},
{
title: 'Service Name',
dataIndex: ['data', 'serviceName'],
key: 'serviceName-string-tag',
width: 150,
},
{
title: 'Name',
dataIndex: ['data', 'name'],
key: 'name-string-tag',
width: 145,
},
{
title: 'Duration',
dataIndex: ['data', 'durationNano'],
key: 'durationNano-float64-tag',
width: 145,
render: (duration: number): string => `${nanoToMilli(duration)}ms`,
},
{
title: 'HTTP Method',
dataIndex: ['data', 'httpMethod'],
key: 'httpMethod-string-tag',
width: 145,
},
{
title: 'Status Code',
dataIndex: ['data', 'responseStatusCode'],
key: 'responseStatusCode-string-tag',
width: 145,
},
];
export const selectedColumns: BaseAutocompleteData[] = [
{
key: 'timestamp',
dataType: DataTypes.String,
type: 'tag',
},
{
key: 'serviceName',
dataType: DataTypes.String,
type: 'tag',
},
{
key: 'name',
dataType: DataTypes.String,
type: 'tag',
},
{
key: 'durationNano',
dataType: DataTypes.Float64,
type: 'tag',
},
{
key: 'httpMethod',
dataType: DataTypes.String,
type: 'tag',
},
{
key: 'responseStatusCode',
dataType: DataTypes.String,
type: 'tag',
},
];
export const getHostTracesQueryPayload = (
start: number,
end: number,
offset = 0,
filters: IBuilderQuery['filters'],
): GetQueryResultsProps => ({
query: {
promql: [],
clickhouse_sql: [],
builder: {
queryData: [
{
dataSource: DataSource.TRACES,
queryName: 'A',
aggregateOperator: 'noop',
aggregateAttribute: {
id: '------false',
dataType: DataTypes.EMPTY,
key: '',
type: '',
},
timeAggregation: 'rate',
spaceAggregation: 'sum',
functions: [],
filters,
expression: 'A',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [
{
columnName: 'timestamp',
order: 'desc',
},
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
},
],
queryFormulas: [],
queryTraceOperator: [],
},
id: '572f1d91-6ac0-46c0-b726-c21488b34434',
queryType: EQueryType.QUERY_BUILDER,
},
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
start,
end,
params: {
dataSource: DataSource.TRACES,
},
tableParams: {
pagination: {
limit: 10,
offset,
},
selectColumns: [
{
key: 'serviceName',
dataType: 'string',
type: 'tag',
id: 'serviceName--string--tag--true',
isIndexed: false,
},
{
key: 'name',
dataType: 'string',
type: 'tag',
id: 'name--string--tag--true',
isIndexed: false,
},
{
key: 'durationNano',
dataType: 'float64',
type: 'tag',
id: 'durationNano--float64--tag--true',
isIndexed: false,
},
{
key: 'httpMethod',
dataType: 'string',
type: 'tag',
id: 'httpMethod--string--tag--true',
isIndexed: false,
},
{
key: 'responseStatusCode',
dataType: 'string',
type: 'tag',
id: 'responseStatusCode--string--tag--true',
isIndexed: false,
},
],
},
});

View File

@@ -18,7 +18,7 @@ const keyToLabelMap: Record<string, string> = {
responseStatusCode: 'Status Code',
};
export const getTraceListColumns = (
export const getListColumns = (
selectedColumns: BaseAutocompleteData[],
): ColumnsType<RowData> => {
const columns: ColumnsType<RowData> =

View File

@@ -0,0 +1,233 @@
.host-detail-drawer {
border-left: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
.ant-drawer-header {
padding: 8px 16px;
border-bottom: none;
align-items: stretch;
border-bottom: 1px solid var(--bg-slate-500);
background: var(--bg-ink-400);
}
.ant-drawer-close {
margin-inline-end: 0px;
}
.ant-drawer-body {
display: flex;
flex-direction: column;
padding: 16px;
}
.title {
color: var(--text-vanilla-400);
font-family: 'Geist Mono';
font-size: 14px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.radio-button {
display: flex;
align-items: center;
justify-content: center;
padding-top: var(--padding-1);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.host-detail-drawer__host {
.host-details-grid {
.labels-row,
.values-row {
display: grid;
grid-template-columns: 1fr 1.5fr 1.5fr 1.5fr;
gap: 30px;
align-items: center;
}
.labels-row {
margin-bottom: 8px;
}
.host-details-metadata-label {
color: var(--text-vanilla-400);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 500;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
text-transform: uppercase;
}
.status-tag {
margin: 0;
&.active {
color: var(--success-500);
background: var(--success-100);
border-color: var(--success-500);
}
&.inactive {
color: var(--error-500);
background: var(--error-100);
border-color: var(--error-500);
}
}
.progress-container {
width: 158px;
.ant-progress {
margin: 0;
.ant-progress-text {
font-weight: 600;
}
}
}
.ant-card {
&.ant-card-bordered {
border: 1px solid var(--bg-slate-500) !important;
}
}
}
}
.tabs-and-search {
display: flex;
justify-content: space-between;
align-items: center;
margin: 16px 0;
.action-btn {
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
}
.views-tabs-container {
margin-top: 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
.views-tabs {
color: var(--text-vanilla-400);
.view-title {
display: flex;
gap: var(--margin-2);
align-items: center;
justify-content: center;
font-size: var(--font-size-xs);
font-style: normal;
font-weight: var(--font-weight-normal);
}
.tab {
border: 1px solid var(--bg-slate-400);
width: 114px;
}
.tab::before {
background: var(--bg-slate-400);
}
.selected_view {
background: var(--bg-slate-300);
color: var(--text-vanilla-100);
border: 1px solid var(--bg-slate-400);
}
.selected_view::before {
background: var(--bg-slate-400);
}
}
.compass-button {
width: 30px;
height: 30px;
border-radius: 2px;
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
}
.ant-drawer-close {
padding: 0px;
}
}
.lightMode {
.ant-drawer-header {
border-bottom: 1px solid var(--bg-vanilla-400);
background: var(--bg-vanilla-100);
}
.host-detail-drawer {
.title {
color: var(--text-ink-300);
}
.host-detail-drawer__host {
.ant-typography {
color: var(--text-ink-300);
background: transparent;
}
}
.radio-button {
border: 1px solid var(--bg-vanilla-400);
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
}
.views-tabs {
.tab {
background: var(--bg-vanilla-100);
}
.selected_view {
background: var(--bg-vanilla-300);
border: 1px solid var(--bg-slate-300);
color: var(--text-ink-400);
}
.selected_view::before {
background: var(--bg-vanilla-300);
border-left: 1px solid var(--bg-slate-300);
}
}
.compass-button {
border: 1px solid var(--bg-vanilla-300);
background: var(--bg-vanilla-100);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
.tabs-and-search {
.action-btn {
border: 1px solid var(--bg-vanilla-400);
background: var(--bg-vanilla-100);
color: var(--text-ink-300);
}
}
}
}

View File

@@ -0,0 +1,595 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
// eslint-disable-next-line no-restricted-imports
import { useSelector } from 'react-redux';
import { useSearchParams } from 'react-router-dom-v5-compat';
import { Color, Spacing } from '@signozhq/design-tokens';
import {
Button,
Divider,
Drawer,
Progress,
Radio,
Tag,
Typography,
} from 'antd';
import type { RadioChangeEvent } from 'antd/lib';
import logEvent from 'api/common/logEvent';
import { InfraMonitoringEvents } from 'constants/events';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
initialQueryState,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { getFiltersFromParams } from 'container/InfraMonitoringK8s/commonUtils';
import { INFRA_MONITORING_K8S_PARAMS_KEYS } from 'container/InfraMonitoringK8s/constants';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import useUrlQuery from 'hooks/useUrlQuery';
import GetMinMax from 'lib/getMinMax';
import {
BarChart2,
ChevronsLeftRight,
Compass,
DraftingCompass,
Package2,
ScrollText,
X,
} from 'lucide-react';
import { AppState } from 'store/reducers';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import {
IBuilderQuery,
TagFilterItem,
} from 'types/api/queryBuilder/queryBuilderData';
import {
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { GlobalReducer } from 'types/reducer/globalTime';
import { v4 as uuidv4 } from 'uuid';
import { VIEW_TYPES, VIEWS } from './constants';
import Containers from './Containers/Containers';
import { HostDetailProps } from './HostMetricDetail.interfaces';
import HostMetricLogsDetailedView from './HostMetricsLogs/HostMetricLogsDetailedView';
import HostMetricTraces from './HostMetricTraces/HostMetricTraces';
import Metrics from './Metrics/Metrics';
import Processes from './Processes/Processes';
import './HostMetricsDetail.styles.scss';
// eslint-disable-next-line sonarjs/cognitive-complexity
function HostMetricsDetails({
host,
onClose,
isModalTimeSelection,
}: HostDetailProps): JSX.Element {
const { maxTime, minTime, selectedTime } = useSelector<
AppState,
GlobalReducer
>((state) => state.globalTime);
const [searchParams, setSearchParams] = useSearchParams();
const startMs = useMemo(() => Math.floor(Number(minTime) / 1000000000), [
minTime,
]);
const endMs = useMemo(() => Math.floor(Number(maxTime) / 1000000000), [
maxTime,
]);
const urlQuery = useUrlQuery();
const [modalTimeRange, setModalTimeRange] = useState(() => ({
startTime: startMs,
endTime: endMs,
}));
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: (selectedTime as Time),
);
const [selectedView, setSelectedView] = useState<VIEWS>(
(searchParams.get('view') as VIEWS) || VIEWS.METRICS,
);
const isDarkMode = useIsDarkMode();
const initialFilters = useMemo(() => {
const urlView = searchParams.get(INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW);
const queryKey =
urlView === VIEW_TYPES.LOGS
? INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS
: INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS;
const filters = getFiltersFromParams(searchParams, queryKey);
if (filters) {
return filters;
}
return {
op: 'AND',
items: [
{
id: uuidv4(),
key: {
key: 'host.name',
dataType: DataTypes.String,
type: 'resource',
id: 'host.name--string--resource--false',
},
op: '=',
value: host?.hostName || '',
},
],
};
}, [host?.hostName, searchParams]);
const [logFilters, setLogFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
const [tracesFilters, setTracesFilters] = useState<IBuilderQuery['filters']>(
initialFilters,
);
useEffect(() => {
if (host) {
logEvent(InfraMonitoringEvents.PageVisited, {
entity: InfraMonitoringEvents.HostEntity,
page: InfraMonitoringEvents.DetailedPage,
});
}
}, [host]);
useEffect(() => {
setLogFilters(initialFilters);
setTracesFilters(initialFilters);
}, [initialFilters]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
setSelectedInterval(currentSelectedInterval as Time);
if (currentSelectedInterval !== 'custom') {
const { maxTime, minTime } = GetMinMax(currentSelectedInterval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
endTime: Math.floor(maxTime / 1000000000),
});
}
}, [selectedTime, minTime, maxTime]);
const handleTabChange = (e: RadioChangeEvent): void => {
setSelectedView(e.target.value);
if (host?.hostName) {
setSelectedView(e.target.value);
setSearchParams({
...Object.fromEntries(searchParams.entries()),
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: e.target.value,
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(null),
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(null),
});
}
logEvent(InfraMonitoringEvents.TabChanged, {
entity: InfraMonitoringEvents.HostEntity,
view: e.target.value,
});
};
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
setModalTimeRange({
startTime: Math.floor(dateTimeRange[0] / 1000),
endTime: Math.floor(dateTimeRange[1] / 1000),
});
} else {
const { maxTime, minTime } = GetMinMax(interval);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
endTime: Math.floor(maxTime / 1000000000),
});
}
logEvent(InfraMonitoringEvents.TimeUpdated, {
entity: InfraMonitoringEvents.HostEntity,
interval,
page: InfraMonitoringEvents.DetailedPage,
});
},
[],
);
const handleChangeLogFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setLogFilters((prevFilters) => {
const hostNameFilter = prevFilters?.items?.find(
(item) => item.key?.key === 'host.name',
);
const paginationFilter = value?.items?.find(
(item) => item.key?.key === 'id',
);
const newFilters = value?.items?.filter(
(item) => item.key?.key !== 'id' && item.key?.key !== 'host.name',
);
if (newFilters && newFilters?.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.HostEntity,
view: InfraMonitoringEvents.LogsView,
page: InfraMonitoringEvents.DetailedPage,
});
}
const updatedFilters = {
op: 'AND',
items: [
hostNameFilter,
...(newFilters || []),
...(paginationFilter ? [paginationFilter] : []),
].filter((item): item is TagFilterItem => item !== undefined),
};
setSearchParams({
...Object.fromEntries(searchParams.entries()),
[INFRA_MONITORING_K8S_PARAMS_KEYS.LOG_FILTERS]: JSON.stringify(
updatedFilters,
),
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
});
return updatedFilters;
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const handleChangeTracesFilters = useCallback(
(value: IBuilderQuery['filters'], view: VIEWS) => {
setTracesFilters((prevFilters) => {
const hostNameFilter = prevFilters?.items?.find(
(item) => item.key?.key === 'host.name',
);
if (value?.items && value?.items?.length > 0) {
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.HostEntity,
view: InfraMonitoringEvents.TracesView,
page: InfraMonitoringEvents.DetailedPage,
});
}
const updatedFilters = {
op: 'AND',
items: [
hostNameFilter,
...(value?.items?.filter((item) => item.key?.key !== 'host.name') || []),
].filter((item): item is TagFilterItem => item !== undefined),
};
setSearchParams({
...Object.fromEntries(searchParams.entries()),
[INFRA_MONITORING_K8S_PARAMS_KEYS.TRACES_FILTERS]: JSON.stringify(
updatedFilters,
),
[INFRA_MONITORING_K8S_PARAMS_KEYS.VIEW]: view,
});
return updatedFilters;
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const handleExplorePagesRedirect = (): void => {
if (selectedInterval !== 'custom') {
urlQuery.set(QueryParams.relativeTime, selectedInterval);
} else {
urlQuery.delete(QueryParams.relativeTime);
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
}
logEvent(InfraMonitoringEvents.ExploreClicked, {
view: selectedView,
entity: InfraMonitoringEvents.HostEntity,
page: InfraMonitoringEvents.DetailedPage,
});
if (selectedView === VIEW_TYPES.LOGS) {
const filtersWithoutPagination = {
...logFilters,
items: logFilters?.items?.filter((item) => item.key?.key !== 'id') || [],
};
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.logs,
aggregateOperator: LogsAggregatorOperator.NOOP,
filters: filtersWithoutPagination,
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
} else if (selectedView === VIEW_TYPES.TRACES) {
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
filters: tracesFilters,
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
window.open(
`${window.location.origin}${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`,
'_blank',
);
}
};
const handleClose = (): void => {
setSelectedInterval(selectedTime as Time);
lastSelectedInterval.current = null;
setSearchParams({});
if (selectedTime !== 'custom') {
const { maxTime, minTime } = GetMinMax(selectedTime);
setModalTimeRange({
startTime: Math.floor(minTime / 1000000000),
endTime: Math.floor(maxTime / 1000000000),
});
}
setSelectedView(VIEW_TYPES.METRICS);
onClose();
};
return (
<Drawer
width="70%"
title={
<>
<Divider type="vertical" />
<Typography.Text className="title">{host?.hostName}</Typography.Text>
</>
}
placement="right"
onClose={handleClose}
open={!!host}
style={{
overscrollBehavior: 'contain',
background: isDarkMode ? Color.BG_INK_400 : Color.BG_VANILLA_100,
}}
className="host-detail-drawer"
destroyOnClose
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
>
{host && (
<>
<div className="host-detail-drawer__host">
<div className="host-details-grid">
<div className="labels-row">
<Typography.Text
type="secondary"
className="host-details-metadata-label"
>
STATUS
</Typography.Text>
<Typography.Text
type="secondary"
className="host-details-metadata-label"
>
OPERATING SYSTEM
</Typography.Text>
<Typography.Text
type="secondary"
className="host-details-metadata-label"
>
CPU USAGE
</Typography.Text>
<Typography.Text
type="secondary"
className="host-details-metadata-label"
>
MEMORY USAGE
</Typography.Text>
</div>
<div className="values-row">
<Tag
bordered
className={`infra-monitoring-tags ${
host.active ? 'active' : 'inactive'
}`}
>
{host.active ? 'ACTIVE' : 'INACTIVE'}
</Tag>
{host.os ? (
<Tag className="infra-monitoring-tags" bordered>
{host.os}
</Tag>
) : (
<Typography.Text>-</Typography.Text>
)}
<div className="progress-container">
<Progress
percent={Number((host.cpu * 100).toFixed(1))}
size="small"
strokeColor={((): string => {
const cpuPercent = Number((host.cpu * 100).toFixed(1));
if (cpuPercent >= 90) {
return Color.BG_SAKURA_500;
}
if (cpuPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
</div>
<div className="progress-container">
<Progress
percent={Number((host.memory * 100).toFixed(1))}
size="small"
strokeColor={((): string => {
const memoryPercent = Number((host.memory * 100).toFixed(1));
if (memoryPercent >= 90) {
return Color.BG_CHERRY_500;
}
if (memoryPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
</div>
</div>
</div>
</div>
<div className="views-tabs-container">
<Radio.Group
className="views-tabs"
onChange={handleTabChange}
value={selectedView}
>
<Radio.Button
className={
selectedView === VIEW_TYPES.METRICS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.METRICS}
>
<div className="view-title">
<BarChart2 size={14} />
Metrics
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.LOGS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.LOGS}
>
<div className="view-title">
<ScrollText size={14} />
Logs
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.TRACES ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.TRACES}
>
<div className="view-title">
<DraftingCompass size={14} />
Traces
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.CONTAINERS ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.CONTAINERS}
>
<div className="view-title">
<Package2 size={14} />
Containers
</div>
</Radio.Button>
<Radio.Button
className={
selectedView === VIEW_TYPES.PROCESSES ? 'selected_view tab' : 'tab'
}
value={VIEW_TYPES.PROCESSES}
>
<div className="view-title">
<ChevronsLeftRight size={14} />
Processes
</div>
</Radio.Button>
</Radio.Group>
{(selectedView === VIEW_TYPES.LOGS ||
selectedView === VIEW_TYPES.TRACES) && (
<Button
icon={<Compass size={18} />}
className="compass-button"
onClick={handleExplorePagesRedirect}
/>
)}
</div>
{selectedView === VIEW_TYPES.METRICS && (
<Metrics
selectedInterval={selectedInterval}
hostName={host.hostName}
timeRange={modalTimeRange}
handleTimeChange={handleTimeChange}
isModalTimeSelection={isModalTimeSelection}
/>
)}
{selectedView === VIEW_TYPES.LOGS && (
<HostMetricLogsDetailedView
timeRange={modalTimeRange}
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeLogFilters={handleChangeLogFilters}
logFilters={logFilters}
selectedInterval={selectedInterval}
/>
)}
{selectedView === VIEW_TYPES.TRACES && (
<HostMetricTraces
timeRange={modalTimeRange}
isModalTimeSelection={isModalTimeSelection}
handleTimeChange={handleTimeChange}
handleChangeTracesFilters={handleChangeTracesFilters}
tracesFilters={tracesFilters}
selectedInterval={selectedInterval}
/>
)}
{selectedView === VIEW_TYPES.CONTAINERS && <Containers />}
{selectedView === VIEW_TYPES.PROCESSES && <Processes />}
</>
)}
</Drawer>
);
}
export default HostMetricsDetails;

View File

@@ -0,0 +1,133 @@
.host-metrics-logs-container {
margin-top: 1rem;
.filter-section {
flex: 1;
.ant-select-selector {
border-radius: 2px;
border: 1px solid var(--bg-slate-400) !important;
background-color: var(--bg-ink-300) !important;
input {
font-size: 12px;
}
.ant-tag .ant-typography {
font-size: 12px;
}
}
}
.host-metrics-logs-header {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 12px;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
}
.host-metrics-logs {
margin-top: 1rem;
.virtuoso-list {
overflow-y: hidden !important;
&::-webkit-scrollbar {
width: 0.3rem;
height: 0.3rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--bg-slate-300);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--bg-slate-200);
}
.ant-row {
width: fit-content;
}
}
.skeleton-container {
height: 100%;
padding: 16px;
}
}
}
.host-metrics-logs-list-container {
flex: 1;
height: calc(100vh - 272px) !important;
display: flex;
height: 100%;
.raw-log-content {
width: 100%;
text-wrap: inherit;
word-wrap: break-word;
}
}
.host-metrics-logs-list-card {
width: 100%;
margin-top: 12px;
.ant-card-body {
padding: 0;
height: 100%;
width: 100%;
}
}
.logs-loading-skeleton {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
padding: 8px 0;
.ant-skeleton-input-sm {
height: 18px;
}
}
.no-logs-found {
height: 50vh;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding: 24px;
box-sizing: border-box;
.ant-typography {
display: flex;
align-items: center;
gap: 16px;
}
}
.lightMode {
.filter-section {
border-top: 1px solid var(--bg-vanilla-300);
border-bottom: 1px solid var(--bg-vanilla-300);
.ant-select-selector {
border-color: var(--bg-vanilla-300) !important;
background-color: var(--bg-vanilla-100) !important;
color: var(--bg-ink-200);
}
}
}

View File

@@ -0,0 +1,100 @@
import { useMemo } from 'react';
import QueryBuilderSearch from 'container/QueryBuilder/filters/QueryBuilderSearch';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource } from 'types/common/queryBuilder';
import { VIEWS } from '../constants';
import HostMetricsLogs from './HostMetricsLogs';
import './HostMetricLogs.styles.scss';
interface Props {
timeRange: {
startTime: number;
endTime: number;
};
isModalTimeSelection: boolean;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
handleChangeLogFilters: (value: IBuilderQuery['filters'], view: VIEWS) => void;
logFilters: IBuilderQuery['filters'];
selectedInterval: Time;
}
function HostMetricLogsDetailedView({
timeRange,
isModalTimeSelection,
handleTimeChange,
handleChangeLogFilters,
logFilters,
selectedInterval,
}: Props): JSX.Element {
const { currentQuery } = useQueryBuilder();
const updatedCurrentQuery = useMemo(
() => ({
...currentQuery,
builder: {
...currentQuery.builder,
queryData: [
{
...currentQuery.builder.queryData[0],
dataSource: DataSource.LOGS,
aggregateOperator: 'noop',
aggregateAttribute: {
...currentQuery.builder.queryData[0].aggregateAttribute,
},
filters: {
items:
logFilters?.items?.filter((item) => item.key?.key !== 'host.name') ||
[],
op: 'AND',
},
},
],
},
}),
[currentQuery, logFilters?.items],
);
const query = updatedCurrentQuery?.builder?.queryData[0] || null;
return (
<div className="host-metrics-logs-container">
<div className="host-metrics-logs-header">
<div className="filter-section">
{query && (
<QueryBuilderSearch
query={query as IBuilderQuery}
onChange={(value): void => handleChangeLogFilters(value, VIEWS.LOGS)}
disableNavigationShortcuts
/>
)}
</div>
<div className="datetime-section">
<DateTimeSelectionV2
showAutoRefresh
showRefreshText={false}
hideShareModal
isModalTimeSelection={isModalTimeSelection}
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>
<HostMetricsLogs timeRange={timeRange} filters={logFilters} />
</div>
);
}
export default HostMetricLogsDetailedView;

View File

@@ -0,0 +1,187 @@
import { useCallback, useEffect, useMemo, useRef } from 'react';
import { useQuery } from 'react-query';
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { Card } from 'antd';
import LogDetail from 'components/LogDetail';
import RawLogView from 'components/Logs/RawLogView';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import { DEFAULT_ENTITY_VERSION } from 'constants/app';
import LogsError from 'container/LogsError/LogsError';
import { LogsLoading } from 'container/LogsLoading/LogsLoading';
import { FontSize } from 'container/OptionsMenu/types';
import { useHandleLogsPagination } from 'hooks/infraMonitoring/useHandleLogsPagination';
import useLogDetailHandlers from 'hooks/logs/useLogDetailHandlers';
import useScrollToLog from 'hooks/logs/useScrollToLog';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { ILog } from 'types/api/logs/log';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { getHostLogsQueryPayload } from './constants';
import NoLogsContainer from './NoLogsContainer';
import './HostMetricLogs.styles.scss';
interface Props {
timeRange: {
startTime: number;
endTime: number;
};
filters: IBuilderQuery['filters'];
}
function HostMetricsLogs({ timeRange, filters }: Props): JSX.Element {
const virtuosoRef = useRef<VirtuosoHandle>(null);
const {
activeLog,
onAddToQuery,
selectedTab,
handleSetActiveLog,
handleCloseLogDetail,
} = useLogDetailHandlers();
const basePayload = getHostLogsQueryPayload(
timeRange.startTime,
timeRange.endTime,
filters,
);
const {
logs,
hasReachedEndOfLogs,
isPaginating,
currentPage,
setIsPaginating,
handleNewData,
loadMoreLogs,
queryPayload,
} = useHandleLogsPagination({
timeRange,
filters,
excludeFilterKeys: ['host.name'],
basePayload,
});
const { data, isLoading, isFetching, isError } = useQuery({
queryKey: [
'hostMetricsLogs',
timeRange.startTime,
timeRange.endTime,
filters,
currentPage,
],
queryFn: () => GetMetricQueryRange(queryPayload, DEFAULT_ENTITY_VERSION),
enabled: !!queryPayload,
keepPreviousData: isPaginating,
});
useEffect(() => {
if (data?.payload?.data?.newResult?.data?.result) {
handleNewData(data.payload.data.newResult.data.result);
}
}, [data, handleNewData]);
useEffect(() => {
setIsPaginating(false);
}, [data, setIsPaginating]);
const handleScrollToLog = useScrollToLog({
logs,
virtuosoRef,
});
const getItemContent = useCallback(
(_: number, logToRender: ILog): JSX.Element => {
return (
<div key={logToRender.id}>
<RawLogView
isTextOverflowEllipsisDisabled
data={logToRender}
linesPerRow={5}
fontSize={FontSize.MEDIUM}
selectedFields={[
{
dataType: 'string',
type: '',
name: 'body',
},
{
dataType: 'string',
type: '',
name: 'timestamp',
},
]}
onSetActiveLog={handleSetActiveLog}
onClearActiveLog={handleCloseLogDetail}
isActiveLog={activeLog?.id === logToRender.id}
/>
</div>
);
},
[activeLog, handleSetActiveLog, handleCloseLogDetail],
);
const renderFooter = useCallback(
(): JSX.Element | null => (
<>
{isFetching ? (
<div className="logs-loading-skeleton"> Loading more logs ... </div>
) : hasReachedEndOfLogs ? (
<div className="logs-loading-skeleton"> *** End *** </div>
) : null}
</>
),
[isFetching, hasReachedEndOfLogs],
);
const renderContent = useMemo(
() => (
<Card bordered={false} className="host-metrics-logs-list-card">
<OverlayScrollbar isVirtuoso>
<Virtuoso
className="host-metrics-logs-virtuoso"
key="host-metrics-logs-virtuoso"
ref={virtuosoRef}
data={logs}
endReached={loadMoreLogs}
totalCount={logs.length}
itemContent={getItemContent}
overscan={200}
components={{
Footer: renderFooter,
}}
/>
</OverlayScrollbar>
</Card>
),
[logs, loadMoreLogs, getItemContent, renderFooter],
);
return (
<div className="host-metrics-logs">
{isLoading && <LogsLoading />}
{!isLoading && !isError && logs.length === 0 && <NoLogsContainer />}
{isError && !isLoading && <LogsError />}
{!isLoading && !isError && logs.length > 0 && (
<div
className="host-metrics-logs-list-container"
data-log-detail-ignore="true"
>
{renderContent}
</div>
)}
{selectedTab && activeLog && (
<LogDetail
log={activeLog}
onClose={handleCloseLogDetail}
logs={logs}
onNavigateLog={handleSetActiveLog}
selectedTab={selectedTab}
onAddToQuery={onAddToQuery}
onClickActionItem={onAddToQuery}
onScrollToLog={handleScrollToLog}
/>
)}
</div>
);
}
export default HostMetricsLogs;

View File

@@ -0,0 +1,16 @@
import { Color } from '@signozhq/design-tokens';
import { Typography } from 'antd';
import { Ghost } from 'lucide-react';
const { Text } = Typography;
export default function NoLogsContainer(): React.ReactElement {
return (
<div className="no-logs-found">
<Text type="secondary">
<Ghost size={24} color={Color.BG_AMBER_500} /> No logs found for this host
in the selected time range.
</Text>
</div>
);
}

View File

@@ -0,0 +1,61 @@
import { PANEL_TYPES } from 'constants/queryBuilder';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { IBuilderQuery } from 'types/api/queryBuilder/queryBuilderData';
import { EQueryType } from 'types/common/dashboard';
import { DataSource, ReduceOperators } from 'types/common/queryBuilder';
import { v4 as uuidv4 } from 'uuid';
export const getHostLogsQueryPayload = (
start: number,
end: number,
filters: IBuilderQuery['filters'],
): GetQueryResultsProps => ({
graphType: PANEL_TYPES.LIST,
selectedTime: 'GLOBAL_TIME',
query: {
clickhouse_sql: [],
promql: [],
builder: {
queryData: [
{
dataSource: DataSource.LOGS,
queryName: 'A',
aggregateOperator: 'noop',
aggregateAttribute: {
id: '------false',
dataType: DataTypes.String,
key: '',
type: '',
},
timeAggregation: 'rate',
spaceAggregation: 'sum',
functions: [],
filters,
expression: 'A',
disabled: false,
stepInterval: 60,
having: [],
limit: null,
orderBy: [
{
columnName: 'timestamp',
order: 'desc',
},
],
groupBy: [],
legend: '',
reduceTo: ReduceOperators.AVG,
offset: 0,
pageSize: 100,
},
],
queryFormulas: [],
queryTraceOperator: [],
},
id: uuidv4(),
queryType: EQueryType.QUERY_BUILDER,
},
start,
end,
});

View File

@@ -0,0 +1,45 @@
.empty-container {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
.host-metrics-container {
margin-top: 1rem;
}
.metrics-header {
display: flex;
justify-content: flex-end;
margin-top: 1rem;
gap: 8px;
padding: 12px;
border-radius: 3px;
border: 1px solid var(--bg-slate-500);
}
.host-metrics-card {
margin: 8px 0 1rem 0;
height: 300px;
padding: 10px;
border: 1px solid var(--bg-slate-500);
.ant-card-body {
padding: 0;
}
.chart-container {
width: 100%;
height: 100%;
}
.no-data-container {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}

View File

@@ -0,0 +1,233 @@
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { QueryFunctionContext, useQueries, UseQueryResult } from 'react-query';
import { Card, Col, Row, Skeleton, Typography } from 'antd';
import cx from 'classnames';
import Uplot from 'components/Uplot';
import { ENTITY_VERSION_V4 } from 'constants/app';
import {
getHostQueryPayload,
hostWidgetInfo,
} from 'container/LogDetailedView/InfraMetrics/constants';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { useResizeObserver } from 'hooks/useDimensions';
import { useMultiIntersectionObserver } from 'hooks/useMultiIntersectionObserver';
import { GetMetricQueryRange } from 'lib/dashboard/getQueryResults';
import { getUPlotChartOptions } from 'lib/uPlotLib/getUplotChartOptions';
import { getUPlotChartData } from 'lib/uPlotLib/utils/getUplotChartData';
import { SuccessResponse } from 'types/api';
import { MetricRangePayloadProps } from 'types/api/metrics/getQueryRange';
import { FeatureKeys } from '../../../constants/features';
import { useAppContext } from '../../../providers/App/App';
import './Metrics.styles.scss';
interface MetricsTabProps {
timeRange: {
startTime: number;
endTime: number;
};
isModalTimeSelection: boolean;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
selectedInterval: Time;
hostName: string;
}
function Metrics({
selectedInterval,
hostName,
timeRange,
handleTimeChange,
isModalTimeSelection,
}: MetricsTabProps): JSX.Element {
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const {
visibilities,
setElement,
} = useMultiIntersectionObserver(hostWidgetInfo.length, { threshold: 0.1 });
const legendScrollPositionRef = useRef<{
scrollTop: number;
scrollLeft: number;
}>({
scrollTop: 0,
scrollLeft: 0,
});
const queryPayloads = useMemo(
() =>
getHostQueryPayload(
hostName,
timeRange.startTime,
timeRange.endTime,
dotMetricsEnabled,
),
[hostName, timeRange.startTime, timeRange.endTime, dotMetricsEnabled],
);
const queries = useQueries(
queryPayloads.map((payload, index) => ({
queryKey: ['host-metrics', payload, ENTITY_VERSION_V4, 'HOST'],
queryFn: ({
signal,
}: QueryFunctionContext): Promise<
SuccessResponse<MetricRangePayloadProps>
> => GetMetricQueryRange(payload, ENTITY_VERSION_V4, undefined, signal),
enabled: !!payload && visibilities[index],
keepPreviousData: true,
})),
);
const isDarkMode = useIsDarkMode();
const graphRef = useRef<HTMLDivElement>(null);
const dimensions = useResizeObserver(graphRef);
const { currentQuery } = useQueryBuilder();
const chartData = useMemo(
() => queries.map(({ data }) => getUPlotChartData(data?.payload)),
[queries],
);
const [graphTimeIntervals, setGraphTimeIntervals] = useState<
{
start: number;
end: number;
}[]
>(
new Array(queries.length).fill({
start: timeRange.startTime,
end: timeRange.endTime,
}),
);
useEffect(() => {
setGraphTimeIntervals(
new Array(queries.length).fill({
start: timeRange.startTime,
end: timeRange.endTime,
}),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timeRange]);
const onDragSelect = useCallback(
(start: number, end: number, graphIndex: number) => {
const startTimestamp = Math.trunc(start);
const endTimestamp = Math.trunc(end);
setGraphTimeIntervals((prev) => {
const newIntervals = [...prev];
newIntervals[graphIndex] = {
start: Math.floor(startTimestamp / 1000),
end: Math.floor(endTimestamp / 1000),
};
return newIntervals;
});
},
[],
);
const options = useMemo(
() =>
queries.map(({ data }, idx) =>
getUPlotChartOptions({
apiResponse: data?.payload,
isDarkMode,
dimensions,
yAxisUnit: hostWidgetInfo[idx].yAxisUnit,
softMax: null,
softMin: null,
minTimeScale: graphTimeIntervals[idx].start,
maxTimeScale: graphTimeIntervals[idx].end,
onDragSelect: (start, end) => onDragSelect(start, end, idx),
query: currentQuery,
legendScrollPosition: legendScrollPositionRef.current,
setLegendScrollPosition: (position: {
scrollTop: number;
scrollLeft: number;
}) => {
legendScrollPositionRef.current = position;
},
}),
),
[
queries,
isDarkMode,
dimensions,
graphTimeIntervals,
onDragSelect,
currentQuery,
],
);
const renderCardContent = (
query: UseQueryResult<SuccessResponse<MetricRangePayloadProps>, unknown>,
idx: number,
): JSX.Element => {
if ((!query.data && query.isLoading) || !visibilities[idx]) {
return <Skeleton />;
}
if (query.error) {
const errorMessage =
(query.error as Error)?.message || 'Something went wrong';
return <div>{errorMessage}</div>;
}
return (
<div
className={cx('chart-container', {
'no-data-container':
!query.isLoading && !query?.data?.payload?.data?.result?.length,
})}
>
<Uplot options={options[idx]} data={chartData[idx]} />
</div>
);
};
return (
<>
<div className="metrics-header">
<div className="metrics-datetime-section">
<DateTimeSelectionV2
showAutoRefresh
showRefreshText={false}
hideShareModal
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
isModalTimeSelection={isModalTimeSelection}
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
</div>
<Row gutter={24} className="host-metrics-container">
{queries.map((query, idx) => (
<Col ref={setElement(idx)} span={12} key={hostWidgetInfo[idx].title}>
<Typography.Text>{hostWidgetInfo[idx].title}</Typography.Text>
<Card bordered className="host-metrics-card" ref={graphRef}>
{renderCardContent(query, idx)}
</Card>
</Col>
))}
</Row>
</>
);
}
export default Metrics;

View File

@@ -28,7 +28,7 @@
.infra-container-card-text {
font-size: var(--font-size-sm);
color: var(--l2-foreground);
color: var(--text-vanilla-400);
line-height: 20px;
letter-spacing: -0.07px;
width: 400px;
@@ -44,7 +44,7 @@
align-items: flex-start;
gap: 12px;
border-radius: 4px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
background: rgba(171, 189, 255, 0.04);
.ant-space {
align-items: flex-start;
@@ -58,3 +58,9 @@
margin: auto;
}
}
.lightMode {
.infra-container-card-text {
color: var(--text-ink-200);
}
}

View File

@@ -1,15 +1,16 @@
import { useTranslation } from 'react-i18next';
import { Space, Typography } from 'antd';
import WaitlistFragment from 'components/HostMetricsDetail/WaitlistFragment/WaitlistFragment';
import broomUrl from '@/assets/Icons/broom.svg';
import infraContainersUrl from '@/assets/Icons/infraContainers.svg';
import 'components/HostMetricsDetail/Processes/Processes.styles.scss';
import WaitlistFragment from '../WaitlistFragment/WaitlistFragment';
import './Processes.styles.scss';
const { Text } = Typography;
function EntityProcesses(): JSX.Element {
function Processes(): JSX.Element {
const { t } = useTranslation(['infraMonitoring']);
return (
@@ -42,4 +43,4 @@ function EntityProcesses(): JSX.Element {
);
}
export default EntityProcesses;
export default Processes;

View File

@@ -0,0 +1,17 @@
export enum VIEWS {
METRICS = 'metrics',
LOGS = 'logs',
TRACES = 'traces',
CONTAINERS = 'containers',
PROCESSES = 'processes',
EVENTS = 'events',
}
export const VIEW_TYPES = {
METRICS: VIEWS.METRICS,
LOGS: VIEWS.LOGS,
TRACES: VIEWS.TRACES,
CONTAINERS: VIEWS.CONTAINERS,
PROCESSES: VIEWS.PROCESSES,
EVENTS: VIEWS.EVENTS,
};

View File

@@ -0,0 +1,3 @@
import HostMetricsDetails from './HostMetricsDetails';
export default HostMetricsDetails;

View File

@@ -1,4 +1,4 @@
import { Badge } from '@signozhq/ui';
import { Badge } from '@signozhq/badge';
type BadgeColor =
| 'vanilla'

View File

@@ -5,7 +5,7 @@
border-radius: 2px 0px 0px 2px;
.label {
color: var(--l2-foreground);
color: var(--bg-vanilla-400);
font-size: 12px;
font-style: normal;
font-weight: 500;
@@ -21,8 +21,8 @@
padding: 0px 8px;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
display: flex;
justify-content: flex-start;
@@ -36,7 +36,7 @@
font-family: 'Space Mono', monospace !important;
border-radius: 2px 0px 0px 2px;
border: 1px solid var(--l1-border);
border: 1px solid var(--bg-slate-400);
border-right: none;
border-left: none;
@@ -47,7 +47,7 @@
font-size: 12px !important;
line-height: 27px;
&::placeholder {
color: var(--l2-foreground) !important;
color: var(--bg-vanilla-400) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
@@ -61,8 +61,8 @@
.close-btn {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
height: 38px;
width: 38px;
}
@@ -70,8 +70,8 @@
&.labelAfter {
.input {
border-radius: 0px 2px 2px 0px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
border: 1px solid var(--bg-slate-400);
background: var(--bg-ink-300);
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
}
@@ -83,3 +83,31 @@
}
}
}
.lightMode {
.input-with-label {
.label {
color: var(--bg-ink-500) !important;
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
}
.input {
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
}
.close-btn {
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
}
&.labelAfter {
.input {
border: 1px solid var(--bg-vanilla-300) !important;
background: var(--bg-vanilla-100) !important;
}
}
}
}

View File

@@ -19,7 +19,7 @@
font-weight: var(--label-base-400-font-weight);
line-height: var(--label-base-400-line-height);
letter-spacing: -0.065px;
color: var(--l1-foreground);
color: var(--bg-base-white);
margin: 0;
}
@@ -116,7 +116,7 @@
height: 32px;
color: var(--l1-foreground);
background-color: var(--l2-background);
border-color: var(--l1-border);
border-color: var(--border);
font-size: var(--paragraph-base-400-font-size);
&::placeholder {
@@ -149,7 +149,7 @@
.ant-select-selection-item {
font-size: var(--paragraph-base-400-font-size);
letter-spacing: -0.07px;
color: var(--l1-foreground);
color: var(--bg-base-white);
line-height: 32px;
}
}
@@ -181,7 +181,7 @@
box-shadow: none;
&:hover {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
background: rgba(229, 72, 77, 0.1);
opacity: 0.9;
}
}
@@ -196,8 +196,8 @@
}
.invite-team-members-error-callout {
background: color-mix(in srgb, var(--bg-cherry-500) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--bg-cherry-500) 20%, transparent);
background: rgba(229, 72, 77, 0.1);
border: 1px solid rgba(229, 72, 77, 0.2);
border-radius: 4px;
animation: horizontal-shaking 300ms ease-out;
}
@@ -246,3 +246,19 @@
color: var(--l1-foreground);
}
}
.lightMode {
.invite-members-modal {
[data-slot='dialog-title'] {
color: var(--bg-base-black);
}
}
.team-member-role-select {
.ant-select-selector {
.ant-select-selection-item {
color: var(--bg-base-black);
}
}
}
}

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