Compare commits

..

70 Commits

Author SHA1 Message Date
Vinícius Lourenço
5fa5b7b5a3 refactor(fonts): load Inter from design-tokens and drop the subset script 2026-11-09 21:59:36 -03:00
Vinícius Lourenço
26b3c9a55a fix(logs): animate the log details drawer open and close 2026-11-09 21:42:05 -03:00
Vinícius Lourenço
3ba120c6b7 test(alerts): check the sample alert links point at the docs in a new tab 2026-11-09 21:42:04 -03:00
Vinícius Lourenço
a42a285854 feat(logs): move the log details drawer to the new Drawer 2026-11-09 21:42:04 -03:00
Vinícius Lourenço
409ac92858 fix(dialog): keep dialogs above antd error modals like main did 2026-11-09 21:42:04 -03:00
Vinícius Lourenço
3c19f2ebc3 fix(custom-domain): show the domain update as a library toast with an action 2026-11-09 21:42:03 -03:00
Vinícius Lourenço
411fd4814d test(stories): wait for the GCP combobox to open before typing 2026-11-09 21:42:03 -03:00
Vinícius Lourenço
a40e07f899 test(stories): drop the feedback fixture toaster, the providers mount one 2026-11-09 21:42:03 -03:00
Vinícius Lourenço
52399fa34c test(stories): give the overview drawer story the entity width and open its time picker 2026-11-09 21:42:03 -03:00
Vinícius Lourenço
b367442e1b feat(k8s): step back through the entity drawer with the breadcrumb trail 2026-11-09 21:42:03 -03:00
Vinícius Lourenço
259a11bafa refactor(drawer): set stack on every drawer for the trail-capable Drawer 2026-11-09 21:42:02 -03:00
Vinícius Lourenço
7e49616554 test(stories): drop the Home feedback story, Home has no Feedback button 2026-11-09 21:42:02 -03:00
Vinícius Lourenço
d00c563e97 fix(button): render labels with fractional advances to keep word gaps 2026-11-09 21:42:02 -03:00
Vinícius Lourenço
013ea514b0 fix(fonts): mark the Inter subsets unhinted so FreeType keeps word spaces 2026-11-09 21:42:01 -03:00
Vinícius Lourenço
4c79b5e852 fix(ui): restore list heading spacing, muted and inline links, and quick filter height 2026-11-09 21:42:01 -03:00
Vinícius Lourenço
00fe373bb1 fix(button): keep the Run Query and Create new funnel labels whole 2026-11-09 21:42:01 -03:00
Vinícius Lourenço
9d94606d69 fix(k8s): restore the instrumentation checks header and the 70vw entity drawer 2026-11-09 21:42:00 -03:00
Vinícius Lourenço
1260bb7aa3 test(stories): type into Input fields and let the modal settle before the nested select 2026-11-09 21:42:00 -03:00
Vinícius Lourenço
e6b8450df7 test(stories): type into the Input field and scope picks so plays reach their end state 2026-11-09 21:42:00 -03:00
Vinícius Lourenço
bff80d4ca4 fix(input): size number inputs, label chips and the integrations search through the new Input frame 2026-11-09 21:42:00 -03:00
Vinícius Lourenço
32cb8091e6 fix(toast): keep toasts clear of drawer, dialog and page controls 2026-11-09 21:41:59 -03:00
Vinícius Lourenço
34a11e3f1d test: pick combobox and command items through the search row 2026-11-09 21:41:59 -03:00
Vinícius Lourenço
b24ed1bcce test(drawer): follow the new close button and back control 2026-11-09 21:41:59 -03:00
Vinícius Lourenço
016befce89 fix(traces): step through spans with the arrow keys inside the new drawer 2026-11-09 21:41:59 -03:00
Vinícius Lourenço
f0d064dae5 test(gcp): pick the deployment region through the combobox search row 2026-11-09 21:41:58 -03:00
Vinícius Lourenço
2b0795ca18 test: update snapshots for the new component class hashes and lazy tooltip 2026-11-09 21:41:58 -03:00
Vinícius Lourenço
3b31064884 refactor(styles): replace the removed design tokens main added since 2026-11-09 21:41:58 -03:00
Vinícius Lourenço
1fbfe4b6f4 docs(css-modules): describe the Periscope text style tokens 2026-11-09 21:41:57 -03:00
Vinícius Lourenço
271664440f refactor(styles): switch mono text to the code font family 2026-11-09 21:41:57 -03:00
Vinícius Lourenço
aab8becf26 refactor(styles): replace removed design tokens with Periscope text styles 2026-11-09 21:41:56 -03:00
Vinícius Lourenço
a263394170 refactor(styles): drop font and text CSS that renders nothing 2026-11-09 21:41:54 -03:00
Vinícius Lourenço
9966f020ee refactor(typography): migrate the files main added since the first pass 2026-11-09 21:41:50 -03:00
Vinícius Lourenço
91a4cb61e7 chore(lint): let font-face files set font descriptors 2026-11-09 21:41:50 -03:00
Vinícius Lourenço
3b214df25b perf(fonts): pin Inter at opsz 14 and split it by unicode-range 2026-11-09 21:41:49 -03:00
Vinícius Lourenço
42f16aab76 fix(fonts): ship the OFL license with Geist Mono 2026-11-09 21:41:49 -03:00
Vinícius Lourenço
4551144d8f feat(fonts): self-host Inter 4.1 so ss01, zero and case work 2026-11-09 21:41:49 -03:00
Vinícius Lourenço
2c2f8e6dee feat(styles): align root text rendering with the Periscope type tokens page 2026-11-09 21:41:49 -03:00
Vinícius Lourenço
95826abb34 fix(styles): stop font synthesis and set font smoothing once on the root 2026-11-09 21:41:48 -03:00
Vinícius Lourenço
e48d30ae05 chore(lint): warn on font and text CSS outside Typography 2026-11-09 21:41:48 -03:00
Vinícius Lourenço
385a0e3c48 refactor(typography): migrate to the reworked Typography 2026-11-09 21:41:48 -03:00
Vinícius Lourenço
c1a3dfcbb9 refactor(ui): port drawers to the reworked Drawer 2026-11-09 21:41:42 -03:00
Vinícius Lourenço
6cfc70675d refactor(ui): port dialogs and confirmations to the reworked Dialog and AlertDialog 2026-11-09 21:41:41 -03:00
Vinícius Lourenço
3739b74b94 refactor(ui): port breadcrumb, pagination, avatar, kbd and skeleton to the reworked components 2026-11-09 21:41:39 -03:00
Vinícius Lourenço
bb353968c3 feat(popover): bring back the handle-based popover now that the library ships it 2026-11-09 21:41:38 -03:00
Vinícius Lourenço
82402a25da test(panel-editor): pick multi-select options through the combobox search row 2026-11-09 21:41:37 -03:00
Vinícius Lourenço
6a2714dccb test(tanstack-table): pick the page size through the combobox search row 2026-11-09 21:41:36 -03:00
Vinícius Lourenço
ed7abc5ee1 test: use the 0.3.0 input and combobox test ids in tests added on main 2026-11-09 21:41:36 -03:00
Vinícius Lourenço
ee912dea34 fix: drop styles and story helpers the old merges brought back 2026-11-09 21:41:36 -03:00
Vinícius Lourenço
2a5e8c1b4d fix: adopt @signozhq/ui 0.3.0 in the alert rules list and saved views tests 2026-11-09 21:41:36 -03:00
Vinícius Lourenço
702ed3e194 fix(popover): use the @signozhq/ui 0.3.0 popover API 2026-11-09 21:41:35 -03:00
Vinícius Lourenço
3badeac4f3 fix: adopt reworked @signozhq/ui in code added on main 2026-11-09 21:41:35 -03:00
Manika Malhotra
fee26ebbbe fix(popover): drop nativeButton from areas where handle is passed 2026-11-09 21:41:32 -03:00
Manika Malhotra
0c41ae06e0 fix: tooltip mock and simplify variables popover 2026-11-09 21:41:32 -03:00
Manika Malhotra
465212af07 feat(popover): integrate popover component 2026-11-09 21:41:32 -03:00
Manika Malhotra
7d5db88ded fix: cleanup conflicting css 2026-11-09 21:41:31 -03:00
Manika Malhotra
90bbf9bce6 fix: remove sonner shim and use toast 2026-11-09 21:41:29 -03:00
Manika Malhotra
ce8f741303 chore: bump packages, integrate alert-strip and resizable 2026-11-09 21:41:26 -03:00
Manika Malhotra
00e7cb475f feat(frontend): adopt reworked @signozhq/ui components across remaining areas 2026-11-09 21:41:26 -03:00
Vinícius Lourenço
b32e6689ef test(stories): add K8s drawer depth and pods log details stories 2026-11-09 21:39:13 -03:00
Manika Malhotra
5c067d75fc test(stories): cover the states the component migration changes 2026-11-09 21:39:13 -03:00
Srikanth Chekuri
4b1b3092d5 fix(querier): skip the cache for logs and traces queries with a limited group by (#13472)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- A logs or traces time series query with a group by and a limit ranks
its groups over the statement window. On a partial cache hit the gap
statement ranked over the gap, so the response was a union of the
winners of each piece, each with points for part of the window. A
narrower window served from a wider cached entry got the wider window's
winners.
- No piece of a window can stand in for the window, so such a query is
no longer served from the cache. Its fingerprint is empty, the same way
an AI builder query opts out today. Metrics are not affected, their
limit is applied after the full series set is read.
- Integration tests: a top-2 over a window whose first half is cached
must return the window's two winners with points for the whole window,
and a top-1 over the second half of a cached window must return that
half's winner. Both match the request with noCache.

#### Issues closed by this PR

Closes SigNoz/nerve-pod#627

#### Additional Information

- The first commit adds the tests alone. CI ran them against main and
both failed. The second commit is the fix.
- This is the stop gap. Caching these queries exactly needs the ranking
summary of each piece, top-K totals with a threshold, so the merge can
rank with bounds and fall back when it cannot prove the top-N. That is a
design item, not a cache patch.
2026-11-09 20:33:56 +00:00
Srikanth Chekuri
d6017f88fd fix(querier): do not shift the gap of a timeShift query twice (#13470)
#### Description

- A timeShift query's window is moved into the shifted clock when the
query is built. On a partial cache hit the missing range comes from that
window, and the ranged gap query shifted it again, so the gap was read
one shift too far back, and the wrong points were served and written
back to the cache.
- The ranged query now takes the range as given. The spec copy already
carries the shift, so the gap statement reads the right data and the
postprocess shift moves it forward once.
- Integration test: a timeShift(3600) logs query with its first 30
minutes cached must return the same points for the next 20 minutes as
the request with noCache.

#### Issues closed by this PR

Closes SigNoz/nerve-pod#625

#### Additional Information

- The first commit adds the test alone. CI ran it against main and it
failed with only that test failing in the suite. The second commit is
the fix.
2026-11-09 20:33:48 +00:00
Srikanth Chekuri
9d28014664 feat(querier): apply the having of a formula (#13452)
#### Description

The querier computes a formula in Go after its queries return, so a
formula having cannot run as SQL HAVING. It was accepted and ignored.

The having now filters the formula values in Go. It uses the having
grammar, validation, and messages of a builder query, with the formula
name and __result as the only references. A time series keeps the
points that match and drops a series with no points left. A scalar
result keeps the rows that match. The having runs before functions,
order, and limit, as HAVING does in SQL. A bad having fails the
request before any query runs.

#### Issues closed by this PR

Closes SigNoz/nerve-pod#612

#### Additional Information

Assisted-by: Claude Opus 5.5
2026-11-09 20:33:22 +00:00
Srikanth Chekuri
1997901102 fix(querier): validate formula limits, raw order keys, and request types (#13426)
#### Description

- A negative formula `limit` now returns 400, as for builder queries.
Before, validation accepted it, and the series limit treated it as no
limit.
- An empty order key on a `raw`, `raw_stream`, or `trace` request now
returns 400. Before, validation accepted it. Aggregation requests keep
their own order key check, which lists the valid keys.
- A missing `requestType` returned `invalid request type: {}` and
suggested `timeseries`, which the decoder rejects. It now returns
`requestType is required`, with the valid request types as suggestions.
The decoder error for an unknown value lists the same set.
- Validation now accepts exactly what `RequestType.Enum` lists. The
spec, the decoder error, and validation read one list, so they cannot
drift apart.

#### Issues closed by this PR

Closes SigNoz/nerve-pod#609

#### Additional Information

- Existing alerts should not be affected. Stored rules load without
validation, and the querier validates only in the `query_range` handler.
The UI does not send a negative formula limit or an empty order key.
- Part of https://github.com/SigNoz/nerve-pod/issues/428
2026-11-09 20:33:14 +00:00
Srikanth Chekuri
84268d00c8 docs(querier): add query_range examples for every builder function (#13475)
#### Description

- The query_range examples cover signals and request types but none of
the functions a builder query can carry, so a client has no place to
learn the argument shapes.
- Adds one example per function family: timeShift, fillZero, the four
threshold functions, runningDiff, ewma, median, cumulativeSum, log,
anomaly. Each description says what the function does to the series,
which arguments it takes, their units and their defaults.
- A test unmarshals every example into a `QueryRangeRequest` and
validates it, so an example cannot drift from the request type.
- The OpenAPI spec is regenerated in its own commit.
2026-11-09 20:32:41 +00:00
Ashwin Bhatkal
628e989bc0 fix(alerts): refresh the v3 rules list after rule changes (#13442)
Some checks failed
build-staging / js-build (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
cacheci / tests (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
#### Description

- Saving a rule, and cloning or renaming one from its details page, now
refreshes the v3 rules list instead of v2. The list page (#13322) and
`useListAllRules` (#13440) both read v3, so nothing reads the v2 list
anymore.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/718
2026-11-09 13:53:16 +00:00
Ashwin Bhatkal
74d272a945 fix(ai-assistant): read alert rules from the v3 list API (#13441)
#### Description

- The AI assistant's Alerts context picker now loads rules with one
`/api/v3/rules` request (`limit: 200`, the most the API returns per
call) instead of the deprecated `GET /api/v2/rules`. The Dashboards tab
next to it already loads one page (`limit: 1000`) the same way.
- Alert context chips read their names from the same cache entry.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/717
2026-11-09 13:20:39 +00:00
Ashwin Bhatkal
f8421e81e9 fix(planned-downtime): read alert rules from the v3 list API (#13440)
#### Description

- The planned downtime rule picker now reads `/api/v3/rules` through a
new `useListAllRules` hook instead of the deprecated `GET
/api/v2/rules`.
- The picker lists every rule, and v3 returns at most 200 per call, so
the hook fetches the remaining pages in parallel, like
`useChannelOptions` does for channels. Orgs with 200 rules or fewer make
one request.
- The planned downtime story now mocks `/api/v3/rules`.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/713
2026-11-09 12:45:49 +00:00
Ashwin Bhatkal
e938a10ad5 fix(home): read alert rules from the v3 list API (#13439)
#### Description

- `GET /api/v2/rules` is deprecated in favor of the paginated
`/api/v3/rules`, and #13158 removes it. This moves the home page alerts
widget to v3 ahead of that.
- The widget still shows firing rules first, then the most recently
updated others, up to 5. It asks v3 for the firing rules first and
fetches the latest rules only when fewer than 5 are firing, instead of
loading every rule.
- Clicking a rule now opens the overview with only `ruleId`, like the
alert list, triggered alerts and Metrics Explorer. The overview loads
the rule and builds its query from it, so the `compositeQuery` the old
link carried is not needed.
- The home page story now mocks `/api/v3/rules`.

#### Issues closed by this PR

Closes https://github.com/SigNoz/pulse-pod/issues/712
2026-11-09 11:52:40 +00:00
Ashwin Bhatkal
93cc1957d4 chore: make pulse-frontend the code owner of the list filter, views and alert rules folders (#13476)
#### Description

- `@SigNoz/pulse-frontend` now owns `components/ListFilter`,
`components/ListViews` and `hooks/listViews`. Only the dashboards list
and the alert rules list use them.
- It also owns `container/PlannedDowntime`, `hooks/alerts` and the home
page alert rules widget (`container/Home/AlertRules`).
- Until now these fell to the `/frontend/` catch-all, so changes to them
also needed `@SigNoz/frontend-maintainers`.
2026-11-09 11:26:04 +00:00
1281 changed files with 20936 additions and 14458 deletions

9
.github/CODEOWNERS vendored
View File

@@ -209,6 +209,10 @@ go.mod @therealpandey
/frontend/src/types/api/alerts/ @SigNoz/pulse-frontend
/frontend/src/providers/Alert.tsx @SigNoz/pulse-frontend
/frontend/src/constants/alerts.ts @SigNoz/pulse-frontend
/frontend/src/container/PlannedDowntime/ @SigNoz/pulse-frontend
/frontend/src/hooks/alerts/ @SigNoz/pulse-frontend
/frontend/src/container/Home/AlertRules/ @SigNoz/pulse-frontend
/frontend/src/container/Home/__tests__/AlertRules.test.tsx @SigNoz/pulse-frontend
## Notification Channels
/frontend/src/pages/ChannelsEdit/ @SigNoz/pulse-frontend
@@ -217,6 +221,11 @@ go.mod @therealpandey
/frontend/src/container/FormAlertChannels/ @SigNoz/pulse-frontend
/frontend/src/hooks/notificationChannels/ @SigNoz/pulse-frontend
## List filter + views (dashboards and alerts lists)
/frontend/src/components/ListFilter/ @SigNoz/pulse-frontend
/frontend/src/components/ListViews/ @SigNoz/pulse-frontend
/frontend/src/hooks/listViews/ @SigNoz/pulse-frontend
## OpenAPI Schema - Generated
/frontend/src/api/generated/services/ @therealpandey @vikrantgupta25 @srikanthccv
/docs/api/openapi.yml @therealpandey @vikrantgupta25 @srikanthccv

View File

@@ -10231,8 +10231,10 @@ components:
having:
allOf:
- $ref: '#/components/schemas/Querybuildertypesv5Having'
description: Accepted but currently ignored; the computed values are returned
unfiltered.
description: A filter on the formula values, referencing the formula by
its name or as __result, for example F1 > 0. A time series keeps only
the points that match and drops a series with none left; a scalar result
keeps only the rows that match.
legend:
description: The display name template for each series; {{field}} placeholders
are replaced by label values.
@@ -32729,6 +32731,305 @@ paths:
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_anomaly_request_rate:
description: 'anomaly is the one function with named arguments. seasonality
says which past periods predict the current value: hourly, daily
or weekly, daily when absent. z_score_threshold is how many standard
deviations from the prediction count as an anomaly. It has no default:
a request without it gets bounds of zero width, so send it. 3 is
the value the UI sends. The response carries, next to the values,
a predicted series, an upper and a lower bound series and an anomaly
score series. The scoring runs in the enterprise build; the community
build returns the series unchanged.'
summary: 'anomaly: request rate scored against its daily pattern'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.count
spaceAggregation: sum
timeAggregation: rate
functions:
- args:
- name: seasonality
value: daily
- name: z_score_threshold
value: 3
name: anomaly
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 300s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_cumulativesum_errors_so_far:
description: cumulativeSum replaces each point with the sum of every
point up to and including it, so a per-step count becomes a running
total whose first point is the first count. It takes no arguments.
The total resets with the window, so a longer time range gives a
larger final value; it is a view of the window, not a counter.
summary: 'cumulativeSum: 5xx responses since the start of the window'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: server_errors
expression: count()
filter:
expression: http.response.status_code >= 500
functions:
- name: cumulativeSum
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: traces
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_cutoffmin_hide_idle_hosts:
description: The four threshold functions take one number. cutOffMin
and cutOffMax remove the points below or above the threshold, so
an idle host disappears from the chart instead of drawing a line
along the bottom. clampMin and clampMax keep the points and replace
the value with the threshold, which caps spikes without hiding them.
system.cpu.utilization is a ratio, so 0.05 is 5%.
summary: 'cutOffMin: drop hosts below 5% CPU from the chart'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.cpu.utilization
spaceAggregation: avg
timeAggregation: avg
functions:
- args:
- value: 0.05
name: cutOffMin
groupBy:
- fieldContext: resource
name: host.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_ewma_smoothed_latency:
description: 'ewma3, ewma5 and ewma7 replace each point with an exponentially
weighted average of the point and the points before it. The number
in the name is the window the default weight is derived from: alpha
= 2 / (n + 1), so 0.5, 0.333 and 0.25. One optional argument overrides
alpha; a value close to 1 follows the raw series, a value close
to 0 smooths harder. 0.3 here smooths a little more than ewma5''s
default.'
summary: 'ewma5: smoothed p95 latency'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.bucket
spaceAggregation: p95
functions:
- args:
- value: 0.3
name: ewma5
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_fillzero_sparse_errors:
description: A count over sparse events returns a point only for steps
that had a match, so a chart of failed spans has gaps where nothing
failed. fillZero adds a zero at every missing step. With no arguments
the server fills the request window at the query's step, which is
the common case. The three optional arguments are start, end and
step in milliseconds, for callers that want to fill a different
grid.
summary: 'fillZero: a zero at every step with no failed spans'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: failed_spans
expression: count()
filter:
expression: has_error = true
functions:
- name: fillZero
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: traces
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_log10_network_bytes:
description: log2 and log10 replace each value with its logarithm,
so a loopback device moving kilobytes and an uplink moving gigabytes
fit on one chart. They take no arguments. Zero and negative values
have no logarithm and are not drawn. absolute, also without arguments,
is the usual companion for a signed series before a logarithm.
summary: 'log10: network throughput across devices that differ by
orders of magnitude'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.network.io
spaceAggregation: sum
timeAggregation: rate
filter:
expression: direction = 'transmit'
functions:
- name: log10
groupBy:
- fieldContext: attribute
name: device
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_median_despiked_request_rate:
description: median3, median5 and median7 replace each point with
the median of the surrounding 3, 5 or 7 points. A spike that lasts
one step vanishes, a shift that lasts longer than half the window
stays. The first and last half window, 2 points each for median5,
have no full window and keep their values, so the newest spike stays
visible; a series shorter than the window is returned unchanged.
They take no arguments. Use them when the raw series is noisy and
the chart is for a trend, not for catching the spike.
summary: 'median5: request rate without one-step spikes'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: http.server.duration.count
spaceAggregation: sum
timeAggregation: rate
functions:
- name: median5
groupBy:
- fieldContext: resource
name: service.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_runningdiff_disk_growth:
description: 'runningDiff turns a gauge into the change between consecutive
points, so a filesystem usage gauge becomes bytes written per step.
It takes no arguments and drops the first point of the window, since
that point has nothing to difference against; the server reads one
step before the window to keep the first interval. Functions apply
in order: clampMin with 0 runs on the differences and replaces the
negative value a cleanup or a volume reset produces with zero.'
summary: 'runningDiff then clampMin: bytes written to disk per step'
value:
compositeQuery:
queries:
- spec:
aggregations:
- metricName: system.filesystem.usage
spaceAggregation: sum
timeAggregation: latest
filter:
expression: state = 'used'
functions:
- name: runningDiff
- args:
- value: 0
name: clampMin
groupBy:
- fieldContext: resource
name: host.name
name: A
signal: metrics
stepInterval: 60s
type: builder_query
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
function_timeshift_week_over_week:
description: timeShift moves every point of its query forward by the
argument, in seconds, so a series read from the past lands on the
current timestamps. B here reads the hour exactly seven days (604800
s) before the requested window and comes back on this week's timestamps,
so it overlays A on a chart and the formula can subtract it point
by point. The argument is positive; it says how far back to read.
summary: 'timeShift: this week''s error logs next to last week''s'
value:
compositeQuery:
queries:
- spec:
aggregations:
- alias: errors
expression: count()
filter:
expression: severity_text = 'ERROR'
name: A
signal: logs
stepInterval: 60s
type: builder_query
- spec:
aggregations:
- alias: errors
expression: count()
filter:
expression: severity_text = 'ERROR'
functions:
- args:
- value: 604800
name: timeShift
name: B
signal: logs
stepInterval: 60s
type: builder_query
- spec:
expression: A - B
name: F1
type: builder_formula
end: 1640998800000
requestType: time_series
schemaVersion: v1
start: 1640995200000
logs_raw:
summary: 'Raw: fetch raw log records'
value:

View File

@@ -8,5 +8,5 @@
.title {
color: var(--l1-foreground);
font-size: var(--font-size-lg);
font-size: var(--label-large-400-font-size);
}

View File

@@ -497,7 +497,11 @@
// Requires final else in if-else-if chains (was disabled)
"signoz/no-conditional-text-nodes-with-siblings": "warn",
// Vendored from eslint-plugin-react-google-translate
"signoz/no-return-text-nodes": "warn"
"signoz/no-return-text-nodes": "warn",
"signoz/no-text-css": "warn",
// Font and text CSS in style props, CSSProperties objects and styled-components; text style goes through Typography props
"signoz/require-disable-reason": "error"
// A disable of signoz/no-text-css must say why Typography cannot render the text
},
"ignorePatterns": [
"src/parser/*.ts",

View File

@@ -45,8 +45,8 @@ const mockAliases = [
},
{
// jest: not replaced, a test asserts on the toast it fires.
find: /^@signozhq\/ui\/sonner$/,
replacement: `${srcPath}/storybook/mocks/sonner.mock.tsx`,
find: /^@signozhq\/ui\/toast$/,
replacement: `${srcPath}/storybook/mocks/toast.mock.tsx`,
},
];

View File

@@ -1,8 +1,6 @@
<!--
`index.html` links Inter from Google Fonts here. The link is parsed before the
msw worker has started, so that one request escapes the iframe while every
other font request the app makes is answered with an empty stylesheet; the
local faces stand in for both.
Every Google Fonts request the app makes is answered with an empty
stylesheet; the local faces stand in for them.
-->
<link rel="stylesheet" href="storybook-fonts.css" />

View File

@@ -1,22 +1,11 @@
/*
* The five families the app pulls from Google Fonts, served from the files in
* The four families the app pulls from Google Fonts, served from the files in
* `public/fonts` instead. `msw/appShellHandlers.ts` answers the CDN with an
* empty stylesheet so no request leaves the iframe, which without this left
* every story on a fallback for the four families `src/styles.scss` imports at
* runtime, and on whatever the Inter link in `index.html` happened to fetch
* before the worker had started.
* runtime. Inter comes from `src/styles.scss` itself.
*/
@font-face {
font-family: 'Inter';
/* The comma is part of the filename and the dev server serves it raw, so it
stays literal inside the quoted url rather than percent-encoded. */
src: url('fonts/Inter-VariableFont_opsz,wght.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Work Sans';
src: url('fonts/WorkSans-VariableFont_wght.ttf') format('truetype');

View File

@@ -14,9 +14,23 @@ module.exports = {
path.join(__dirname, 'stylelint-rules/css-modules/class-name-pattern.js'),
],
customSyntax: 'postcss-scss',
// Disables of property-disallowed-list must say why after "--"
reportDescriptionlessDisables: [
false,
{ except: ['property-disallowed-list'], severity: 'error' },
],
rules: {
// Applies to all SCSS files
'local/no-unsupported-asset-url': true,
// Text style goes through @signozhq/ui/typography props
'property-disallowed-list': [
['/^font/', '/^text-/', 'line-height', 'letter-spacing'],
{
severity: 'warning',
message: (property) =>
`Do not set "${property}" in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after "--" saying why Typography cannot.`,
},
],
},
overrides: [
{

View File

@@ -191,34 +191,30 @@ You can read the ./node_modules/@signozhq/design-tokens/dist/style.css to find c
### Typography
Text styles are tokens named `--{variant}-{size}-{weight}-{property}`:
- Variant: `label` (line height 1), `paragraph` (line height 18, 20 or 26px), `uppercase` (11 and 13px) or `code` (SF Mono, 11 and 13px).
- Size: `small` (11px), `base` (13px), `medium` (16px) or `large` (18px). Paragraph has no `large`; uppercase and code have only `small` and `base`.
- Weight: `400`, `500` or `600`.
- Property: `font-family`, `font-size`, `font-weight`, `line-height`, `letter-spacing` or `text-transform`.
```scss
// Font sizes (preferred)
.title {
font-size: var(--periscope-font-size-large); // 18px
font-size: var(--periscope-font-size-medium); // 16px
font-size: var(--periscope-font-size-base); // 13px
font-size: var(--periscope-font-size-small); // 11px
font-family: var(--label-large-500-font-family);
font-size: var(--label-large-500-font-size);
font-weight: var(--label-large-500-font-weight);
line-height: var(--label-large-500-line-height);
letter-spacing: var(--label-large-500-letter-spacing);
}
// Alternative scale (rem-based)
.heading {
font-size: var(--font-size-xl); // 1.25rem
font-size: var(--font-size-lg); // 1.125rem
font-size: var(--font-size-base); // 1rem
font-size: var(--font-size-sm); // 0.875rem
.hint {
font-size: var(--paragraph-small-400-font-size); // 11px
line-height: var(--paragraph-small-400-line-height); // 18px
}
// Font weights
.bold {
font-weight: var(--font-weight-semibold); // 600
font-weight: var(--font-weight-medium); // 500
font-weight: var(--font-weight-normal); // 400
}
// Line heights
.text {
line-height: var(--line-height-20); // 20px
line-height: var(--line-height-24); // 24px
.id {
font-family: var(--code-base-400-font-family);
font-size: var(--code-base-400-font-size); // 13px
}
```
@@ -312,7 +308,7 @@ Each component exposes `--<component>-<property>` variables:
--button-background: var(--success-background);
--button-border-radius: var(--radius-2);
--button-padding: var(--spacing-4) var(--spacing-8);
--button-font-size: var(--periscope-font-size-base);
--button-font-size: var(--label-base-400-font-size);
}
// Override Input
@@ -342,7 +338,7 @@ Components use data attributes for variants/states. Target them for state-specif
// Target size
.wrapper :global([data-size="sm"]) {
--button-font-size: var(--periscope-font-size-small);
--button-font-size: var(--label-small-400-font-size);
}
// Target color

View File

@@ -0,0 +1,191 @@
# Popover migration analysis
The new `@signozhq/ui` Popover is click-only. This note maps the current app onto that API so a later migration does not break open state, focus, or the global `.ant-popover` overrides. It does not change any call site.
The component this describes lives in the design-system repo (`packages/ui/src/popover`). Storybook stories for it are `Playground`, `SaveViewForm`, `ControlledOpen`, `InsideDialog`, `MaxHeightScrolling`, and `AnchoredTypeahead`.
## The API to migrate onto
```tsx
<Popover
nativeButton
side="bottom"
align="start"
trigger={<button type="button">Open</button>}
open={open}
onOpenChange={setOpen}
>
{content}
</Popover>
```
| Today | New Popover |
| --- | --- |
| antd `children` (the trigger) | `trigger` (required) |
| antd `content`, or Radix `<PopoverContent>` | `children` |
| antd `trigger="click"` | the only mode; there is no `openOn` |
| antd `trigger="hover"` | not this component; a Tooltip, or a click popover when the body is interactive |
| `placement="bottomRight"` | `side="bottom"` `align="end"` |
| `placement="bottomLeft"` | `side="bottom"` `align="start"` |
| `open` / `onOpenChange` | kept; `onOpenChange` receives Base UI's reason and `details.cancel()` |
| `defaultOpen` | removed; nothing in the app starts open |
| `overlayClassName`, `overlayStyle`, `overlayInnerStyle`, `className` | rejected on the shell; size is `width` / `maxWidth` / `maxHeight` |
| `getPopupContainer` | `container` |
| `destroyTooltipOnHide` | the only behavior; the popup unmounts on close |
| `arrow` / `showArrow` | rejected; the shell has no arrow |
| Radix `PopoverAnchor` | `anchor`, and the trigger is still required |
| Radix `withPortal={false}` | `container` (inside a Dialog the popup already portals into the panel) |
| Radix `onInteractOutside` | `onOpenChange` + `details.cancel()` when `reason === 'outside-press'` |
| `sideOffset` / `alignOffset` | not props; fixed at 4px and 0 |
`nativeButton` is required. `true` when `trigger` renders a `<button>` (including `Button`). `false` for anything else.
A node that renders nothing (`null`, `false`, `''`) renders the trigger alone, with no listeners and no portal.
## What is in the app today
Counted from `frontend/src` on this branch:
- **29 files** import `Popover` from `antd`.
- **7 files** import `Popover` from `@signozhq/ui` (the Radix wrapper this rework replaces). Those seven break as soon as the package ships, because `children` stops being the trigger.
- **18 SCSS files** override `.ant-popover*`.
antd `Dropdown` is already on `@signozhq/ui/dropdown`. antd `Tooltip` (on the order of 100 importers) is a separate migration.
### antd files
`src/components/CustomTimePicker/CustomTimePicker.tsx`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.tsx`
`src/components/ErrorPopover/ErrorPopover.tsx`
`src/components/ExplorerCard/ExplorerCard.tsx`
`src/components/HeaderRightSection/HeaderRightSection.tsx`
`src/components/Logs/AddToQueryHOC.tsx`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.tsx`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregationSelect.tsx`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.tsx`
`src/components/QueryBuilderV2/QueryV2/TraceOperator/TraceOperatorEditor.tsx`
`src/components/TableRenderer/LabelColumn.tsx`
`src/components/WarningPopover/WarningPopover.tsx`
`src/container/AlertHistory/AlertPopover/AlertPopover.tsx`
`src/container/CreateAlertV2/EvaluationSettings/EvaluationSettings.tsx`
`src/container/Home/Home.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributes.tsx`
`src/container/MetricsExplorer/MetricDetails/AllAttributesValue.tsx`
`src/container/MetricsExplorer/Summary/MetricNameSearch.tsx`
`src/container/OptionsMenu/index.tsx`
`src/container/TopNav/AutoRefreshV2/index.tsx`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.tsx`
`src/pages/MetricsApplication/ApDex/ApDexApplication.tsx`
`src/pages/PublicDashboard/PublicDashboardView/PublicAutoRefresh/PublicAutoRefresh.tsx`
`src/pages/TraceDetailsV3/SpanDetailsPanel/Events/components/AttributeWithExpandablePopover.tsx`
`src/pages/TracesFunnelDetails/components/FunnelConfiguration/FunnelStepPopover.tsx`
`src/pages/TracesFunnels/components/FunnelsList/FunnelItemPopover.tsx`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.tsx`
`src/periscope/components/ContextMenu/index.tsx`
`src/periscope/components/SeeMore/SeeMore.tsx`
### Existing `@signozhq/ui` popovers
`src/components/ExportMenu/ExportMenu.tsx`
`src/components/MarkdownEditor/MarkdownHelp.tsx`
`src/components/TanStackTableView/TanStackHeaderRow.tsx`
`src/container/AIAssistant/components/ChatInput/ChatInput.tsx`
`src/container/ListAlertRules/components/ColumnSelector.tsx`
`src/pages/DashboardPage/DashboardContainer/PanelEditor/ConfigPane/sections/ContextLinksSection/VariablesPopover.tsx`
`src/pages/DashboardsListPage/components/ViewsRail/ViewNamePopover.tsx`
### SCSS that overrides `.ant-popover`
`src/components/DownloadOptionsMenu/DownloadOptionsMenu.styles.scss`
`src/components/LogsFormatOptionsMenu/LogsFormatOptionsMenu.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QueryAggregation/QueryAggregation.styles.scss`
`src/components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch.styles.scss`
`src/container/AlertHistory/AlertPopover/AlertPopover.styles.scss`
`src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
`src/container/CreateAlertV2/EvaluationSettings/styles.scss`
`src/container/Home/Home.styles.scss`
`src/container/MetricsExplorer/MetricDetails/MetricDetails.styles.scss`
`src/container/MetricsExplorer/Summary/Summary.styles.scss`
`src/container/TopNav/AutoRefreshV2/AutoRefreshV2.styles.scss`
`src/container/TopNav/DateTimeSelectionV2/DateTimeSelectionV2.styles.scss`
`src/container/WidgetCard/WidgetCard.styles.scss`
`src/pages/DashboardsListPage/components/ListHeader/ListHeader.module.scss`
`src/pages/TracesFunnels/TracesFunnels.styles.scss`
`src/pages/TracesFunnels/components/FunnelsList/FunnelsList.styles.scss`
`src/pages/TracesFunnels/components/SearchBar/SearchBar.styles.scss`
`src/periscope/components/ContextMenu/styles.scss`
Three of these are unscoped and restyle every antd popover on the page, not just their own:
- `src/container/MetricsExplorer/Summary/Summary.styles.scss`
- `src/container/CreateAlertV2/EvaluationSettings/styles.scss`
- `src/container/AlertHistory/Statistics/TopContributorsCard/TopContributorsCard.styles.scss`
Scope or delete those three before moving any nearby popover, or a migrated popup and a leftover antd popup will share rules that only one of them understands.
Most of the other files repeat one recipe: padding 0, 4px radius, a 1px border, a translucent background, the arrow hidden. The new shell already has zero padding, `--radius-2`, and no arrow. Per-site size (fixed widths around 187–500px, max-width 600px, max-height 254px) becomes `width` / `maxWidth` / `maxHeight`. Do not re-apply the recipe as a `className`; the component rejects it.
## What should not become this Popover
**Hover descriptions.** `AddToQueryHOC`, `LabelColumn`, the three Query Builder validation popovers, `SeeMore`, and `AttributeWithExpandablePopover` open on hover. Those are Tooltips. `AttributeWithExpandablePopover` shows a value the user copies; if Tooltip cannot hold that, make it a click popover instead of keeping hover.
**WarningPopover** (imported from about 10 files) is hover today and contains docs links. A tooltip cannot host those links. Either the links move out and the summary becomes a Tooltip, or the wrapper becomes a click Popover with `maxWidth` around 600.
**Menus.** Funnel item and funnel step action lists, the sort lists in `ListHeader` and Traces Funnels `SearchBar`, `AutoRefreshV2` / `PublicAutoRefresh` interval lists, and `AlertPopover`'s two links are rows that close when one is picked. Those are Dropdowns. `AllAttributes` per-row actions are the same shape.
**ContextMenu** (`src/periscope/components/ContextMenu/index.tsx`, imported across the dashboard drilldown) opens at chart-click coordinates. There is no trigger element. The new Popover requires `trigger`, so this stays a context menu. Do not fold the phantom span, `position: fixed` overlay, `zIndex: 10000`, or `calculatePopoverPosition.ts` into `anchor`. `anchor` is for a popover that already has a trigger and should position against something else (the typeahead input row).
**Inspect charts.** `MetricsExplorer/Inspect` `GraphPopover` and `HoverPopover` are absolutely positioned cards, not antd Popover. Leave them until there is a context-menu or anchor design that matches them.
**PanelStatusPopover** is a Tooltip despite the name.
## What should become this Popover
Click panels whose content is a form, a picker, or a composed layout:
- `CustomTimePicker` — the most visible one. Controlled `open`, `container` for `getPopupContainer`, and it already unmounts on hide. It opens from both input focus and click; that logic stays in the caller, with `onOpenChange`.
- `EvaluationSettings` / `EvaluationWindowPopover` — controlled, wide (`width` / `maxWidth`), and sitting on a global `.ant-popover` override. Migrate the override in the same change.
- `HeaderRightSection` — three controlled forms (feedback, announcements, share).
- `ExplorerCard` save-view form and `ViewNamePopover` — close only after the save succeeds, so `open` stays controlled. `ViewNamePopover` is already on the library and will not compile against the new props.
- `ApDex` threshold form.
- `Home` welcome checklist — drop the transparent-shell override; the shell is the component's.
- `OptionsMenu`, `DownloadOptionsMenu`, `LogsFormatOptionsMenu` — click panels. `LogsFormatOptionsMenu` nests a submenu; the inner popup must be portalled, and the outer `onOpenChange` must `cancel()` an outside press that lands in the inner popup.
- `MetricNameSearch` — controlled search panel. Scope the global padding override in `Summary.styles.scss` in the same change.
- `AllAttributesValue` — one popover open at a time in a list. That is controlled `open` keyed by row, which the API still allows.
- `ChatInput` context picker — already on the library; `side="top"` `align="end"` plus `width`.
- `VariablesPopover` — already on the library. Keep `anchor` on the input row, `initialFocus={false}` so the caret stays, and `details.cancel()` when the outside press is the anchor. `withPortal={false}` becomes the default dialog `container`, not an unportalled popup.
- `ColumnSelector`, `MarkdownHelp`, `TanStackHeaderRow`, `ExportMenu` — Radix `asChild` trigger plus content. The trigger moves to the `trigger` prop, content moves to `children`, and `className` on the shell becomes `width` / `maxWidth`.
`ErrorPopover` is a thin wrapper. Re-point it at the new props and its consumer follows.
## Order
Before any change: screenshot every current popover, in both themes, so each call site's pattern is on record and a component swap is reviewable against it. Flag the misusages to design as they turn up — links inside hover surfaces above all. Swapping the component during migration is expected where the old call site was on the wrong one; antd call sites stay untouched until their turn.
1. Scope the three global `.ant-popover` files so later migrations cannot restyle leftover antd popovers.
2. Move the seven existing `@signozhq/ui` call sites first. They break at the package bump; the 29 antd files do not, until each one is switched.
3. Migrate shared wrappers that are actually popovers (`ErrorPopover`, then `WarningPopover` once the links decision is made). Leave `ContextMenu` on antd until a context menu exists.
4. Migrate click forms (`CustomTimePicker`, evaluation window, header menus, save-view, ApDex).
5. Send hover descriptions to Tooltip and row menus to Dropdown, instead of forcing them through Popover.
6. Delete the matching `.ant-popover` rules in the same change as each call site, so a half-migrated page is not styled twice.
## Risks
- **Controlled `open` is load-bearing.** Save forms close from inside the popup only when the mutation succeeds. A list keeps one popover open at a time. Dropping `open` and driving everything from `onOpenChange` alone cannot express either. Keep the controlled prop at those sites.
- **Focus moves in.** Click popovers focus the first tabbable child. Typeaheads (`VariablesPopover`, and any filter that must keep the caret) need `initialFocus={false}`.
- **Outside press and anchors.** A press on the anchor looks outside. Call `details.cancel()` or the typeahead closes when the user clicks the input it is anchored to.
- **Dialogs.** Inside a Dialog or Drawer the popup portals into the panel so the focus trap can reach it. Do not set `container={document.body}` there.
- **Z-index.** The shell starts at `--popover-z-index` (50) and lifts above the layer its trigger sits in. A Tooltip inside a row must still paint above the popup. The app ladder today is tooltip 1070, dropdown 1050, popover 1030, modal 1000. Check a truncated row tooltip inside a popover after the first real migration.
- **Scroll locking.** Chart popovers measure the wrong box when a modal has locked scroll. This popover does not lock scroll (`modal` is rejected). Re-check Infrastructure Monitoring charts if a surrounding modal is open.
- **`nativeButton` mismatches.** Base UI logs an error when the flag does not match the element. Every non-button trigger (a badge, a custom row, an input) needs `nativeButton={false}`.
## `--popover-foreground` audit
The shell reads `--popover-background`, `--popover-foreground`, `--popover-border`, and `--popover-shadow`. Those semantic values belong to the token package, not to call sites.
Two legacy shadcn variables predate those hooks:
- `--popover-foreground` is the foreground hook the shell uses. It should stay pointed at the same foreground as the rest of the light and dark themes (`--l1-foreground` today).
- `--popover` (no suffix) is the old background. `--calendar-select-background` still reads it. Do not repoint `--popover` at `--popover-background` or at `surface-3` until the calendar token is audited, or date selection changes color with the popover shell.
Call sites that pass antd's `color` (a gradient string on `AlertPopover`, a raw token on `SeeMore`) do not map to a prop. The shell color comes from the tokens. Content that needs its own background paints it inside `children`.

View File

@@ -14,11 +14,6 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link rel="preconnect" href="https://cdn.vercel.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap"
rel="stylesheet"
/>
<title data-react-helmet="true">
Open source Observability platform | SigNoz
</title>

View File

@@ -53,9 +53,9 @@
"@monaco-editor/react": "^4.7.0",
"@sentry/react": "10.57.0",
"@sentry/vite-plugin": "5.3.0",
"@signozhq/design-tokens": "2.2.1",
"@signozhq/design-tokens": "2.6.0",
"@signozhq/icons": "0.4.0",
"@signozhq/ui": "0.2.0",
"@signozhq/ui": "0.3.0",
"@tanstack/react-table": "8.21.3",
"@tanstack/react-virtual": "3.13.22",
"@uiw/codemirror-theme-copilot": "4.23.11",

View File

@@ -0,0 +1,170 @@
import { ruleTester } from './rule-tester.mjs';
const message = (property) =>
`Do not set '${property}' in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after \`--\` saying why Typography cannot.`;
await ruleTester({
rule: 'no-text-css',
valid: [
{
name: 'layout properties in a style prop',
code: 'export const A = () => <div style={{ width: 10, color: "red" }} />;',
},
{
name: 'font properties in an object that is not a style',
code: 'export const options = { fontSize: 12, font: "12px Inter" };',
},
{
name: 'font properties in a prop that is not a style',
code: 'export const A = () => <Chart options={{ fontSize: 12 }} />;',
},
{
name: 'computed key',
code:
'const k = "fontSize";\nexport const A = () => <div style={{ [k]: 12 }} />;',
},
{
name: 'style identifier from a parameter',
code: 'export const A = ({ s }) => <div style={s} />;',
},
{
name: 'template without a styled tag',
code: 'export const s = html`font-size: 12px;`;',
},
{
name: 'styled template with layout only',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\tdisplay: flex;\n\ttext: none;\n`;",
},
{
name: 'custom property that ends in a banned name',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\t--my-font-size: 12px;\n`;",
},
],
invalid: [
{
name: 'inline style',
code: 'export const A = () => <div style={{ fontSize: 12, width: 1 }} />;',
errors: [{ message: message('fontSize'), line: 1, column: 38 }],
},
{
name: 'string key',
code: "export const A = () => <div style={{ 'line-height': 1 }} />;",
errors: [{ message: message('line-height') }],
},
{
name: 'every banned family',
code:
'export const A = () => (\n\t<div\n\t\tstyle={{\n\t\t\tfont: "x",\n\t\t\tfontWeight: 500,\n\t\t\tfontFamily: "x",\n\t\t\ttextAlign: "center",\n\t\t\ttextTransform: "none",\n\t\t\tlineHeight: 1,\n\t\t\tletterSpacing: 1,\n\t\t}}\n\t/>\n);',
errors: [
{ message: message('font') },
{ message: message('fontWeight') },
{ message: message('fontFamily') },
{ message: message('textAlign') },
{ message: message('textTransform') },
{ message: message('lineHeight') },
{ message: message('letterSpacing') },
],
},
{
name: 'conditional, logical and spread',
code:
'export const A = ({ a, b }) => (\n\t<div style={{ ...(a ? { fontSize: 1 } : {}), ...(b && { lineHeight: 1 }) }} />\n);',
errors: [
{ message: message('fontSize') },
{ message: message('lineHeight') },
],
},
{
name: 'style prop with a Style suffix',
code:
'export const A = () => <Card headStyle={{ fontWeight: 600 }} bodyStyle={{ padding: 0 }} />;',
errors: [{ message: message('fontWeight') }],
},
{
name: 'antd semantic styles',
code:
'export const A = () => <Modal styles={{ body: { fontSize: 12 }, header: { padding: 0 } }} />;',
errors: [{ message: message('fontSize') }],
},
{
name: 'identifier resolved to a local object',
code:
'const s = { fontSize: 12 };\nexport const A = () => (\n\t<>\n\t\t<div style={s} />\n\t\t<span style={s} />\n\t</>\n);',
errors: [{ message: message('fontSize'), line: 1, column: 13 }],
},
{
name: 'useMemo style',
code:
"import { useMemo } from 'react';\nexport const A = () => {\n\tconst s = useMemo(() => ({ textAlign: 'left' }), []);\n\treturn <div style={s} />;\n};",
errors: [{ message: message('textAlign') }],
},
{
name: 'object typed CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const s: CSSProperties = { fontStyle: 'italic' };",
errors: [{ message: message('fontStyle') }],
},
{
name:
'object typed React.CSSProperties, reported once when also used as style',
code:
"import React from 'react';\nconst s: React.CSSProperties = { fontSize: 1 };\nexport const A = () => <div style={s} />;",
errors: [{ message: message('fontSize') }],
},
{
name: 'Record of CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const s: Record<string, CSSProperties> = { a: { lineHeight: 1 } };",
errors: [{ message: message('lineHeight') }],
},
{
name: 'as and satisfies CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const a = { fontSize: 1 } as CSSProperties;\nexport const b = { fontWeight: 1 } satisfies CSSProperties;",
errors: [
{ message: message('fontSize') },
{ message: message('fontWeight') },
],
},
{
name: 'styled-components template',
code:
"import styled from 'styled-components';\nexport const A = styled.div`\n\tdisplay: flex;\n\tfont-size: 12px;\n\tcolor: ${(p) => p.c};\n\ttext-transform: none;line-height: 1;\n`;",
errors: [
{ message: message('font-size'), line: 4, column: 2 },
{ message: message('text-transform'), line: 6, column: 2 },
{ message: message('line-height'), line: 6, column: 23 },
],
},
{
name: 'function returning CSSProperties',
code:
"import type { CSSProperties } from 'react';\nexport const a = (): CSSProperties => ({ fontSize: 1 });\nexport function b(): CSSProperties {\n\treturn { lineHeight: 1 };\n}",
errors: [
{ message: message('fontSize') },
{ message: message('lineHeight') },
],
},
{
name: 'strings in styled interpolations',
code:
"import styled, { css } from 'styled-components';\nexport const A = styled.div`\n\t${(p) => (p.s ? 'font-size: 11px;' : `line-height: 1;`)}\n\t${(p) => p.b && css`font-weight: 500;`}\n`;",
errors: [
{ message: message('font-size'), line: 3, column: 19 },
{ message: message('line-height'), line: 3, column: 40 },
{ message: message('font-weight'), line: 4, column: 22 },
],
},
{
name: 'styled(Component) and css helper',
code:
"import styled, { css } from 'styled-components';\nexport const A = styled(B)`letter-spacing: 1px;`;\nexport const c = css`\n\tfont-weight: 500;\n`;",
errors: [
{ message: message('letter-spacing'), line: 2, column: 28 },
{ message: message('font-weight'), line: 4, column: 2 },
],
},
],
});

View File

@@ -0,0 +1,49 @@
import { ruleTester } from './rule-tester.mjs';
const message = 'Disabling signoz/no-text-css needs a reason after `--`.';
await ruleTester({
rule: 'require-disable-reason',
valid: [
{
name: 'reason after --',
code:
'// oxlint-disable-next-line signoz/no-text-css -- canvas label\nexport const a = 1;',
},
{
name: 'block comment with a reason',
code:
'/* eslint-disable signoz/no-text-css -- email HTML */\nexport const a = 1;',
},
{
name: 'another rule without a reason',
code: '// oxlint-disable-next-line no-console\nexport const a = 1;',
},
{
name: 'disable of every rule',
code: '// eslint-disable-next-line\nexport const a = 1;',
},
{
name: 'plain comment naming the rule',
code: '// signoz/no-text-css flags font CSS\nexport const a = 1;',
},
],
invalid: [
{
name: 'next-line without a reason',
code: '// oxlint-disable-next-line signoz/no-text-css\nexport const a = 1;',
errors: [{ message, line: 1, column: 1 }],
},
{
name: 'empty reason',
code: '// eslint-disable-line signoz/no-text-css --\nexport const a = 1;',
errors: [{ message }],
},
{
name: 'among other rules',
code:
'/* oxlint-disable no-console, signoz/no-text-css */\nexport const a = 1;',
errors: [{ message }],
},
],
});

View File

@@ -0,0 +1,324 @@
/**
* Rule: no-text-css
*
* Text style (font, line height, letter spacing, case, alignment, overflow)
* belongs to Typography props, not to CSS written in a component.
*
* This rule catches font and text properties in:
* <div style={{ fontSize: 12 }} /> style and *Style props
* <Modal styles={{ body: { fontWeight: 500 } }} />
* const s: CSSProperties = { lineHeight: 1 } objects typed CSSProperties
* ({ ... }) as CSSProperties, satisfies CSSProperties
* (): CSSProperties => ({ ... }) functions returning CSSProperties
* styled.div`font-size: 12px;` styled-components templates,
* ${(p) => p.small && 'font-size: 11px;'} strings in their interpolations
*
* Identifiers passed to a style prop are followed to a local object literal.
* SCSS is covered by the stylelint config.
*/
const MESSAGE = (property) =>
`Do not set '${property}' in CSS. Render the text with @signozhq/ui/typography and use its props, or disable this rule with a reason after \`--\` saying why Typography cannot.`;
const BANNED_JS = /^(font([A-Z]\w*)?|text[A-Z]\w*|lineHeight|letterSpacing)$/;
const BANNED_CSS =
/(^|[\s;{])(font(-[a-z]+)*|text-[a-z-]+|line-height|letter-spacing)\s*:/g;
const STYLE_TAGS = new Set(['css', 'createGlobalStyle', 'keyframes']);
const FUNCTIONS = new Set([
'ArrowFunctionExpression',
'FunctionExpression',
'FunctionDeclaration',
]);
const camelCase = (name) =>
name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
function propertyName(property) {
if (property.computed) {
return null;
}
if (property.key.type === 'Identifier') {
return property.key.name;
}
if (
property.key.type === 'Literal' &&
typeof property.key.value === 'string'
) {
return property.key.value;
}
return null;
}
function isCssPropertiesType(typeNode) {
if (!typeNode || typeNode.type !== 'TSTypeReference') {
return false;
}
const name = typeNode.typeName;
if (name.type === 'Identifier') {
return name.name === 'CSSProperties';
}
return name.type === 'TSQualifiedName' && name.right.name === 'CSSProperties';
}
// Record<K, CSSProperties> and { [key: string]: CSSProperties }
function isCssPropertiesMapType(typeNode) {
if (!typeNode) {
return false;
}
if (
typeNode.type === 'TSTypeReference' &&
typeNode.typeName.type === 'Identifier' &&
typeNode.typeName.name === 'Record'
) {
const params = (typeNode.typeArguments ?? typeNode.typeParameters)?.params;
return isCssPropertiesType(params?.[1]);
}
if (typeNode.type === 'TSTypeLiteral') {
return typeNode.members.some(
(member) =>
member.type === 'TSIndexSignature' &&
isCssPropertiesType(member.typeAnnotation?.typeAnnotation),
);
}
return false;
}
function rootIdentifier(node) {
let current = node;
while (current) {
if (current.type === 'Identifier') {
return current.name;
}
if (current.type === 'MemberExpression') {
current = current.object;
} else if (current.type === 'CallExpression') {
current = current.callee;
} else {
return null;
}
}
return null;
}
function isStyleTag(tag) {
if (tag.type === 'Identifier') {
return tag.name === 'styled' || STYLE_TAGS.has(tag.name);
}
return rootIdentifier(tag) === 'styled';
}
export default {
create(context) {
const reported = new Set();
const followed = new Set();
const reportedCss = new Set();
function report(property, name) {
if (reported.has(property)) {
return;
}
reported.add(property);
context.report({ node: property.key, message: MESSAGE(name) });
}
function resolveIdentifier(identifier) {
const scope = context.sourceCode.getScope(identifier);
let current = scope;
while (current) {
const variable = current.set.get(identifier.name);
if (variable) {
const definition = variable.defs[0];
if (
definition?.node?.type === 'VariableDeclarator' &&
definition.node.init
) {
return definition.node.init;
}
return null;
}
current = current.upper;
}
return null;
}
function checkObject(object) {
for (const property of object.properties) {
if (property.type === 'SpreadElement') {
checkStyle(property.argument);
continue;
}
const name = propertyName(property);
if (name && BANNED_JS.test(camelCase(name))) {
report(property, name);
}
}
}
function checkStyle(expression) {
if (!expression) {
return;
}
switch (expression.type) {
case 'ObjectExpression':
checkObject(expression);
return;
case 'ConditionalExpression':
checkStyle(expression.consequent);
checkStyle(expression.alternate);
return;
case 'LogicalExpression':
checkStyle(expression.left);
checkStyle(expression.right);
return;
case 'TSAsExpression':
case 'TSSatisfiesExpression':
case 'TSNonNullExpression':
case 'ParenthesizedExpression':
checkStyle(expression.expression);
return;
case 'CallExpression':
// useMemo(() => ({ ... }), deps)
if (
expression.callee.type === 'Identifier' &&
expression.callee.name === 'useMemo'
) {
checkReturned(expression.arguments[0]);
}
return;
case 'Identifier': {
const init = resolveIdentifier(expression);
if (!init || followed.has(init)) {
return;
}
followed.add(init);
checkStyle(init);
return;
}
default:
}
}
function checkReturned(fn) {
if (!fn || !FUNCTIONS.has(fn.type)) {
return;
}
if (fn.body.type !== 'BlockStatement') {
checkStyle(fn.body);
return;
}
for (const statement of fn.body.body) {
if (statement.type === 'ReturnStatement') {
checkStyle(statement.argument);
}
}
}
function checkStyleMap(expression) {
if (expression?.type !== 'ObjectExpression') {
return;
}
for (const property of expression.properties) {
if (property.type === 'Property') {
checkStyle(property.value);
}
}
}
// `start` is the index of the first character after the opening quote.
function checkCss(text, start) {
for (const match of text.matchAll(BANNED_CSS)) {
const index = start + match.index + match[1].length;
if (reportedCss.has(index)) {
continue;
}
reportedCss.add(index);
context.report({
loc: {
start: context.sourceCode.getLocFromIndex(index),
end: context.sourceCode.getLocFromIndex(index + match[2].length),
},
message: MESSAGE(match[2]),
});
}
}
// CSS returned from interpolations: ${(p) => p.small && 'font-size: 11px;'}
function checkCssStrings(node) {
if (!node || typeof node.type !== 'string') {
return;
}
if (node.type === 'TemplateLiteral') {
for (const quasi of node.quasis) {
checkCss(quasi.value.raw, quasi.range[0] + 1);
}
} else if (node.type === 'Literal' && typeof node.value === 'string') {
checkCss(node.raw.slice(1, -1), node.range[0] + 1);
return;
}
for (const [key, value] of Object.entries(node)) {
if (key === 'parent') {
continue;
}
if (Array.isArray(value)) {
value.forEach(checkCssStrings);
} else if (value && typeof value === 'object') {
checkCssStrings(value);
}
}
}
function checkFunction(node) {
if (isCssPropertiesType(node.returnType?.typeAnnotation)) {
checkReturned(node);
}
}
return {
JSXAttribute(node) {
if (
node.name.type !== 'JSXIdentifier' ||
node.value?.type !== 'JSXExpressionContainer'
) {
return;
}
const name = node.name.name;
if (name === 'styles') {
checkStyleMap(node.value.expression);
} else if (name === 'style' || name.endsWith('Style')) {
checkStyle(node.value.expression);
}
},
VariableDeclarator(node) {
const type = node.id.typeAnnotation?.typeAnnotation;
if (isCssPropertiesType(type)) {
checkStyle(node.init);
} else if (isCssPropertiesMapType(type)) {
checkStyleMap(node.init);
}
},
TSAsExpression(node) {
if (isCssPropertiesType(node.typeAnnotation)) {
checkStyle(node.expression);
}
},
TSSatisfiesExpression(node) {
if (isCssPropertiesType(node.typeAnnotation)) {
checkStyle(node.expression);
} else if (isCssPropertiesMapType(node.typeAnnotation)) {
checkStyleMap(node.expression);
}
},
TaggedTemplateExpression(node) {
if (!isStyleTag(node.tag)) {
return;
}
checkCssStrings(node.quasi);
},
ArrowFunctionExpression: checkFunction,
FunctionExpression: checkFunction,
FunctionDeclaration: checkFunction,
};
},
};

View File

@@ -0,0 +1,38 @@
/**
* Rule: require-disable-reason
*
* A disable directive that names one of RULES must say why after `--`:
* // oxlint-disable-next-line signoz/no-text-css -- canvas text, not DOM
*
* Directives that disable every rule are not checked.
*/
const RULES = new Set(['signoz/no-text-css']);
const DIRECTIVE = /^\s*(?:eslint|oxlint)-disable(?:-next-line|-line)?\s+(.*)$/s;
export default {
create(context) {
return {
Program() {
for (const comment of context.sourceCode.getAllComments()) {
const match = comment.value.match(DIRECTIVE);
if (!match) {
continue;
}
const [rules, reason] = match[1].split(/\s--\s?/);
const named = rules
.split(',')
.map((rule) => rule.trim())
.filter((rule) => RULES.has(rule));
if (named.length > 0 && !reason?.trim()) {
context.report({
node: comment,
message: `Disabling ${named.join(', ')} needs a reason after \`--\`.`,
});
}
}
},
};
},
};

View File

@@ -17,6 +17,8 @@ import noConditionalTextNodesWithSiblings from './rules/no-conditional-text-node
import noReturnTextNodes from './rules/no-return-text-nodes.mjs';
import noMswInStoryFile from './rules/no-msw-in-story-file.mjs';
import noDirectReactRouterImport from './rules/no-direct-react-router-import.mjs';
import noTextCss from './rules/no-text-css.mjs';
import requireDisableReason from './rules/require-disable-reason.mjs';
export default {
meta: {
@@ -35,5 +37,7 @@ export default {
'no-return-text-nodes': noReturnTextNodes,
'no-msw-in-story-file': noMswInStoryFile,
'no-direct-react-router-import': noDirectReactRouterImport,
'no-text-css': noTextCss,
'require-disable-reason': requireDisableReason,
},
};

450
frontend/pnpm-lock.yaml generated
View File

@@ -74,14 +74,14 @@ importers:
specifier: 5.3.0
version: 5.3.0
'@signozhq/design-tokens':
specifier: 2.2.1
version: 2.2.1
specifier: 2.6.0
version: 2.6.0
'@signozhq/icons':
specifier: 0.4.0
version: 0.4.0
'@signozhq/ui':
specifier: 0.2.0
version: 0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
specifier: 0.3.0
version: 0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
'@tanstack/react-table':
specifier: 8.21.3
version: 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -2909,9 +2909,6 @@ packages:
'@posthog/core@1.6.0':
resolution: {integrity: sha512-Tbh8UACwbb7jFdDC7wwXHtfNzO+4wKh3VbyMHmp2UBe6w1jliJixexTJNfkqdGZm+ht3M10mcKvGGPnoZ2zLBg==}
'@radix-ui/number@1.1.3':
resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==}
'@radix-ui/primitive@1.1.7':
resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
@@ -2928,19 +2925,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-collection@1.1.15':
resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-compose-refs@1.1.5':
resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==}
peerDependencies:
@@ -2972,15 +2956,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-direction@1.1.4':
resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-dismissable-layer@1.1.19':
resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==}
peerDependencies:
@@ -3095,71 +3070,6 @@ packages:
'@types/react-dom':
optional: true
'@radix-ui/react-progress@1.1.16':
resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-radio-group@1.4.7':
resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-roving-focus@1.1.19':
resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-select@2.3.7':
resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slider@1.4.7':
resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-slot@1.3.3':
resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==}
peerDependencies:
@@ -3169,19 +3079,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-tabs@1.1.21':
resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/react-use-callback-ref@1.1.4':
resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==}
peerDependencies:
@@ -3209,15 +3106,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-is-hydrated@0.1.3':
resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-layout-effect@1.1.4':
resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==}
peerDependencies:
@@ -3227,15 +3115,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-use-previous@1.1.4':
resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==}
peerDependencies:
'@types/react': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@radix-ui/react-use-rect@1.1.4':
resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==}
peerDependencies:
@@ -3254,19 +3133,6 @@ packages:
'@types/react':
optional: true
'@radix-ui/react-visually-hidden@1.2.11':
resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==}
peerDependencies:
'@types/react': '*'
'@types/react-dom': '*'
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
peerDependenciesMeta:
'@types/react':
optional: true
'@types/react-dom':
optional: true
'@radix-ui/rect@1.1.3':
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
@@ -3585,14 +3451,14 @@ packages:
'@sideway/pinpoint@2.0.0':
resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
'@signozhq/design-tokens@2.2.1':
resolution: {integrity: sha512-ulf6lhWUP6qxqsyemQySUMfOIKRvefvZ5rBRv+wlhOpPfYPk3uatPmPkKEhAjISpX8f9g1Gg8wgRZxUCysf20Q==}
'@signozhq/design-tokens@2.6.0':
resolution: {integrity: sha512-7+lBxC3N4r1A2M8awhop7mtYMMd5yOg4BKfRAN3hnle4lclZHlxm7b3gdplfNN10JA1mcwRSQvWrjQPmA/euqw==}
'@signozhq/icons@0.4.0':
resolution: {integrity: sha512-QctNvNPlQlsefFdIVmSy/P/7a1IcQ3IryKP0KPMqtoqew3q89FGUmFa5IfEJ7Z3CHhtG1zaXwBKqP72QkLyA3g==}
'@signozhq/ui@0.2.0':
resolution: {integrity: sha512-j20ldiOkR150ON0BCDhlq6fXGoPN++7w6e4t2HtM2XMM4buJO7aHzPzNKvDRZ2UUf/LGPULPAW8w6sg6gUVHyw==}
'@signozhq/ui@0.3.0':
resolution: {integrity: sha512-Khzr3/YC/QACwBi1zfQwt+h88iyuIu9hm5hkcIkuCTteD9P+r13US/NN4kkbJe8IHyZXCoDkoIpmQxzbrA3/SA==}
peerDependencies:
'@signozhq/icons': 0.3.0
react: ^18.2.0
@@ -4925,12 +4791,6 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
cmdk@1.1.1:
resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
co@4.6.0:
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
@@ -7459,12 +7319,6 @@ packages:
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
next-themes@0.4.6:
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
peerDependencies:
react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
@@ -8433,8 +8287,8 @@ packages:
'@types/react':
optional: true
react-resizable-panels@4.12.3:
resolution: {integrity: sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==}
react-resizable-panels@4.12.2:
resolution: {integrity: sha512-NwY5LCo4WrxVvDh0xoMML6EMLPONP/8ckKcIdpnojxexoatZdjLiRqLJQjQK5CPkd4SYiB/2M5BVrjZBQtOO7Q==}
peerDependencies:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
@@ -8934,12 +8788,6 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
sonner@2.0.7:
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
peerDependencies:
react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
sort-asc@0.1.0:
resolution: {integrity: sha512-jBgdDd+rQ+HkZF2/OHCmace5dvpos/aWQpcxuyRs9QUbPRnkEJmYVo81PIGpjIdpOcsnJ4rGjStfDHsbn+UVyw==}
engines: {node: '>=0.10.0'}
@@ -9950,7 +9798,7 @@ snapshots:
'@ant-design/cssinjs@1.17.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@emotion/hash': 0.8.0
'@emotion/unitless': 0.7.5
classnames: 2.3.2
@@ -9966,7 +9814,7 @@ snapshots:
dependencies:
'@ant-design/colors': 7.0.0
'@ant-design/icons-svg': 4.3.1
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -9974,7 +9822,7 @@ snapshots:
'@ant-design/react-slick@1.0.2(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
json2mq: 0.2.0
react: 18.2.0
@@ -12498,8 +12346,6 @@ snapshots:
dependencies:
cross-spawn: 7.0.6
'@radix-ui/number@1.1.3': {}
'@radix-ui/primitive@1.1.7': {}
'@radix-ui/react-arrow@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
@@ -12511,18 +12357,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-collection@1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-compose-refs@1.1.5(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12558,12 +12392,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-direction@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
@@ -12674,101 +12502,6 @@ snapshots:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-progress@1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-radio-group@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-roving-focus@1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-select@2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-focus-guards': 1.1.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-popper': 1.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-portal': 1.1.17(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-callback-ref': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
aria-hidden: 1.2.6
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-remove-scroll: 2.7.2(@types/react@18.0.26)(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slider@1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/number': 1.1.3
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-collection': 1.1.15(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-layout-effect': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-previous': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-use-size': 1.1.4(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-slot@1.3.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
@@ -12776,22 +12509,6 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-tabs@1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/primitive': 1.1.7
'@radix-ui/react-context': 1.2.2(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-direction': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-presence': 1.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-use-controllable-state': 1.2.6(@types/react@18.0.26)(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/react-use-callback-ref@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
@@ -12814,24 +12531,12 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-is-hydrated@0.1.3(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-layout-effect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-previous@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
react: 18.2.0
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-use-rect@1.1.4(@types/react@18.0.26)(react@18.2.0)':
dependencies:
'@radix-ui/rect': 1.1.3
@@ -12846,20 +12551,11 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
'@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
optionalDependencies:
'@types/react': 18.0.26
'@types/react-dom': 18.0.10
'@radix-ui/rect@1.1.3': {}
'@rc-component/color-picker@1.4.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@ctrl/tinycolor': 3.6.1
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -12879,7 +12575,7 @@ snapshots:
'@rc-component/mutate-observer@1.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -12895,7 +12591,7 @@ snapshots:
'@rc-component/tour@1.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
@@ -12905,7 +12601,7 @@ snapshots:
'@rc-component/trigger@1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)':
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -13150,41 +12846,33 @@ snapshots:
'@sideway/pinpoint@2.0.0': {}
'@signozhq/design-tokens@2.2.1': {}
'@signozhq/design-tokens@2.6.0': {}
'@signozhq/icons@0.4.0': {}
'@signozhq/ui@0.2.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
'@signozhq/ui@0.3.0(@date-fns/tz@1.4.1)(@emotion/is-prop-valid@1.2.0)(@signozhq/icons@0.4.0)(@types/react-dom@18.0.10)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)':
dependencies:
'@base-ui/react': 1.7.0(@date-fns/tz@1.4.1)(@types/react@18.0.26)(date-fns@4.1.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@chenglou/pretext': 0.0.8
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-icons': 1.3.2(react@18.2.0)
'@radix-ui/react-popover': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-progress': 1.1.16(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-radio-group': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-select': 2.3.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slider': 1.4.7(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-slot': 1.3.3(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-tabs': 1.1.21(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@signozhq/icons': 0.4.0
'@tanstack/react-table': 8.21.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@tanstack/react-virtual': 3.14.9(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@types/lodash-es': 4.17.12
clsx: 2.1.1
cmdk: 1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
copy-text-to-clipboard: 3.2.2
dayjs: 1.11.23
lodash-es: 4.18.1
motion: 11.18.2(@emotion/is-prop-valid@1.2.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next-themes: 0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
nuqs: 2.9.6(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.6(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-compiler-runtime: 1.0.0(react@18.2.0)
react-day-picker: 9.9.0(react@18.2.0)
react-dom: 18.2.0(react@18.2.0)
react-resizable-panels: 4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
sonner: 2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react-resizable-panels: 4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
transitivePeerDependencies:
- '@date-fns/tz'
- '@emotion/is-prop-valid'
@@ -14434,7 +14122,7 @@ snapshots:
better-xlsx@0.7.6:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
jszip: 3.10.1
kind-of: 6.0.3
@@ -14471,7 +14159,7 @@ snapshots:
broadcast-channel@3.7.0:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
detect-node: 2.1.0
js-sha3: 0.8.0
microseconds: 0.2.0
@@ -14680,18 +14368,6 @@ snapshots:
clsx@2.1.1: {}
cmdk@1.1.1(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@radix-ui/react-compose-refs': 1.1.5(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@radix-ui/react-id': 1.1.4(@types/react@18.0.26)(react@18.2.0)
'@radix-ui/react-primitive': 2.1.10(@types/react-dom@18.0.10)(@types/react@18.0.26)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
co@4.6.0: {}
codemirror@6.0.1:
@@ -17240,7 +16916,7 @@ snapshots:
match-sorter@6.3.1:
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
remove-accents: 0.4.2
math-intrinsics@1.1.0: {}
@@ -17750,11 +17426,6 @@ snapshots:
neo-async@2.6.2: {}
next-themes@0.4.6(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
node-addon-api@7.1.1:
optional: true
@@ -18378,7 +18049,7 @@ snapshots:
rc-cascader@3.20.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
array-tree-filter: 2.1.0
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18389,7 +18060,7 @@ snapshots:
rc-checkbox@3.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18397,7 +18068,7 @@ snapshots:
rc-collapse@3.7.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18406,7 +18077,7 @@ snapshots:
rc-dialog@9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18416,7 +18087,7 @@ snapshots:
rc-drawer@6.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18426,7 +18097,7 @@ snapshots:
rc-dropdown@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18435,7 +18106,7 @@ snapshots:
rc-field-form@1.40.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
async-validator: 4.2.5
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18443,7 +18114,7 @@ snapshots:
rc-image@7.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/portal': 1.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-dialog: 9.3.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18454,7 +18125,7 @@ snapshots:
rc-input-number@8.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/mini-decimal': 1.0.1
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18464,7 +18135,7 @@ snapshots:
rc-input@1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18472,7 +18143,7 @@ snapshots:
rc-mentions@2.9.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18484,7 +18155,7 @@ snapshots:
rc-menu@9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18495,7 +18166,7 @@ snapshots:
rc-motion@2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18503,7 +18174,7 @@ snapshots:
rc-notification@5.3.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18512,7 +18183,7 @@ snapshots:
rc-overflow@1.3.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18521,7 +18192,7 @@ snapshots:
rc-pagination@3.7.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18529,7 +18200,7 @@ snapshots:
rc-picker@3.14.6(date-fns@4.1.0)(dayjs@1.11.20)(moment@2.30.1)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18542,7 +18213,7 @@ snapshots:
rc-progress@3.5.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18550,7 +18221,7 @@ snapshots:
rc-rate@2.12.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18558,7 +18229,7 @@ snapshots:
rc-resize-observer@1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18567,7 +18238,7 @@ snapshots:
rc-segmented@2.2.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18588,7 +18259,7 @@ snapshots:
rc-slider@10.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18596,7 +18267,7 @@ snapshots:
rc-steps@6.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18604,7 +18275,7 @@ snapshots:
rc-switch@4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18612,7 +18283,7 @@ snapshots:
rc-table@7.35.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/context': 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18623,7 +18294,7 @@ snapshots:
rc-tabs@12.13.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-dropdown: 4.1.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-menu: 9.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18635,7 +18306,7 @@ snapshots:
rc-textarea@1.5.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-input: 1.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18645,7 +18316,7 @@ snapshots:
rc-tooltip@6.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
'@rc-component/trigger': 1.18.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
classnames: 2.3.2
react: 18.2.0
@@ -18653,7 +18324,7 @@ snapshots:
rc-tree-select@5.15.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-select: 14.10.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-tree: 5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18663,7 +18334,7 @@ snapshots:
rc-tree@5.8.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-motion: 2.9.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18673,7 +18344,7 @@ snapshots:
rc-upload@4.3.5(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react: 18.2.0
@@ -18681,14 +18352,14 @@ snapshots:
rc-util@5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
react-is: 18.3.1
rc-virtual-list@3.11.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
classnames: 2.3.2
rc-resize-observer: 1.4.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
rc-util: 5.38.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -18914,7 +18585,7 @@ snapshots:
optionalDependencies:
'@types/react': 18.0.26
react-resizable-panels@4.12.3(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
react-resizable-panels@4.12.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
@@ -18965,7 +18636,7 @@ snapshots:
react-router@5.3.4(react@18.2.0):
dependencies:
'@babel/runtime': 7.28.2
'@babel/runtime': 7.29.2
history: 4.10.1
hoist-non-react-statics: 3.3.2
loose-envify: 1.4.0
@@ -19505,11 +19176,6 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
sonner@2.0.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
sort-asc@0.1.0: {}
sort-desc@0.1.1: {}

View File

@@ -0,0 +1,92 @@
Copyright (c) 2023 Vercel, in collaboration with basement.studio
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION AND CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -7079,7 +7079,7 @@ export interface Querybuildertypesv5QueryBuilderFormulaDTO {
*/
functions?: Querybuildertypesv5FunctionDTO[] | null;
/**
* @description Accepted but currently ignored; the computed values are returned unfiltered.
* @description A filter on the formula values, referencing the formula by its name or as __result, for example F1 > 0. A time series keeps only the points that match and drops a series with none left; a scalar result keeps only the rows that match.
*/
having?: Querybuildertypesv5HavingDTO;
/**

View File

@@ -90,10 +90,17 @@ function AddCreditCardModal({
</AuthZTooltip>,
]}
>
<Typography.Text className="add-credit-card-text">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
You&apos;re currently on <span className="highlight-text">Trial plan</span>.
Add a credit card to access SigNoz chat support to your workspace.
</Typography.Text>
</Typography>
</Modal>
);
}

View File

@@ -1,6 +1,9 @@
.breadcrumb {
padding: var(--spacing-4) 16px var(--spacing-2);
// The antd breadcrumb provides the item spacing.
--button-padding: 0;
ol {
align-items: center;
}
@@ -8,13 +11,4 @@
:global(.ant-breadcrumb-separator) {
color: var(--muted-foreground);
}
[data-slot='button'] {
padding: 0px;
}
}
.divider {
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
}

View File

@@ -28,7 +28,7 @@ function AlertBreadcrumb({
items={breadcrumbItems}
data-testid={testId}
/>
{showDivider && <Divider className={styles.divider} />}
{showDivider && <Divider spacing="10px 16px" />}
</>
);
}

View File

@@ -1,5 +1,4 @@
.itemLast {
color: var(--button-secondary-label);
font-size: var(--button-font-size, var(--periscope-font-size-base));
line-height: var(--button-size-line-height, 16px);
}

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import cx from 'classnames';
import { useLayoutEffect, useRef, useState } from 'react';

View File

@@ -1,6 +1,6 @@
import { Copy } from '@signozhq/icons';
import { Badge, type BadgeColorType } from '@signozhq/ui/badge';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Tooltip } from '@signozhq/ui/tooltip';
import { useCopyToClipboard } from 'react-use';

View File

@@ -26,8 +26,6 @@
}
.brand-title {
font-size: 20px;
font-weight: 600;
color: var(--l1-foreground); // Primary text
margin: 0;
}

View File

@@ -28,15 +28,29 @@ function AppLoading(): JSX.Element {
<div className="brand">
<img src={signozBrandLogoUrl} alt="SigNoz" className="brand-logo" />
<Typography.Title level={2} className="brand-title">
<Typography
variant="label"
size="large"
weight="bold"
color="main"
as="h2"
textOverflow="wrap"
>
SigNoz
</Typography.Title>
</Typography>
</div>
<div className="brand-tagline">
<Typography.Text>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
OpenTelemetry-Native Logs, Metrics and Traces in a single pane
</Typography.Text>
</Typography>
</div>
<div className="loader" />

View File

@@ -0,0 +1,11 @@
// Clears the pagination bars and footers that sit above the bottom strip, and the 56px footer of
// a drawer or dialog panel, which sits 16px from the edge of the window.
.toaster[data-slot='toaster'][data-position^='bottom'] {
bottom: calc(var(--bottom-strip-height, 0px) + var(--spacing-48));
}
// Clears the header of a drawer or dialog panel, 16px from the edge plus 48px or 56px.
:global(body):has([data-slot='drawer-popup'], [data-slot='dialog-popup'])
.toaster[data-slot='toaster'][data-position^='top'] {
top: var(--spacing-40);
}

View File

@@ -0,0 +1,11 @@
import { Toaster } from '@signozhq/ui/toast';
import styles from './AppToaster.module.scss';
function AppToaster(): JSX.Element {
return (
<Toaster position="bottom-right" offset={24} className={styles.toaster} />
);
}
export default AppToaster;

View File

@@ -35,7 +35,6 @@
&__error-code {
color: var(--bg-cherry-100);
font-size: 13px;
font-weight: 500;
line-height: 1;
letter-spacing: -0.065px;
}
@@ -43,8 +42,6 @@
&__error-message {
color: var(--bg-cherry-300);
font-size: 13px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.065px;
}
@@ -75,7 +72,6 @@
&__message-item {
color: var(--bg-cherry-300);
font-size: 13px;
font-weight: 400;
line-height: 20px;
letter-spacing: -0.065px;

View File

@@ -45,9 +45,8 @@
}
.auth-footer-text {
font-family: var(--font-family-inter, Inter, sans-serif);
font-family: var(--label-small-400-font-family);
font-size: 11px;
font-weight: 400;
line-height: 1;
color: var(--l2-foreground);
text-align: center;
@@ -57,7 +56,6 @@
display: flex;
align-items: center;
gap: 6px;
text-decoration: none;
transition: opacity 0.2s ease;
&:hover {

View File

@@ -16,7 +16,6 @@
display: flex;
align-items: center;
gap: 4.9px;
text-decoration: none;
}
.auth-header-logo-icon {
@@ -26,7 +25,7 @@
}
.auth-header-logo-text {
font-family: Satoshi, var(--font-family-inter, Inter), sans-serif;
font-family: Satoshi, var(--label-medium-500-font-family), sans-serif;
font-size: 15.4px;
font-weight: 500;
line-height: 17.5px;

View File

@@ -17,8 +17,3 @@
letter-spacing: -0.01em;
color: var(--l1-foreground);
}
.divider {
--divider-color: var(--l1-border);
--divider-margin: 10px 0 16px 0;
}

View File

@@ -44,7 +44,7 @@ function BackHeader({
</Button>
<div className={styles.title}>{title}</div>
</div>
<Divider className={styles.divider} />
<Divider spacing="10px 16px" />
</>
);
}

View File

@@ -23,7 +23,6 @@
.ant-table {
.ant-table-thead > tr > th {
padding: 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
@@ -31,10 +30,7 @@
border-bottom: none;
color: var(--l2-foreground);
font-family: Inter;
font-size: 11px;
font-style: normal;
font-weight: 600;
line-height: 18px; /* 163.636% */
letter-spacing: 0.44px;
@@ -45,7 +41,6 @@
.ant-table-cell {
padding: 12px;
font-size: 13px;
line-height: 20px;
color: var(--l1-foreground);
background: var(--l1-background);
@@ -67,12 +62,6 @@
.ant-table-cell:nth-child(n + 3) {
padding-right: 24px;
}
.column-header-right {
text-align: right;
}
.column-header-left {
text-align: left;
}
.ant-table-tbody > tr > td {
border-bottom: none;
}

View File

@@ -40,10 +40,27 @@ const INITIAL_PAGE_SIZE = 20;
const showPaginationItem = (total: number, range: number[]): JSX.Element => (
<>
<Typography.Text className="numbers">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{range[0]} &#8212; {range[1]}
</Typography.Text>
<Typography.Text className="total"> of {total}</Typography.Text>
</Typography>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{' '}
of {total}
</Typography>
</>
);
@@ -56,22 +73,15 @@ function ProgressRender(item: string | number): JSX.Element {
const percent = Number(Number(item).toFixed(1));
return (
<div className="progress-container">
<Progress
percent={percent}
strokeLinecap="butt"
showInfo
strokeColor={((): string => {
const cpuPercent = percent;
if (cpuPercent >= 90) {
return Color.BG_SAKURA_500;
<div className="progress-bar">
<Progress
percent={percent}
showInfo
color={
percent >= 90 ? 'highlight-danger' : percent >= 60 ? 'warning' : 'success'
}
if (cpuPercent >= 60) {
return Color.BG_AMBER_500;
}
return Color.BG_FOREST_500;
})()}
className="progress-bar"
/>
/>
</div>
</div>
);
}
@@ -526,7 +536,18 @@ export default function CeleryOverviewTable({
),
}}
locale={{
emptyText: isLoading ? null : <Typography.Text>No data</Typography.Text>,
emptyText: isLoading ? null : (
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
No data
</Typography>
),
}}
scroll={{ x: 'max-content' }}
showSorterTooltip

View File

@@ -24,9 +24,16 @@ function CeleryTaskConfigOptions(): JSX.Element {
return (
<div className="celery-task-filters">
<div className="celery-filters">
<Typography.Text style={{ whiteSpace: 'nowrap' }}>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="visible"
>
Task Name
</Typography.Text>
</Typography>
<Select
data-testid="celery-task-name-select"
placeholder="Task Name"

View File

@@ -49,11 +49,7 @@
.subtitle {
color: var(--l2-foreground);
font-family: Inter;
font-size: 12px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
}
}
@@ -64,11 +60,6 @@
.footer-text {
color: var(--l2-foreground);
font-family: Inter;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: 20px;
}
}
}

View File

@@ -65,15 +65,38 @@ export default function CeleryTaskDetail({
width="45%"
title={
<div>
<Typography.Text className="title">{`Details - ${taskData.entity}`}</Typography.Text>
<Typography
variant="label"
size="large"
weight="bold"
color="main"
as="span"
textOverflow="wrap"
>{`Details - ${taskData.entity}`}</Typography>
<div>
<Typography.Text className="subtitle">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
{`${formatTimestamp(startTime)} ${
endTime ? `- ${formatTimestamp(endTime)}` : ''
}`}
</Typography.Text>
<Divider type="vertical" />
<Typography.Text className="subtitle">{taskData.value}</Typography.Text>
</Typography>
<Divider orientation="vertical" />
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
{taskData.value}
</Typography>
</div>
</div>
}
@@ -88,7 +111,14 @@ export default function CeleryTaskDetail({
destroyOnClose
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
footer={
<Typography.Text className="footer-text">{`Total Task: ${totalTask}`}</Typography.Text>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>{`Total Task: ${totalTask}`}</Typography>
}
>
<CeleryTaskGraph

View File

@@ -52,11 +52,6 @@
display: flex;
justify-content: center;
align-items: center;
.celery-task-graph-worker-count-text {
font-size: 2.5vw;
text-align: center;
}
}
.worker-count-header {
@@ -148,8 +143,6 @@
.configure-option-Info-text {
color: var(--l2-foreground);
font-size: 12px;
font-weight: 400;
}
}
@@ -217,7 +210,6 @@
}
&__label {
font-family: 'Inter';
font-size: 14px;
color: var(--l2-foreground);
line-height: 20px;
@@ -225,7 +217,7 @@
}
&__value {
font-family: 'Geist Mono';
font-family: var(--code-base-400-font-family);
font-size: 24px;
color: var(--l1-foreground);
line-height: 32px;

View File

@@ -113,9 +113,16 @@ export default function CeleryTaskGraphGrid({
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography.Text className="section-title">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
Flower Metrics
</Typography.Text>
</Typography>
{collapsedSections.metricBasedGraphs ? (
<ChevronDown
size={14}
@@ -146,14 +153,30 @@ export default function CeleryTaskGraphGrid({
/>
<Card className="celery-task-graph-worker-count">
<div className="worker-count-header">
<Typography.Text className="worker-count-header-text">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
Worker Online
</Typography.Text>
</Typography>
</div>
<div className="worker-count-text-container">
<Typography.Text className="celery-task-graph-worker-count-text">
<Typography
/* TODO(typography): not lifted from className: font-size: 2.5vw */ variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
display="inline-block"
align="center"
textOverflow="wrap"
>
{options.filter((option) => option.value).length}
</Typography.Text>
</Typography>
</div>
</Card>
</div>
@@ -164,9 +187,16 @@ export default function CeleryTaskGraphGrid({
<CardContainer className="row-card" isDarkMode={isDarkMode}>
<div className="row-panel">
<div className="row-panel-section">
<Typography.Text className="section-title">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
Span Based Stats
</Typography.Text>
</Typography>
{collapsedSections.traceBasedGraphs ? (
<ChevronDown
size={14}
@@ -185,9 +215,16 @@ export default function CeleryTaskGraphGrid({
</CardContainer>
<div className="configure-option-Info">
{configureOptionComponent}
<Typography.Text className="configure-option-Info-text">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
Click on a graph co-ordinate to see more details
</Typography.Text>
</Typography>
</div>
</div>
{!collapsedSections.traceBasedGraphs && (

View File

@@ -11,9 +11,7 @@
justify-content: center;
}
&__label {
font-family: 'Geist Mono';
font-size: 12px;
line-height: normal;
font-family: var(--code-base-400-font-family);
}
&--positive {
.change-percentage-pill {

View File

@@ -17,10 +17,6 @@
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;

View File

@@ -17,13 +17,13 @@
pre {
color: var(--l2-foreground) !important;
font-family: 'Geist Mono' !important;
font-family: var(--code-small-400-font-family) !important;
font-size: 12px !important;
}
code {
color: var(--l1-foreground) !important;
font-family: 'Geist Mono' !important;
font-family: var(--code-small-400-font-family) !important;
font-size: 12px !important;
}
}

View File

@@ -1,36 +1,3 @@
.create-sa-modal {
max-width: 530px;
background: var(--popover);
border: 1px solid var(--secondary);
border-radius: 4px;
box-shadow: 0 4px 9px 0 rgba(0, 0, 0, 0.04);
[data-slot='dialog-header'] {
padding: var(--padding-4);
border-bottom: 1px solid var(--secondary);
flex-shrink: 0;
background: transparent;
margin: 0;
}
[data-slot='dialog-title'] {
font-size: var(--label-base-400-font-size);
font-weight: var(--label-base-400-font-weight);
line-height: var(--label-base-400-line-height);
letter-spacing: -0.065px;
color: var(--l1-foreground);
margin: 0;
}
[data-slot='dialog-description'] {
padding: 0;
.create-sa-modal__content {
padding: var(--padding-4);
}
}
}
.create-sa-form {
display: flex;
flex-direction: column;
@@ -43,32 +10,11 @@
margin-bottom: var(--spacing-4);
> label {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
letter-spacing: -0.07px;
}
}
&__input {
height: 32px;
color: var(--l1-foreground);
background-color: var(--l2-background);
border-color: var(--l1-border);
font-size: var(--paragraph-base-400-font-size);
border-radius: 2px;
width: 100%;
&::placeholder {
color: var(--l3-foreground);
}
&:focus {
border-color: var(--primary);
box-shadow: none;
}
}
&__error {
font-size: var(--paragraph-small-400-font-size);
color: var(--destructive);
@@ -77,22 +23,7 @@
}
&__helper {
font-size: var(--paragraph-small-400-font-size);
color: var(--l3-foreground);
margin: calc(var(--spacing-2) * -1) 0 var(--spacing-4) 0;
line-height: var(--paragraph-small-400-line-height);
}
}
.create-sa-modal__footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-end;
padding: 0 var(--padding-4);
height: 56px;
min-height: 56px;
border-top: 1px solid var(--secondary);
gap: var(--spacing-4);
flex-shrink: 0;
}

View File

@@ -5,9 +5,9 @@ import { Button } from '@signozhq/ui/button';
import AuthZButton from 'lib/authz/components/AuthZButton/AuthZButton';
import { AuthZGuardContent } from 'lib/authz/components/AuthZGuard/AuthZGuardContent';
import { SACreatePermission } from 'lib/authz/hooks/useAuthZ/permissions/service-account.permissions';
import { DialogFooter, DialogWrapper } from '@signozhq/ui/dialog';
import { Dialog } from '@signozhq/ui/dialog';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
invalidateListServiceAccounts,
@@ -35,7 +35,7 @@ function CreateServiceAccountModal(): JSX.Element {
);
const [, setSelectedAccountId] = useQueryState(SA_QUERY_PARAMS.ACCOUNT);
const { showErrorModal, isErrorModalVisible } = useErrorModal();
const { showErrorModal } = useErrorModal();
const {
control,
@@ -82,84 +82,75 @@ function CreateServiceAccountModal(): JSX.Element {
}
return (
<DialogWrapper
<Dialog
title="New Service Account"
open={isOpen}
onOpenChange={(open): void => {
if (!open) {
handleClose();
}
}}
showCloseButton
width="narrow"
className="create-sa-modal"
disableOutsideClick={isErrorModalVisible}
onClose={handleClose}
width="base"
testId="create-service-account-modal"
>
<div className="create-sa-modal__content">
<AuthZGuardContent
checks={[SACreatePermission]}
fallbackOnLoading={<Skeleton active paragraph={{ rows: 1 }} />}
>
<form
id="create-sa-form"
className="create-sa-form"
onSubmit={handleSubmit(handleCreate)}
footerSuffix={
<>
<Button
size="md"
type="button"
variant="solid"
color="secondary"
onClick={handleClose}
testId="create-sa-cancel-btn"
prefix={<X size={12} />}
>
<div className="create-sa-form__item">
<label htmlFor="sa-name">Name</label>
<Controller
name="name"
control={control}
rules={{ required: 'Name is required' }}
render={({ field }): JSX.Element => (
<Input
id="sa-name"
placeholder="Enter a name"
className="create-sa-form__input"
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
data-testid="create-sa-name-input"
/>
)}
/>
{errors.name && (
<p className="create-sa-form__error">{errors.name.message}</p>
Cancel
</Button>
<AuthZButton
size="md"
checks={[SACreatePermission]}
type="submit"
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
testId="create-sa-submit-btn"
>
Create Service Account
</AuthZButton>
</>
}
>
<AuthZGuardContent
checks={[SACreatePermission]}
fallbackOnLoading={<Skeleton active paragraph={{ rows: 1 }} />}
>
<form
id="create-sa-form"
className="create-sa-form"
onSubmit={handleSubmit(handleCreate)}
>
<div className="create-sa-form__item">
<label htmlFor="sa-name">Name</label>
<Controller
name="name"
control={control}
rules={{ required: 'Name is required' }}
render={({ field }): JSX.Element => (
<Input
id="sa-name"
placeholder="Enter a name"
value={field.value}
onChange={field.onChange}
onBlur={field.onBlur}
testId="create-sa-name-input"
/>
)}
</div>
</form>
</AuthZGuardContent>
</div>
<DialogFooter className="create-sa-modal__footer">
<Button
size="md"
type="button"
variant="solid"
color="secondary"
onClick={handleClose}
testId="create-sa-cancel-btn"
prefix={<X size={12} />}
>
Cancel
</Button>
<AuthZButton
size="md"
checks={[SACreatePermission]}
type="submit"
form="create-sa-form"
variant="solid"
color="primary"
loading={isSubmitting}
disabled={!isValid}
testId="create-sa-submit-btn"
>
Create Service Account
</AuthZButton>
</DialogFooter>
</DialogWrapper>
/>
{errors.name && (
<p className="create-sa-form__error">{errors.name.message}</p>
)}
</div>
</form>
</AuthZGuardContent>
</Dialog>
);
}

View File

@@ -1,4 +1,4 @@
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import {
setupAuthzAdmin,
setupAuthzDenyAll,
@@ -10,9 +10,9 @@ import { render, screen, userEvent, waitFor } from 'tests/test-utils';
import CreateServiceAccountModal from '../CreateServiceAccountModal';
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
const mockToast = jest.mocked(toast);
@@ -23,7 +23,6 @@ jest.mock('providers/ErrorModalProvider', () => ({
...jest.requireActual('providers/ErrorModalProvider'),
useErrorModal: jest.fn(() => ({
showErrorModal,
isErrorModalVisible: false,
})),
}));
@@ -58,7 +57,7 @@ describe('CreateServiceAccountModal', () => {
// The form only renders once the create check resolves, and the name field
// registers its `required` rule on mount, so the empty-form invalid state
// settles a tick later.
await screen.findByTestId('create-sa-name-input');
await screen.findByTestId('create-sa-name-input-field');
await waitFor(() =>
expect(screen.getByTestId('create-sa-submit-btn')).toHaveAttribute(
@@ -72,7 +71,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
const submitBtn = await screen.findByTestId('create-sa-submit-btn');
await user.type(nameInput, 'test');
@@ -90,7 +89,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -128,7 +127,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId<HTMLInputElement>(
'create-sa-name-input',
'create-sa-name-input-field',
);
await user.type(nameInput, 'Deploy Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -160,7 +159,7 @@ describe('CreateServiceAccountModal', () => {
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Dupe Bot');
const submitBtn = screen.getByTestId('create-sa-submit-btn');
@@ -209,7 +208,9 @@ describe('CreateServiceAccountModal', () => {
screen.findByText(/is not authorized to perform/i),
).resolves.toBeInTheDocument();
expect(screen.queryByTestId('create-sa-name-input')).not.toBeInTheDocument();
expect(
screen.queryByTestId('create-sa-name-input-field'),
).not.toBeInTheDocument();
expect(
screen.getByTestId('create-service-account-modal'),
).toBeInTheDocument();
@@ -244,7 +245,7 @@ describe('CreateServiceAccountModal', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderModal();
const nameInput = await screen.findByTestId('create-sa-name-input');
const nameInput = await screen.findByTestId('create-sa-name-input-field');
await user.type(nameInput, 'Bot');
await user.clear(nameInput);

View File

@@ -65,8 +65,6 @@
.valid-format-error {
margin-top: 4px;
color: var(--bg-cherry-400) !important;
font-size: 13px !important;
font-weight: 400 !important;
}
.info-text {
@@ -76,11 +74,6 @@
padding: 4px;
cursor: default;
color: var(--l2-foreground) !important;
font-size: 14px;
font-style: normal;
font-weight: 400;
line-height: normal;
letter-spacing: 0.14px;
}
.info-text:hover {
@@ -104,15 +97,11 @@
gap: 6px;
.timezone__name {
font-size: 12px;
line-height: 16px;
color: var(--bg-robin-400);
font-weight: 500;
}
.timezone__separator {
font-size: 12px;
line-height: 16px;
color: var(--bg-robin-300);
font-weight: 500;
}
@@ -228,10 +217,6 @@
border-radius: 2px;
background: color-mix(in srgb, var(--bg-robin-200) 4%, transparent);
color: var(--l1-foreground);
font-size: 12px;
font-weight: 400;
line-height: 16px;
letter-spacing: -0.06px;
cursor: pointer;
height: 20px;
width: 20px;

View File

@@ -531,7 +531,7 @@ function CustomTimePicker({
// Use setTimeout to wait for React to update the DOM and make input editable
setTimeout(() => {
const inputElement = inputRef.current?.input;
if (inputElement) {
if (inputElement && !document.activeElement?.closest('.timezone-picker')) {
inputElement.focus();
inputElement.select();
}

View File

@@ -21,7 +21,6 @@ $item-spacing: 8px;
.timezone-picker {
width: 532px;
color: var(--l2-foreground);
font-family: $font-family;
&__search {
@include flex-center;
@@ -36,34 +35,6 @@ $item-spacing: 8px;
width: -webkit-fill-available;
}
&__input {
@include text-style-base;
width: 100%;
background: transparent;
border: none;
border-radius: 0;
box-shadow: none;
outline: none;
height: auto;
color: var(--l1-foreground);
font-size: 14px;
line-height: 20px;
letter-spacing: -0.07px;
padding: 0;
&:focus,
&:focus-visible,
&:hover {
border: none;
box-shadow: none;
outline: none;
}
&::placeholder {
color: var(--l2-foreground);
}
}
&__esc-key {
@include text-style-base;
font-size: 8px;

View File

@@ -65,7 +65,8 @@ function SearchBar({
/>
<Input
type="text"
className="timezone-picker__input"
variant="unstyled"
noFocusRing
placeholder="Search timezones..."
value={value}
onChange={(e): void => onChange(e.target.value)}

View File

@@ -12,5 +12,4 @@
.name {
color: var(--danger-background);
font-weight: var(--font-weight-medium);
}

View File

@@ -1,7 +1,4 @@
.details-header {
// ghost + secondary missing hover bg token in @signozhq/button
--button-ghost-hover-background: var(--l3-background);
box-sizing: border-box;
display: flex;
align-items: center;
@@ -12,7 +9,6 @@
background: var(--l2-background);
&__title {
font-size: 13px;
font-weight: 500;
color: var(--l1-foreground);
flex: 1;

View File

@@ -1,17 +0,0 @@
.drawer {
overflow: hidden;
border-color: var(--l2-border);
background: var(--l2-background);
box-shadow: -4px 10px 16px 2px var(--l1-shadow);
&:focus,
&:focus-visible {
outline: none;
}
}
.body {
display: flex;
flex-direction: column;
height: 100%;
}

View File

@@ -1,56 +1,49 @@
import { Drawer, DrawerContent } from '@signozhq/ui/drawer';
import cx from 'classnames';
import { AnimatePresence } from 'motion/react';
import styles from './DetailsPanelDrawer.module.scss';
import { ReactNode } from 'react';
import { Drawer } from '@signozhq/ui/drawer';
interface DetailsPanelDrawerProps {
isOpen: boolean;
onClose: () => void;
children: React.ReactNode;
className?: string;
title: string;
children: ReactNode;
headerPrefix?: ReactNode;
headerSuffix?: ReactNode;
loading?: boolean;
testId?: string;
}
function DetailsPanelDrawer({
isOpen,
onClose,
title,
children,
className,
headerPrefix,
headerSuffix,
loading,
testId,
}: DetailsPanelDrawerProps): JSX.Element {
return (
<Drawer
type="inspector"
stack="overlay"
open={isOpen}
onOpenChange={(open): void => {
if (!open) {
onClose();
}
}}
modal={false}
onClose={onClose}
title={title}
width="extra-wide"
headerPrefix={headerPrefix}
headerSuffix={headerSuffix}
loading={loading}
testId={testId}
>
<AnimatePresence>
{isOpen && (
<DrawerContent
forceMount
direction="right"
width="extra-wide"
showOverlay={false}
// Outside presses are left to the page, which knows what counts as inside.
onInteractOutside={(e): void => e.preventDefault()}
className={cx(styles.drawer, className)}
testId={testId}
>
<div className={styles.body}>{children}</div>
</DrawerContent>
)}
</AnimatePresence>
{children}
</Drawer>
);
}
DetailsPanelDrawer.defaultProps = {
className: undefined,
headerPrefix: undefined,
headerSuffix: undefined,
loading: false,
testId: undefined,
};

View File

@@ -68,7 +68,17 @@ export default function DownloadOptionsMenu({
aria-modal="true"
>
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
FORMAT
</Typography>
<RadioGroup
color="primary"
value={exportFormat}
@@ -83,7 +93,17 @@ export default function DownloadOptionsMenu({
<div className="horizontal-line" />
<div className="row-limit">
<Typography.Text className="title">Number of Rows</Typography.Text>
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
Number of Rows
</Typography>
<RadioGroup
color="primary"
value={String(rowLimit)}
@@ -101,7 +121,17 @@ export default function DownloadOptionsMenu({
<div className="horizontal-line" />
<div className="columns-scope">
<Typography.Text className="title">Columns</Typography.Text>
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
Columns
</Typography>
<RadioGroup
color="primary"
value={columnsScope}

View File

@@ -1,6 +1,5 @@
import { Trash2, X } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { AlertDialog } from '@signozhq/ui/alert-dialog';
import { MemberRow } from 'components/MembersTable/MembersTable';
interface DeleteMemberDialogProps {
@@ -9,7 +8,7 @@ interface DeleteMemberDialogProps {
member: MemberRow | null;
isDeleting: boolean;
onClose: () => void;
onConfirm: () => void;
onConfirm: () => Promise<void>;
}
function DeleteMemberDialog({
@@ -36,47 +35,20 @@ function DeleteMemberDialog({
</>
);
const footer = (
<>
<Button
size="md"
variant="solid"
color="secondary"
onClick={onClose}
prefix={<X size={12} />}
>
Cancel
</Button>
<Button
size="md"
variant="solid"
color="danger"
onClick={onConfirm}
testId="member-delete-confirm-btn"
loading={isDeleting}
prefix={<Trash2 size={12} />}
>
{isDeleting ? 'Processing...' : title}
</Button>
</>
);
return (
<DialogWrapper
<AlertDialog
open={open}
onOpenChange={(isOpen): void => {
if (!isOpen) {
onClose();
}
}}
title={title}
width="narrow"
showCloseButton={false}
disableOutsideClick={false}
footer={footer}
color="danger"
confirmText={isDeleting ? 'Processing...' : title}
confirmIcon={<Trash2 size={12} />}
cancelIcon={<X size={12} />}
onConfirm={onConfirm}
onCancel={onClose}
testId="member-delete"
>
{body}
</DialogWrapper>
</AlertDialog>
);
}

View File

@@ -20,10 +20,9 @@
}
&__label {
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
font-size: var(--paragraph-base-400-font-size);
color: var(--foreground);
line-height: var(--line-height-20);
line-height: var(--paragraph-base-400-line-height);
letter-spacing: -0.07px;
cursor: default;
}
@@ -53,10 +52,8 @@
}
&__email-text {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--foreground);
line-height: var(--line-height-18);
line-height: var(--paragraph-small-400-line-height);
letter-spacing: -0.07px;
white-space: nowrap;
overflow: hidden;
@@ -86,41 +83,26 @@
[data-slot='badge'] {
padding: var(--padding-1) var(--padding-2);
align-items: center;
font-size: var(--uppercase-small-500-font-size);
font-weight: var(--uppercase-small-500-font-weight);
line-height: 100%;
letter-spacing: 0.44px;
}
}
&__meta-label {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
font-size: var(--uppercase-small-500-font-size);
font-weight: var(--uppercase-small-500-font-weight);
color: var(--foreground);
line-height: var(--line-height-20);
line-height: var(--uppercase-small-500-line-height);
letter-spacing: 0.48px;
text-transform: uppercase;
}
&__footer {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
&__footer-left {
display: flex;
align-items: center;
gap: var(--spacing-4);
}
&__footer-right {
display: flex;
align-items: center;
gap: var(--spacing-6);
}
&__footer-divider {
width: 1px;
height: 21px;
@@ -163,14 +145,6 @@
}
.reset-link-dialog {
background: var(--l2-background);
border: 1px solid var(--l1-border);
[data-slot='dialog-header'] {
border-color: var(--l1-border);
color: var(--l1-foreground);
}
&__content {
display: flex;
flex-direction: column;
@@ -178,8 +152,6 @@
}
&__description {
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--l2-foreground);
line-height: var(--paragraph-base-400-line-height);
letter-spacing: -0.065px;
@@ -211,10 +183,8 @@
overflow: hidden;
text-overflow: ellipsis;
padding: 0 var(--padding-2);
font-size: var(--paragraph-base-400-font-size);
font-weight: var(--paragraph-base-400-font-weight);
color: var(--l2-foreground);
line-height: var(--line-height-18);
line-height: var(--paragraph-small-400-line-height);
letter-spacing: -0.07px;
}
}

View File

@@ -3,9 +3,9 @@ import { useCopyToClipboard } from 'react-use';
import { LockKeyhole, RefreshCw, Trash2, X } from '@signozhq/icons';
import { Badge } from '@signozhq/ui/badge';
import { Button } from '@signozhq/ui/button';
import { DrawerWrapper } from '@signozhq/ui/drawer';
import { Drawer } from '@signozhq/ui/drawer';
import { Input } from '@signozhq/ui/input';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Skeleton, Tooltip } from 'antd';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import type { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
@@ -210,7 +210,7 @@ function EditMemberDrawer({
const { mutateAsync: updateMyUser } = useUpdateMyUserV2();
const { mutateAsync: updateUser } = useUpdateUser();
const { mutate: deleteUser, isLoading: isDeleting } = useDeleteUser({
const { mutateAsync: deleteUser, isLoading: isDeleting } = useDeleteUser({
mutation: {
onSuccess: (): void => {
toast.success(
@@ -378,11 +378,15 @@ function EditMemberDrawer({
onComplete,
]);
const handleDelete = useCallback((): void => {
const handleDelete = useCallback(async (): Promise<void> => {
if (!member) {
return;
}
deleteUser({ pathParams: { id: member.id } });
try {
await deleteUser({ pathParams: { id: member.id } });
} catch {
// `onError` already shows the error modal, and the confirmation stays open.
}
}, [member, deleteUser]);
const handleGenerateResetLink = useCallback(async (): Promise<void> => {
@@ -409,7 +413,8 @@ function EditMemberDrawer({
setShowResetLinkDialog(true);
onClose();
} else {
toast.error('Failed to generate password reset link', {
toast.danger('Failed to generate password reset link', {
action: { label: 'Dismiss' },
position: 'top-right',
});
}
@@ -438,7 +443,7 @@ function EditMemberDrawer({
useEffect(() => {
if (copyState.error) {
toast.error('Failed to copy link');
toast.danger('Failed to copy link', { action: { label: 'Dismiss' } });
}
}, [copyState.error]);
@@ -471,20 +476,19 @@ function EditMemberDrawer({
<label className="edit-member-drawer__label" htmlFor="member-name">
Name
</label>
<Tooltip title={isRootUser ? ROOT_USER_TOOLTIP : undefined}>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
/>
</Tooltip>
<Input
id="member-name"
value={localDisplayName}
onChange={(e): void => {
setLocalDisplayName(e.target.value);
setSaveErrors((prev) =>
prev.filter((err) => err.context !== 'Name update'),
);
}}
placeholder="Enter name"
disabled={isRootUser || isDeleted}
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : undefined}
/>
</div>
<div className="edit-member-drawer__field">
@@ -615,96 +619,88 @@ function EditMemberDrawer({
</div>
);
const footer = (
<div className="edit-member-drawer__footer">
{!isDeleted && (
<>
<div className="edit-member-drawer__footer-left">
<Button
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
size="md"
onClick={(): void => setShowDeleteConfirm(true)}
testId="member-delete-btn"
disabled={isRootUser || isSelf}
variant="link"
color="danger"
prefix={<Trash2 size={12} />}
>
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
const footerStart = isDeleted ? null : (
<div className="edit-member-drawer__footer-left">
<Button
disabledTooltip={getDeleteTooltip(isRootUser, isSelf)}
size="md"
onClick={(): void => setShowDeleteConfirm(true)}
testId="member-delete-btn"
disabled={isRootUser || isSelf}
variant="link"
color="danger"
prefix={<Trash2 size={12} />}
>
{isInvited ? 'Revoke Invite' : 'Delete Member'}
</Button>
<div className="edit-member-drawer__footer-divider" />
<Button
disabledTooltip={ROOT_USER_TOOLTIP}
size="md"
onClick={handleGenerateResetLink}
testId="member-reset-link-btn"
disabled={isRootUser}
loading={isGeneratingLink || isLoadingTokenStatus}
variant="link"
color="warning"
prefix={<RefreshCw size={12} />}
>
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</div>
<div className="edit-member-drawer__footer-right">
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleClose}
prefix={<X size={14} />}
>
Cancel
</Button>
<Button
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
size="md"
variant="solid"
color="primary"
disabled={!isDirty || isRootUser}
onClick={handleSave}
testId="member-save-btn"
loading={isSaving}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</div>
</>
)}
<div className="edit-member-drawer__footer-divider" />
<Button
disabledTooltip={ROOT_USER_TOOLTIP}
size="md"
onClick={handleGenerateResetLink}
testId="member-reset-link-btn"
disabled={isRootUser}
loading={isGeneratingLink || isLoadingTokenStatus}
variant="link"
color="warning"
prefix={<RefreshCw size={12} />}
>
{isGeneratingLink
? 'Generating...'
: isInvited
? getInviteButtonLabel(
isLoadingTokenStatus,
existingToken,
isTokenExpired,
tokenNotFound,
)
: 'Generate Password Reset Link'}
</Button>
</div>
);
const footerActions = isDeleted ? null : (
<>
<Button
size="md"
variant="outlined"
color="secondary"
onClick={handleClose}
prefix={<X size={14} />}
>
Cancel
</Button>
<Button
disabledTooltip={isRootUser ? ROOT_USER_TOOLTIP : 'No changes to save'}
size="md"
variant="solid"
color="primary"
disabled={!isDirty || isRootUser}
onClick={handleSave}
testId="member-save-btn"
loading={isSaving}
>
{isSaving ? 'Saving...' : 'Save Member Details'}
</Button>
</>
);
return (
<>
<DrawerWrapper
<Drawer
type="task"
stack="overlay"
open={open}
onOpenChange={(isOpen): void => {
if (!isOpen) {
handleClose();
}
}}
direction="right"
showCloseButton
showOverlay={false}
onClose={handleClose}
title="Member Details"
footer={footer}
width="wide"
footerPrefix={footerStart}
footerSuffix={footerActions}
>
{drawerContent}
</DrawerWrapper>
</Drawer>
<ResetLinkDialog
open={showResetLinkDialog}

View File

@@ -1,6 +1,6 @@
import { Check, Copy } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { DialogWrapper } from '@signozhq/ui/dialog';
import { Dialog } from '@signozhq/ui/dialog';
interface ResetLinkDialogProps {
open: boolean;
@@ -22,17 +22,11 @@ function ResetLinkDialog({
onCopy,
}: ResetLinkDialogProps): JSX.Element {
return (
<DialogWrapper
<Dialog
open={open}
onOpenChange={(isOpen): void => {
if (!isOpen) {
onClose();
}
}}
onClose={onClose}
title={linkType === 'invite' ? 'Invite Link' : 'Password Reset Link'}
showCloseButton
width="base"
className="reset-link-dialog"
>
<div className="reset-link-dialog__content">
<p className="reset-link-dialog__description">
@@ -65,7 +59,7 @@ function ResetLinkDialog({
</p>
)}
</div>
</DialogWrapper>
</Dialog>
);
}

View File

@@ -1,5 +1,5 @@
import type { ReactNode } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import {
useCreateResetPasswordToken,
@@ -41,52 +41,31 @@ jest.mock('api/ErrorResponseHandlerForGeneratedAPIs', () => ({
jest.mock('@signozhq/ui/drawer', () => ({
...jest.requireActual('@signozhq/ui/drawer'),
DrawerWrapper: ({
Drawer: ({
children,
footer,
footerPrefix,
footerSuffix,
open,
}: {
children?: ReactNode;
footer?: ReactNode;
footerPrefix?: ReactNode;
footerSuffix?: ReactNode;
open: boolean;
}): JSX.Element | null =>
open ? (
<div>
{children}
{footer}
{footerPrefix}
{footerSuffix}
</div>
) : null,
}));
jest.mock('@signozhq/ui/dialog', () => ({
...jest.requireActual('@signozhq/ui/dialog'),
DialogWrapper: ({
children,
footer,
open,
title,
}: {
children?: ReactNode;
footer?: ReactNode;
open: boolean;
title?: string;
}): JSX.Element | null =>
open ? (
<div role="dialog" aria-label={title}>
{children}
{footer}
</div>
) : null,
DialogFooter: ({ children }: { children?: ReactNode }): JSX.Element => (
<div>{children}</div>
),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));
@@ -209,7 +188,7 @@ describe('EditMemberDrawer', () => {
isLoading: false,
});
(useDeleteUser as jest.Mock).mockReturnValue({
mutate: mockDeleteMutate,
mutateAsync: mockDeleteMutate,
isLoading: false,
});
// Token query: valid token for invited members
@@ -373,7 +352,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutate: mockDeleteMutate.mockImplementation(() => {
mutateAsync: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.();
}),
isLoading: false,
@@ -453,7 +432,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutate: mockDeleteMutate.mockImplementation(() => {
mutateAsync: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onSuccess?.();
}),
isLoading: false,
@@ -567,7 +546,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutate: mockDeleteMutate.mockImplementation(() => {
mutateAsync: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onError?.({});
}),
isLoading: false,
@@ -598,7 +577,7 @@ describe('EditMemberDrawer', () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
(useDeleteUser as jest.Mock).mockImplementation((options) => ({
mutate: mockDeleteMutate.mockImplementation(() => {
mutateAsync: mockDeleteMutate.mockImplementation(() => {
options?.mutation?.onError?.({});
}),
isLoading: false,

View File

@@ -1,22 +1,16 @@
// The mask of the error modal blurs the page and a dialog opened under it, and the
// dialog's own backdrop would otherwise dim the error modal that sits above the page.
body:has(.error-modal__wrap:not([style*='display: none'])) {
--dialog-backdrop-color: transparent;
--alert-dialog-backdrop-color: transparent;
}
.error-modal {
&__trigger {
width: fit-content;
display: flex;
align-items: center;
gap: 4px;
border-radius: 20px;
background: color-mix(in srgb, var(--danger-background) 20%, transparent);
padding-left: 3px;
padding-right: 8px;
cursor: pointer;
span {
color: var(--danger-background);
font-size: 10px;
font-weight: 500;
line-height: 20px; /* 200% */
letter-spacing: 0.4px;
text-transform: uppercase;
}
}
&__wrap {
background: linear-gradient(
@@ -65,7 +59,6 @@
&__value {
padding: 4px 8px;
font-size: 12px;
font-style: normal;
font-weight: 500;
line-height: 16px;
letter-spacing: 0.48px;

View File

@@ -53,8 +53,13 @@ function ErrorModal({
onClick={(): void => setVisible(true)}
onKeyDown={undefined}
>
<Badge variant="solid" color="danger">
<CircleAlert size={14} color={Color.BG_CHERRY_500} /> error
<Badge
variant="solid"
color="danger"
textTransform="uppercase"
prefix={<CircleAlert size={14} />}
>
error
</Badge>
</span>
) : (

View File

@@ -38,7 +38,6 @@
margin: 0;
color: var(--l2-foreground);
font-size: 14px;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
@@ -49,8 +48,6 @@
gap: 6px;
padding: 9px 12.5px;
color: var(--l2-foreground);
font-size: 12px;
font-weight: 400;
line-height: 18px; /* 150% */
letter-spacing: 0.12px;
border-radius: 2px;
@@ -79,7 +76,6 @@
color: var(--l2-foreground);
font-size: 12px;
font-weight: 500;
line-height: 18px; /* 150% */
letter-spacing: 0.48px;
pointer-events: none;
}
@@ -98,7 +94,6 @@
color: var(--l1-foreground);
font-size: 10px;
font-weight: 500;
line-height: 18px; /* 180% */
letter-spacing: 0.5px;
}
}
@@ -133,16 +128,12 @@
position: relative;
margin-bottom: 4px;
color: var(--l2-foreground);
font-family: Geist Mono;
font-size: 12px;
font-weight: 400;
line-height: 18px;
font-family: var(--code-base-400-font-family);
padding: 3px 12px;
padding-left: 26px;
}
&__message-item::before {
font-family: unset;
content: '';
position: absolute;
left: 12px;

View File

@@ -1,12 +1,12 @@
.export-menu-popover {
width: 240px;
padding: 0 12px 12px 12px;
// The popover shell carries no padding of its own, so the content owns it.
padding: var(--spacing-8);
.title {
display: flex;
color: var(--l1-foreground);
font-family: Inter;
font-size: var(--periscope-font-size-small);
font-size: var(--uppercase-small-500-font-size);
font-style: normal;
font-weight: 500;
line-height: 18px;
@@ -23,7 +23,7 @@
// radio option labels — radix popover content inherits the root font
// size; pin to the app's 13px base the antd popover used to impose
label {
font-size: var(--periscope-font-size-base);
font-size: var(--label-base-400-font-size);
}
}
}

View File

@@ -1,6 +1,6 @@
import { Download } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@signozhq/ui/popover';
import { Popover, usePopoverHandle } from '@signozhq/ui/popover';
import { RadioGroup } from '@signozhq/ui/radio-group';
import { Tooltip } from '@signozhq/ui/tooltip';
import { Typography } from '@signozhq/ui/typography';
@@ -36,6 +36,10 @@ export default function ExportMenu({
}: ExportMenuProps): JSX.Element {
const [exportFormat, setExportFormat] = useState<string>(ExportFormat.Csv);
const [isPopoverOpen, setIsPopoverOpen] = useState<boolean>(false);
// Driven by a handle rather than `trigger`: the button carries a Tooltip, and a Tooltip
// routes every prop it does not recognise to its own popup, so a popover's press handlers
// would never reach the button underneath.
const menu = usePopoverHandle(setIsPopoverOpen);
const { isExporting, handleExport: handleClientExport } = useClientExport({
data,
@@ -45,53 +49,72 @@ export default function ExportMenu({
});
const handleExport = useCallback((): void => {
setIsPopoverOpen(false);
menu.close();
handleClientExport({ format: exportFormat as ExportFormat });
}, [exportFormat, handleClientExport]);
}, [menu, exportFormat, handleClientExport]);
return (
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
<>
<Tooltip title="Download">
<PopoverTrigger asChild>
<Button
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
testId={`export-menu-${dataSource}`}
loading={isExporting}
>
<Download size={14} />
</Button>
</PopoverTrigger>
</Tooltip>
<PopoverContent align="end" className="export-menu-popover">
<div className="export-format">
<Typography.Text className="title">FORMAT</Typography.Text>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
variant="ghost"
color="secondary"
size="sm"
icon
aria-label="Download"
aria-haspopup="dialog"
aria-expanded={isPopoverOpen}
testId={`export-menu-${dataSource}`}
loading={isExporting}
prefix={<Download size={16} />}
onClick={(event): void => menu.showAt(event.currentTarget)}
>
Export
<Download size={14} />
</Button>
</PopoverContent>
</Popover>
</Tooltip>
<Popover
side="bottom"
align="end"
width={240}
handle={menu}
aria-label="Export format"
>
<div className="export-menu-popover">
<div className="export-format">
<Typography
variant="uppercase"
size="small"
weight="medium"
color="main"
as="p"
marginBottom={4}
textOverflow="wrap"
>
FORMAT
</Typography>
<RadioGroup
color="primary"
value={exportFormat}
onChange={setExportFormat}
items={[
{ value: ExportFormat.Csv, label: 'csv' },
{ value: ExportFormat.Jsonl, label: 'jsonl' },
]}
/>
</div>
<Button
size="md"
variant="solid"
color="primary"
width="100%"
onClick={handleExport}
loading={isExporting}
prefix={<Download size={16} />}
>
Export
</Button>
</div>
</Popover>
</>
);
}

View File

@@ -117,9 +117,16 @@ function AddedFields({
<div className={styles.sectionHeader}>
<span>ADDED FIELDS</span>
{maxFields !== undefined && (
<Typography.Text size="sm" weight="medium" color="muted">
<Typography
variant="paragraph"
size="small"
weight="medium"
color="secondary"
as="span"
textOverflow="wrap"
>
Max Allowed: {maxFields}
</Typography.Text>
</Typography>
)}
</div>
<div className={styles.addedList}>

View File

@@ -32,12 +32,15 @@
}
}
// Full-bleed search bar: only top/bottom borders, square corners.
// Per-side widths cannot be expressed via --input-border-width.
.searchInput {
background-color: var(--l1-background);
height: 40px;
border-radius: 0;
border-left: none;
border-right: none;
--input-border-radius: 0;
[data-slot='input'] {
border-left: none;
border-right: none;
}
}
.section {

View File

@@ -1,5 +1,5 @@
import { useCallback, useMemo, useState } from 'react';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button } from '@signozhq/ui/button';
import { Input } from '@signozhq/ui/input';
import useDebouncedFn from 'hooks/useDebouncedFunction';
@@ -140,10 +140,10 @@ function FieldsSelectorContent({
<X className={styles.closeIcon} size={16} onClick={onClose} />
</div>
<section>
<section className={styles.searchInput}>
<Input
className={styles.searchInput}
type="text"
size="large"
value={inputValue}
placeholder="Search for a field..."
onChange={handleInputChange}

View File

@@ -13,9 +13,9 @@ jest.mock('hooks/querySuggestions/useFieldKeysSuggestion', () => ({
})),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
toast: { success: jest.fn(), error: jest.fn() },
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: { success: jest.fn(), danger: jest.fn() },
}));
// FloatingPanel is a react-rnd/portal shell — presentation only. Render its

View File

@@ -4,9 +4,16 @@ function AnnouncementsModal(): JSX.Element {
return (
<div className="announcements-modal-container">
<div className="announcements-modal-container-header">
<Typography.Text className="announcements-modal-title">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
Announcements
</Typography.Text>
</Typography>
</div>
</div>
);

View File

@@ -1,8 +1,9 @@
import { useCallback, useEffect, useState } from 'react';
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { Button, Input } from 'antd';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Button as SignozButton } from '@signozhq/ui/button';
import { Typography } from '@signozhq/ui/typography';
import logEvent from 'api/common/logEvent';
import { handleContactSupport } from 'container/Integrations/utils';
@@ -39,7 +40,8 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
})
.catch(() => {
console.error(`Failed to submit ${entityName}`);
toast.error(`Failed to submit ${entityName}`, {
toast.danger(`Failed to submit ${entityName}`, {
action: { label: 'Dismiss' },
position: 'top-right',
});
})
@@ -136,14 +138,23 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
Submit
</Button>
<div className="feedback-modal-content-footer-info-text">
<Typography.Text>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
Have a specific issue?{' '}
<Typography.Link
className="contact-support-link"
<SignozButton
variant="link"
color="primary"
size="sm"
onClick={handleContactSupportClick}
>
Contact Support{' '}
</Typography.Link>
</SignozButton>
or{' '}
<a
href="https://signoz.io/docs/introduction/"
@@ -153,7 +164,7 @@ function FeedbackModal({ onClose }: { onClose: () => void }): JSX.Element {
>
Read our docs
</a>
</Typography.Text>
</Typography>
</div>
</div>
</div>

View File

@@ -68,11 +68,6 @@
.absolute-relative-time-toggler-label {
color: var(--l1-foreground);
font-size: 13px;
font-style: normal;
font-weight: 500;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
}

View File

@@ -128,7 +128,16 @@ function HeaderRightSection({
}
prefix={<Noz size={20} />}
>
<Typography.Text>Noz</Typography.Text>
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
Noz
</Typography>
</Button>
</Tooltip>
</span>

View File

@@ -141,9 +141,16 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
{(shareURLWithTime || isRouteToBeSharedWithTime) && (
<>
<div className="absolute-relative-time-toggler-container">
<Typography.Text className="absolute-relative-time-toggler-label">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
Enable absolute time
</Typography.Text>
</Typography>
<div className="absolute-relative-time-toggler">
{!isValidateRelativeTime && (
@@ -172,9 +179,16 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
{extraOption && (
<div className="absolute-relative-time-toggler-container">
<Typography.Text className="absolute-relative-time-toggler-label">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
{extraOption.label}
</Typography.Text>
</Typography>
<div className="absolute-relative-time-toggler">
<Switch
color="primary"
@@ -189,12 +203,26 @@ function ShareURLModal({ extraOption }: ShareURLModalProps): JSX.Element {
<div className="share-link">
<div className="url-share-container">
<div className="url-share-container-header">
<Typography.Text className="url-share-title">
<Typography
variant="paragraph"
size="base"
weight="medium"
color="main"
as="span"
textOverflow="wrap"
>
Share page link
</Typography.Text>
<Typography.Text className="url-share-sub-title">
</Typography>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
Share the current page link with your team member
</Typography.Text>
</Typography>
</div>
<Button

View File

@@ -1,6 +1,6 @@
// Mock dependencies before imports
import { useAppLocation } from 'lib/router/useAppLocation';
import { toast } from '@signozhq/ui/sonner';
import { toast } from '@signozhq/ui/toast';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { logEventMock } from '__tests__/logEventMock';
@@ -13,11 +13,11 @@ jest.mock('lib/router/useAppLocation', () => ({
useAppLocation: jest.fn(),
}));
jest.mock('@signozhq/ui/sonner', () => ({
...jest.requireActual('@signozhq/ui/sonner'),
jest.mock('@signozhq/ui/toast', () => ({
...jest.requireActual('@signozhq/ui/toast'),
toast: {
success: jest.fn(),
error: jest.fn(),
danger: jest.fn(),
},
}));
@@ -49,7 +49,7 @@ describe('FeedbackModal', () => {
isCloudUser: false,
});
mockToast.success.mockClear();
mockToast.error.mockClear();
mockToast.danger.mockClear();
});
it('should render feedback modal with all tabs', () => {

View File

@@ -27,7 +27,7 @@
}
.infra-container-card-text {
font-size: var(--font-size-sm);
font-size: var(--paragraph-base-400-font-size);
color: var(--l2-foreground);
line-height: 20px;
letter-spacing: -0.07px;

View File

@@ -27,7 +27,7 @@
}
.infra-container-card-text {
font-size: var(--font-size-sm);
font-size: var(--paragraph-base-400-font-size);
color: var(--l2-foreground);
line-height: 20px;
letter-spacing: -0.07px;

View File

@@ -52,9 +52,16 @@ export default function WaitlistFragment({
return (
<div className="wait-list-container">
<Typography.Text className="wait-list-text">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{t('waitlist_message')}
</Typography.Text>
</Typography>
<Button
className="periscope-btn join-waitlist-btn"

View File

@@ -4,6 +4,9 @@
border-radius: 2px 0px 0px 2px;
// The label chip and the Input share the --input-with-label-* theming
// contract (set by e.g. FormAlertRules, WidgetFullView) so the joined
// group recolors as one.
.label {
// Typography.Text takes its display from this token; at its `inline` default
// the label blockifies as a flex item and the text rides the top of the row.
@@ -31,66 +34,41 @@
display: flex;
justify-content: flex-start;
align-items: center;
font-weight: var(--font-weight-light);
}
.input {
flex: 1;
min-width: 150px;
font-family: 'Space Mono', monospace !important;
--input-border-radius: 0px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
color: var(--input-with-label-color, var(--l2-foreground));
font-size: 12px !important;
line-height: 25px;
position: relative;
// Square corners: the input sits mid-group between label and close button.
--input-border-radius: 0px;
--input-height: 36px;
--input-border: var(--input-with-label-border-color, var(--l2-border));
--input-background: var(
--input-with-label-background-color,
var(--l2-background)
);
--input-foreground: var(--input-with-label-color, var(--l2-foreground));
&:hover,
&:focus {
z-index: 1;
}
.ant-select-selector {
position: relative;
border-radius: inherit;
}
.ant-select:hover .ant-select-selector,
.ant-select-focused .ant-select-selector {
&:focus-within {
z-index: 1;
}
&.input__has-label-after {
margin-left: -1px;
.ant-select-selector {
margin-left: -1px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
&.input__has-close-button {
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
~ .close-btn {
margin-left: -1px;
}
}
&::placeholder {
color: var(--input-with-label-color, var(--l3-foreground)) !important;
font-size: 12px !important;
}
&[type='number']::-webkit-inner-spin-button,
&[type='number']::-webkit-outer-spin-button {
// The Input has no token for the native number spinners.
input[type='number']::-webkit-inner-spin-button,
input[type='number']::-webkit-outer-spin-button {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
@@ -114,16 +92,7 @@
&.labelAfter {
.input {
border-radius: 2px;
border: 1px solid var(--input-with-label-border-color, var(--l2-border));
background: var(--input-with-label-background-color, var(--l2-background));
border-top-right-radius: 0px;
border-bottom-right-radius: 0px;
.ant-select-selector {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
--input-border-radius: 2px 0 0 2px;
}
.label {

View File

@@ -49,21 +49,51 @@ function InputWithLabel({
labelAfter,
})}
>
{!labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
<Input
{!labelAfter && (
<div className="label">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="p"
textOverflow="ellipsis"
>
{label}
</Typography>
</div>
)}
<div
className={cx('input', {
'input__has-label-after': !labelAfter,
'input__has-close-button': !!onClose,
})}
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
name={label.toLowerCase()}
data-testid={`input-${label}`}
/>
{labelAfter && <Typography.Text className="label">{label}</Typography.Text>}
>
<Input
placeholder={placeholder}
type={type}
value={inputValue}
onChange={handleChange}
disabled={disabled}
disabledTooltip={undefined}
name={label.toLowerCase()}
testId={`input-${label}`}
/>
</div>
{labelAfter && (
<div className="label">
<Typography
variant="paragraph"
size="small"
weight="regular"
color="secondary"
as="p"
textOverflow="ellipsis"
>
{label}
</Typography>
</div>
)}
{onClose && (
<Button
className="periscope-btn ghost close-btn"

View File

@@ -55,26 +55,12 @@
flex: 1 1 0;
min-width: 0;
// Matches the antd RolesSelect in the same row, which is forced to the
// same field background below.
--input-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-hover-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-focus-background: var(
--invite-members-field-background,
var(--l2-background)
);
--input-disabled-background: var(
--invite-members-field-background,
var(--l2-background)
);
input::placeholder {
color: var(--l3-foreground);
}
}
.cellRole {

View File

@@ -1,4 +1,4 @@
import { CircleAlert, Plus, Trash2 } from '@signozhq/icons';
import { CircleAlert, Plus, SolidCheckCircle2, Trash2 } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { Callout } from '@signozhq/ui/callout';
import { Input } from '@signozhq/ui/input';
@@ -68,20 +68,30 @@ function InviteMembers({
<div className={styles.table}>
{showHeader && (
<div className={styles.header}>
<Typography.Text
size="base"
weight="semibold"
className={styles.headerCellEmail}
>
Email address
</Typography.Text>
<Typography.Text
size="base"
weight="semibold"
className={styles.headerCellRole}
>
Roles
</Typography.Text>
<span className={styles.headerCellEmail}>
<Typography
variant="paragraph"
size="base"
weight="bold"
color="inherit"
as="span"
textOverflow="wrap"
>
Email address
</Typography>
</span>
<span className={styles.headerCellRole}>
<Typography
variant="paragraph"
size="base"
weight="bold"
color="inherit"
as="span"
textOverflow="wrap"
>
Roles
</Typography>
</span>
<div className={styles.headerCellAction} />
</div>
)}
@@ -97,12 +107,19 @@ function InviteMembers({
onChange={(e): void => updateEmail(row.id, e.target.value)}
name={`invite-email-${row.id}`}
autoComplete="email"
data-testid={`invite-email-${row.id}`}
testId={`invite-email-${row.id}`}
/>
{emailValidity[row.id] === false && row.email.trim() !== '' && (
<Typography.Text size="small" className={styles.errorText}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="danger"
as="span"
textOverflow="wrap"
>
Invalid email address
</Typography.Text>
</Typography>
)}
</div>
@@ -152,61 +169,90 @@ function InviteMembers({
</div>
{hasValidationErrors && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-validation-error"
>
{getValidationErrorMessage()}
</Callout>
</div>
)}
{hasResults && hasFailures && (
<Callout
type="error"
size="small"
showIcon
icon={<CircleAlert size={12} />}
className={styles.callout}
data-testid="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully.
</Typography.Text>
)}
<Typography.Text size="small">
{failedResults.length} invite(s) failed:
</Typography.Text>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography.Text size="small">
{result.email}: {result.error}
</Typography.Text>
</li>
))}
</ul>
</div>
</Callout>
<div className={styles.callout}>
<Callout
color="danger"
size="sm"
icon={<CircleAlert />}
testId="invite-api-error"
>
<div className={styles.results}>
{hasSuccesses && (
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{successResults.length} invite(s) sent successfully.
</Typography>
)}
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{failedResults.length} invite(s) failed:
</Typography>
<ul className={styles.resultsList}>
{failedResults.map((result) => (
<li key={result.email}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{result.email}: {result.error}
</Typography>
</li>
))}
</ul>
</div>
</Callout>
</div>
)}
{hasResults && !hasFailures && hasSuccesses && (
<Callout
type="success"
size="small"
showIcon
className={styles.callout}
data-testid="invite-success"
>
<Typography.Text size="small">
{successResults.length} invite(s) sent successfully!
</Typography.Text>
</Callout>
<div className={styles.callout}>
<Callout
color="success"
size="sm"
icon={<SolidCheckCircle2 />}
testId="invite-success"
>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="inherit"
as="span"
textOverflow="wrap"
>
{successResults.length} invite(s) sent successfully!
</Typography>
</Callout>
</div>
)}
{renderFooter?.({

View File

@@ -25,10 +25,6 @@
}
.filtersLabel {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-medium);
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--l2-foreground);
margin-right: 2px;
}
@@ -107,6 +103,6 @@
}
.error {
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
color: var(--danger-foreground);
}

View File

@@ -116,15 +116,31 @@ function FilterZone({
{rightSlot}
</div>
{error && (
<Typography.Text
className={styles.error}
<Typography
variant="paragraph"
size="small"
weight="regular"
color="danger"
as="span"
textOverflow="wrap"
testId={`${testIdPrefix}-query-error`}
>
{error}
</Typography.Text>
</Typography>
)}
<div className={styles.filtersRow}>
<Typography.Text className={styles.filtersLabel}>Filters</Typography.Text>
<Typography
variant="uppercase"
size="small"
weight="medium"
color="secondary"
as="span"
display="inline-block"
marginRight={1}
textOverflow="wrap"
>
Filters
</Typography>
{wrapControl(
<div
className={styles.filterControls}

View File

@@ -25,7 +25,7 @@
:global(.cm-dsl-logic) {
color: var(--bg-sakura-400);
font-weight: var(--font-weight-medium);
font-weight: var(--label-base-500-font-weight);
}
:global(.cm-dsl-string) {
@@ -59,7 +59,7 @@
content: 'SUGGESTIONS';
padding: 12px 14px 8px;
font-size: 11px;
font-weight: var(--font-weight-medium);
font-weight: var(--uppercase-small-500-font-weight);
line-height: 18px;
letter-spacing: 0.88px;
text-transform: uppercase;
@@ -71,7 +71,7 @@
padding: 10px 14px;
border-top: 1px solid var(--l1-border);
background: var(--l2-background);
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
color: var(--l2-foreground);
}
@@ -131,7 +131,7 @@
border-radius: 50px;
background: color-mix(in srgb, var(--l1-foreground) 10%, transparent);
font-style: normal;
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
color: var(--l2-foreground);
text-transform: capitalize;
}

View File

@@ -29,15 +29,15 @@
background: var(--l2-background);
border: 1px solid var(--l2-border);
color: var(--l2-foreground);
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
font-variant-numeric: tabular-nums;
}
.title {
color: var(--l1-foreground);
font-size: var(--font-size-lg);
font-size: var(--label-large-500-font-size);
font-style: normal;
font-weight: var(--font-weight-medium);
font-weight: var(--label-large-500-font-weight);
line-height: 28px;
letter-spacing: -0.09px;
overflow: hidden;

View File

@@ -22,12 +22,18 @@ function ListCommandHeader({
return (
<div className={styles.commandHeader}>
<div className={styles.headingBlock}>
<Typography.Title
className={styles.title}
<Typography
variant="label"
size="large"
weight="medium"
color="main"
as="h1"
marginY={4}
textOverflow="ellipsis"
testId={testIdPrefix && `${testIdPrefix}-list-heading`}
>
{label}
</Typography.Title>
</Typography>
{isModified && <span className={styles.dirtyDot} title="Unsaved changes" />}
<span className={styles.countPill}>{count}</span>
</div>

View File

@@ -10,6 +10,6 @@
}
.count {
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
color: var(--l3-foreground);
}

View File

@@ -36,9 +36,16 @@ function ListStatusBar({
>
{collapsed ? 'Expand' : 'Collapse'}
</Button>
<Typography.Text className={styles.count}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
{count} of {total} {noun}
</Typography.Text>
</Typography>
</div>
);
}

View File

@@ -24,8 +24,8 @@
.headerTitle {
margin: 0;
font-size: var(--font-size-base);
font-weight: var(--font-weight-semibold);
font-size: var(--label-medium-600-font-size);
font-weight: var(--label-medium-600-font-weight);
letter-spacing: -0.01em;
color: var(--l1-foreground);
text-align: left;
@@ -38,7 +38,7 @@
.searchEmpty {
padding: 12px;
color: var(--l3-foreground);
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
text-align: center;
}
@@ -54,8 +54,8 @@
align-items: center;
gap: 6px;
padding: 16px 8px 8px;
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
font-size: var(--uppercase-small-600-font-size);
font-weight: var(--uppercase-small-600-font-weight);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--l3-foreground);
@@ -68,9 +68,6 @@
}
.groupCount {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-normal);
letter-spacing: 0;
color: var(--l3-foreground);
background: var(--l2-background);
border-radius: 10px;
@@ -79,7 +76,6 @@
.deleteName {
color: var(--danger-background);
font-weight: var(--font-weight-medium);
}
.row {
@@ -161,7 +157,7 @@
border: 1px dashed var(--l2-border);
border-radius: 8px;
color: var(--l3-foreground);
font-size: var(--font-size-xs);
font-size: var(--label-small-400-font-size);
line-height: 1.5;
text-align: center;
}
@@ -178,8 +174,8 @@
}
.dirtyTitle {
font-size: var(--font-size-xs);
font-weight: var(--font-weight-semibold);
font-size: var(--uppercase-small-600-font-size);
font-weight: var(--uppercase-small-600-font-weight);
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--l2-foreground);

View File

@@ -72,7 +72,6 @@ function ViewsRail({
onViewRenamed,
onViewDeleted,
}: Props): JSX.Element {
const [saveOpen, setSaveOpen] = useState(false);
const [query, setQuery] = useState('');
const { contextHolder, confirmDelete } = useDeleteConfirm();
@@ -94,11 +93,28 @@ function ViewsRail({
(id: string, label: string): void => {
confirmDelete({
title: (
<Typography.Title level={5}>
<Typography
variant="paragraph"
size="medium"
weight="bold"
color="inherit"
as="h5"
marginY={4}
textOverflow="wrap"
>
Delete the{' '}
<Typography.Text className={styles.deleteName}>{label}</Typography.Text>{' '}
<Typography
variant="paragraph"
size="base"
weight="medium"
color="danger"
as="span"
textOverflow="wrap"
>
{label}
</Typography>{' '}
view?
</Typography.Title>
</Typography>
),
content: deleteDescription,
// Return the delete promise so the modal's Delete button stays loading
@@ -149,14 +165,12 @@ function ViewsRail({
<div className={styles.header}>
<h4 className={styles.headerTitle}>Views</h4>
<ViewNamePopover
open={saveOpen}
onOpenChange={setSaveOpen}
onSubmit={handleSaveAsView}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={
trigger={(open): JSX.Element => (
<Button
aria-label="Save current filters as a view"
variant="ghost"
@@ -164,10 +178,11 @@ function ViewsRail({
size="sm"
icon
testId={`${testIdPrefix}-view-save-trigger`}
onClick={(e): void => open(e.currentTarget)}
>
<Plus size={14} />
</Button>
}
)}
/>
</div>
@@ -204,9 +219,18 @@ function ViewsRail({
<>
<div className={cx(styles.groupLabel, styles.groupLabelSpaced)}>
{customSectionLabel}
<Typography.Text className={styles.groupCount}>
{customViews.length}
</Typography.Text>
<span className={styles.groupCount}>
<Typography
variant="paragraph"
size="small"
weight="regular"
color="tertiary"
as="span"
textOverflow="wrap"
>
{customViews.length}
</Typography>
</span>
</div>
{customViewsLoading ? (
<div className={styles.empty}>Loading views…</div>
@@ -245,8 +269,9 @@ function ViewsRail({
<ViewsRailDirtyPanel
isCustomActive={isCustomActive}
testIdPrefix={testIdPrefix}
saveTestIdPrefix={saveTestIdPrefix}
onSaveChanges={handleSaveViewChanges}
onSaveAs={(): void => setSaveOpen(true)}
onSaveAs={handleSaveAsView}
onReset={onReset}
/>
)}

View File

@@ -1,20 +1,23 @@
import { Button } from '@signozhq/ui/button';
import { Plus } from '@signozhq/icons';
import cx from 'classnames';
import ViewNamePopover from 'components/ViewNamePopover/ViewNamePopover';
import styles from './ViewsRail.module.scss';
interface Props {
isCustomActive: boolean;
testIdPrefix: string;
saveTestIdPrefix: string;
onSaveChanges: () => void;
onSaveAs: () => void;
onSaveAs: (name: string) => void;
onReset: () => void;
}
function ViewsRailDirtyPanel({
isCustomActive,
testIdPrefix,
saveTestIdPrefix,
onSaveChanges,
onSaveAs,
onReset,
@@ -33,15 +36,24 @@ function ViewsRailDirtyPanel({
>
Save
</Button>
<Button
variant="outlined"
color="secondary"
size="sm"
onClick={onSaveAs}
testId={`${testIdPrefix}-view-save-as`}
>
Save as…
</Button>
<ViewNamePopover
onSubmit={onSaveAs}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={(open): JSX.Element => (
<Button
variant="outlined"
color="secondary"
size="sm"
onClick={(e): void => open(e.currentTarget)}
testId={`${testIdPrefix}-view-save-as`}
>
Save as…
</Button>
)}
/>
<Button
variant="ghost"
color="secondary"
@@ -60,16 +72,25 @@ function ViewsRailDirtyPanel({
<div className={cx(styles.dirtyPanel, styles.dirtyPanelDefault)}>
<div className={styles.dirtyTitle}>Filters active</div>
<div className={styles.dirtyActions}>
<Button
variant="solid"
color="primary"
size="sm"
prefix={<Plus size={12} />}
onClick={onSaveAs}
testId={`${testIdPrefix}-view-save-as-new`}
>
Save as new view
</Button>
<ViewNamePopover
onSubmit={onSaveAs}
title="Save as view"
confirmLabel="Save view"
testIdPrefix={saveTestIdPrefix}
tooltip="Save current filters as a view"
trigger={(open): JSX.Element => (
<Button
variant="solid"
color="primary"
size="sm"
prefix={<Plus size={12} />}
onClick={(e): void => open(e.currentTarget)}
testId={`${testIdPrefix}-view-save-as-new`}
>
Save as new view
</Button>
)}
/>
<Button
variant="ghost"
color="secondary"

View File

@@ -1,4 +1,3 @@
import { useState } from 'react';
import { Button } from '@signozhq/ui/button';
import { Tooltip } from '@signozhq/ui/tooltip';
import { PenLine, Trash2 } from '@signozhq/icons';
@@ -35,8 +34,6 @@ function ViewsRailItem({
onRename,
onDelete,
}: Props): JSX.Element {
const [renaming, setRenaming] = useState(false);
return (
<div className={cx(styles.row, { [styles.rowActive]: active })}>
<Button
@@ -58,15 +55,13 @@ function ViewsRailItem({
{onRename && onDelete && (
<div className={styles.itemActions}>
<ViewNamePopover
open={renaming}
onOpenChange={setRenaming}
onSubmit={(name): void => onRename(id, name)}
title="Rename view"
confirmLabel="Rename"
initialName={label}
testIdPrefix={renameTestIdPrefix}
tooltip="Rename view"
trigger={
trigger={(open): JSX.Element => (
<Button
variant="ghost"
color="secondary"
@@ -74,11 +69,14 @@ function ViewsRailItem({
icon
aria-label="Rename view"
testId={`${testIdPrefix}-view-rename-${id}`}
onClick={(e): void => e.stopPropagation()}
onClick={(e): void => {
e.stopPropagation();
open(e.currentTarget);
}}
>
<PenLine size={12} />
</Button>
}
)}
/>
<Tooltip title="Delete view">
<Button

View File

@@ -63,7 +63,7 @@ describe('ViewsRail', () => {
it('filters views by name and says when none match', async () => {
const user = userEvent.setup({ pointerEventsCheck: 0 });
renderRail();
const search = screen.getByTestId(`${PREFIX}-view-search`);
const search = screen.getByTestId(`${PREFIX}-view-search-field`);
await user.type(search, 'prod');

View File

@@ -1,14 +1,13 @@
import { DrawerProps } from 'antd';
import { MouseEventHandler } from 'react';
import { ChangeViewFunctionType } from 'hooks/useHandleExplorerTabChange';
import { IField } from 'types/api/logs/fields';
import { ILog } from 'types/api/logs/log';
import { VIEWS } from './constants';
import { MouseEventHandler } from 'react';
export type LogDetailProps = {
log: ILog | null;
selectedTab: VIEWS;
selectedTab?: VIEWS;
handleChangeSelectedView?: ChangeViewFunctionType;
isListViewPanel?: boolean;
listViewPanelSelectedFields?: IField[] | null;
@@ -16,10 +15,19 @@ export type LogDetailProps = {
onNavigateLog?: (log: ILog) => void;
onScrollToLog?: (logId: string) => void;
handleOpenInExplorer?: MouseEventHandler;
getContainer?: DrawerProps['getContainer'];
onApplyLogFilter?: (expression: string) => void;
} & Pick<DrawerProps, 'onClose'>;
export type LogDetailInnerProps = LogDetailProps & {
log: NonNullable<LogDetailProps['log']>;
onClose: () => void;
};
export type LogDetailBodyProps = Pick<
LogDetailProps,
| 'selectedTab'
| 'isListViewPanel'
| 'handleChangeSelectedView'
| 'onApplyLogFilter'
| 'onClose'
> & {
log: ILog;
onPrev: () => void;
onNext: () => void;
};

View File

@@ -0,0 +1,364 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button } from '@signozhq/ui/button';
import { ToggleGroup } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
import { Tooltip } from 'antd';
import { SavedviewtypesSourceDTO } from 'api/generated/services/sigNoz.schemas';
import LogStateIndicator from 'components/Logs/LogStateIndicator/LogStateIndicator';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
import QuerySearch from 'components/QueryBuilderV2/QueryV2/QuerySearch/QuerySearch';
import { convertExpressionToFilters } from 'components/QueryBuilderV2/utils';
import { LOCALSTORAGE } from 'constants/localStorage';
import ContextView from 'container/LogDetailedView/ContextView/ContextView';
import InfraMetrics from 'container/LogDetailedView/InfraMetrics/InfraMetrics';
import Overview from 'container/LogDetailedView/Overview';
import {
getSanitizedLogBody,
removeEscapeCharacters,
} from 'container/LogDetailedView/utils';
import useInitialQuery from 'container/LogsExplorerContext/useInitialQuery';
import { useOptionsMenu } from 'container/OptionsMenu';
import { FontSize } from 'container/OptionsMenu/types';
import { useArrowKeyNavigation } from 'container/TracesExplorer/SpanDrawer/useArrowKeyNavigation';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { getBodyMessage } from 'lib/logs/logBody';
import { cloneDeep } from 'lodash-es';
import {
ArrowDown,
ArrowUp,
Filter,
Histogram,
Table,
TextSelect,
} from '@signozhq/icons';
import { Query, TagFilter } from 'types/api/queryBuilder/queryBuilderData';
import { DataSource, StringOperators } from 'types/common/queryBuilder';
import { RESOURCE_KEYS, VIEW_TYPES, VIEWS } from './constants';
import { LogDetailBodyProps } from './LogDetail.interfaces';
import LogHighlights from './LogHighlights/LogHighlights';
/* eslint-disable-next-line sonarjs/cognitive-complexity */
function LogDetailBody({
log,
onClose,
selectedTab = VIEW_TYPES.OVERVIEW,
isListViewPanel = false,
handleChangeSelectedView,
onApplyLogFilter,
onPrev,
onNext,
}: LogDetailBodyProps): JSX.Element {
const initialContextQuery = useInitialQuery(log);
const [contextQuery, setContextQuery] = useState<Query | undefined>(
initialContextQuery,
);
const [selectedView, setSelectedView] = useState<VIEWS>(selectedTab);
const [isFilterVisible, setIsFilterVisible] = useState<boolean>(false);
const [filters, setFilters] = useState<TagFilter | null>(null);
const [isEdit, setIsEdit] = useState<boolean>(false);
const { stagedQuery } = useQueryBuilder();
// Handle clicks outside to close drawer, except on explicitly ignored regions
useEffect(() => {
const handleClickOutside = (e: MouseEvent): void => {
const target = e.target as HTMLElement;
// Don't close if clicking on drawer content, overlays, or portal elements
if (
target.closest('[data-log-detail-ignore="true"]') ||
target.closest('.cm-tooltip-autocomplete') ||
target.closest('.drawer-popover') ||
target.closest('.query-status-popover') ||
target.closest('[data-radix-popper-content-wrapper]') ||
target.closest('[data-base-ui-portal]')
) {
return;
}
// Close the drawer for any other outside click
onClose();
};
document.addEventListener('mousedown', handleClickOutside);
return (): void => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [onClose]);
// Only listen in the OVERVIEW tab so we don't hijack arrow keys from the
// JSON editor / context view.
useArrowKeyNavigation({
enabled: selectedView === VIEW_TYPES.OVERVIEW,
onPrev,
onNext,
});
const listQuery = useMemo(() => {
if (!stagedQuery || stagedQuery.builder.queryData.length < 1) {
return null;
}
return stagedQuery.builder.queryData.find((item) => !item.disabled) || null;
}, [stagedQuery]);
const { options } = useOptionsMenu({
storageKey: LOCALSTORAGE.LOGS_LIST_OPTIONS,
sourcePage: SavedviewtypesSourceDTO.logs,
aggregateOperator: listQuery?.aggregateOperator || StringOperators.NOOP,
});
const handleModeChange = (value: string): void => {
setSelectedView(value as VIEWS);
setIsEdit(false);
setIsFilterVisible(false);
};
const handleFilterVisible = (): void => {
setIsFilterVisible(!isFilterVisible);
setIsEdit(!isEdit);
};
const logBody = useMemo(() => getBodyMessage(log?.body), [log?.body]);
const htmlBody = useMemo(
() => ({
__html: getSanitizedLogBody(logBody || '', { shouldEscapeHtml: true }),
}),
[logBody],
);
const handleQueryExpressionChange = useCallback(
(value: string, queryIndex: number) => {
// update the query at the given index
setContextQuery((prev) => {
if (!prev) {
return prev;
}
return {
...prev,
builder: {
...prev.builder,
queryData: prev.builder.queryData.map((query, idx) =>
idx === queryIndex
? {
...query,
filter: {
...query.filter,
expression: value,
},
}
: query,
),
},
};
});
},
[],
);
const handleRunQuery = (expression: string): void => {
let updatedContextQuery = cloneDeep(contextQuery);
if (!updatedContextQuery || !updatedContextQuery.builder) {
return;
}
const newFilters: TagFilter = {
items: expression ? convertExpressionToFilters(expression) : [],
op: 'AND',
};
updatedContextQuery = {
...updatedContextQuery,
builder: {
...updatedContextQuery?.builder,
queryData: updatedContextQuery?.builder.queryData.map((queryData) => ({
...queryData,
filter: {
...queryData.filter,
expression,
},
filters: {
...queryData.filters,
...newFilters,
op: queryData.filters?.op ?? 'AND',
},
})),
},
};
setContextQuery(updatedContextQuery);
if (newFilters) {
setFilters(newFilters);
}
};
return (
<OverlayScrollbar style={{ height: '100%' }}>
<div className="log-detail-drawer__content">
<div className="log-detail-drawer__log">
<LogStateIndicator
severityText={log.severity_text}
severityNumber={log.severity_number}
fontSize={options?.fontSize ?? FontSize.MEDIUM}
/>
<Tooltip
title={removeEscapeCharacters(logBody)}
placement="left"
mouseLeaveDelay={0}
>
<div
className="log-body"
data-testid="log-detail-body"
dangerouslySetInnerHTML={htmlBody}
/>
</Tooltip>
<div className="log-overflow-shadow">&nbsp;</div>
</div>
<LogHighlights log={log} />
<div className="log-detail-drawer__section-divider" />
<div className="tabs-and-search">
<ToggleGroup
variant="outlined"
color="secondary"
size="sm"
type="single"
testId="log-detail-views-tabs"
onChange={handleModeChange}
value={selectedView}
items={[
{
value: VIEW_TYPES.OVERVIEW,
label: (
<div className="view-title">
<Table size={14} />
Overview
</div>
),
},
{
value: VIEW_TYPES.CONTEXT,
label: (
<div className="view-title">
<TextSelect size={14} />
Context
</div>
),
},
{
value: VIEW_TYPES.INFRAMETRICS,
label: (
<div className="view-title">
<Histogram size="md" />
Metrics
</div>
),
},
]}
/>
<div className="log-detail-drawer__actions">
{selectedView === VIEW_TYPES.CONTEXT && (
<Tooltip
title="Show Filters"
placement="topLeft"
aria-label="Show Filters"
mouseLeaveDelay={0}
>
<Button
variant="link"
color="secondary"
size="sm"
icon
aria-label="Show Filters"
onClick={handleFilterVisible}
>
<Filter size="lg" />
</Button>
</Tooltip>
)}
</div>
</div>
{isFilterVisible && contextQuery?.builder.queryData[0] && (
<div className="log-detail-drawer-query-container">
<QuerySearch
onChange={(value): void => handleQueryExpressionChange(value, 0)}
dataSource={DataSource.LOGS}
queryData={contextQuery?.builder.queryData[0]}
onRun={handleRunQuery}
/>
</div>
)}
{selectedView === VIEW_TYPES.OVERVIEW && (
<Overview
logData={log}
isListViewPanel={isListViewPanel}
handleChangeSelectedView={handleChangeSelectedView}
onApplyLogFilter={onApplyLogFilter}
/>
)}
{selectedView === VIEW_TYPES.CONTEXT && (
<ContextView
log={log}
filters={filters}
contextQuery={contextQuery}
isEdit={isEdit}
/>
)}
{selectedView === VIEW_TYPES.INFRAMETRICS && (
<InfraMetrics
clusterName={log.resources_string?.[RESOURCE_KEYS.CLUSTER_NAME] || ''}
podName={log.resources_string?.[RESOURCE_KEYS.POD_NAME] || ''}
nodeName={log.resources_string?.[RESOURCE_KEYS.NODE_NAME] || ''}
hostName={log.resources_string?.[RESOURCE_KEYS.HOST_NAME] || ''}
timestamp={log.timestamp.toString()}
dataSource={DataSource.LOGS}
/>
)}
{selectedView === VIEW_TYPES.OVERVIEW && (
<div className="log-detail-drawer__footer-hint">
<div className="log-detail-drawer__footer-hint-content">
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
Use
</Typography>
<ArrowUp size={14} className="log-detail-drawer__footer-hint-icon" />
<span>/</span>
<ArrowDown size={14} className="log-detail-drawer__footer-hint-icon" />
<Typography
variant="paragraph"
size="base"
weight="regular"
color="secondary"
as="span"
textOverflow="wrap"
>
to view previous/next log
</Typography>
</div>
</div>
)}
</div>
</OverlayScrollbar>
);
}
export default LogDetailBody;

View File

@@ -1,79 +1,17 @@
.log-detail-drawer {
border-left: 1px solid var(--l1-border);
background: var(--l2-background);
box-shadow: -4px 10px 16px 2px rgba(0, 0, 0, 0.2);
[data-testid='log-detail-drawer'] {
--drawer-extra-wide-max-inline-size: 60vw;
--drawer-body-padding: 0 var(--spacing-8);
// OverlayScrollbar owns the scroll. A native scrollbar here changes the
// body width when it toggles and shifts the header grid.
--drawer-body-overflow-y: hidden;
}
.log-detail-drawer__title {
display: flex;
justify-content: space-between;
align-items: center;
.log-detail-drawer-query-container {
margin-bottom: 16px;
}
.log-detail-drawer__title-left {
display: flex;
align-items: center;
gap: 8px;
}
.log-detail-drawer__title-right {
display: flex;
align-items: center;
.ant-btn {
display: flex;
align-items: center;
}
}
}
.ant-drawer-header {
padding: 8px 16px;
border-bottom: none;
align-items: stretch;
border-bottom: 1px solid var(--l1-border);
background: var(--l2-background);
}
.ant-drawer-close {
margin-inline-end: 0px;
}
.ant-drawer-body {
display: flex;
flex-direction: column;
padding: 0 16px;
// OverlayScrollbar owns the scroll. A native scrollbar here changes the
// body width when it toggles and shifts the header grid.
overflow: hidden;
}
.title {
color: var(--l2-foreground);
font-family: Inter;
font-size: var(--font-size-sm);
font-style: normal;
font-weight: var(--font-weight-normal);
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(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
}
&-query-container {
margin-bottom: 16px;
}
.log-detail-drawer__content {
min-height: 100%;
}
.log-detail-drawer__content {
min-height: 100%;
.log-detail-drawer__log {
width: 100%;
@@ -85,10 +23,9 @@
.log-body {
font-family: 'SF Mono';
font-family: 'Geist Mono';
font-family: var(--code-base-400-font-family);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-normal);
font-size: var(--code-base-400-font-size);
line-height: 18px;
letter-spacing: -0.07px;
white-space: nowrap;
@@ -118,7 +55,11 @@
}
.log-overflow-shadow {
background: linear-gradient(270deg, var(--card) 10.4%, transparent 100%);
background: linear-gradient(
270deg,
var(--drawer-background) 10.4%,
transparent 100%
);
width: 196px;
position: absolute;
@@ -145,16 +86,6 @@
align-items: center;
margin: 16px 0;
.action-btn {
border-radius: 2px;
border: 1px solid var(--l1-border);
background: var(--l3-background);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
}
.log-detail-drawer__actions {
display: flex;
gap: 4px;
@@ -166,23 +97,9 @@
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);
font-size: var(--label-small-400-font-size);
}
.search-input {
margin-top: var(--margin-2);
border: 1px solid var(--l1-border);
height: 46px;
padding: var(--padding-1) var(--padding-2);
box-shadow: none;
border-radius: 0;
}
.ant-drawer-close {
padding: 0px;
}
.log-detail-drawer__footer-hint {
position: sticky;
bottom: 0;
@@ -191,7 +108,7 @@
padding: 8px 16px;
text-align: left;
color: var(--l3-foreground);
background: var(--l2-background);
background: var(--drawer-background);
z-index: 10;
.log-detail-drawer__footer-hint-content {
@@ -205,18 +122,5 @@
vertical-align: middle;
color: var(--l3-foreground);
}
.log-detail-drawer__footer-hint-text {
font-size: 13px;
margin: 0;
}
}
.log-arrows {
display: flex;
box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1);
border-radius: 6px;
padding: 2px 6px;
align-items: center;
margin-left: 8px;
}
}

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