Compare commits

..

37 Commits

Author SHA1 Message Date
Naman Verma
c30ba5414c chore: deprecate v1 dashboard APIs with proper reference to v2 APIs 2026-07-25 16:53:01 +05:30
Naman Verma
51819d6337 chore: add migration script from current to perses dashboard (#11712)
* chore: add migration script from current to perses dashboard

* fix: move WrapInV5Envelope to types package

* fix: generate internal name from title

* fix: match with lower case signal for variables

* fix: sanitize tags instead of throwing error

* fix: extract row and widget positions to build expanded sections

* chore: add a catch all panic check to log migration error

* chore: catch typecast errors

* fix: remove nil nil return

* test: fix ut to check for v2 internal name

* chore: remove spec md file

* chore: separate error type for migration

* feat: note down all errors in migration

* fix: allow zero value for threshold values

* fix: ignore react placeholders in layout

* fix: note error in query parsing

* fix: read threshold value properly during migration

* fix: adjust to schema changes in variables

* test: fix unit tests

* fix: catch error from decodeTelemetryFields

* fix: make threshold value non-nullable

* fix: handle list of widget IDs in panel maps

* fix: add better error note down for failed conversions

* fix: normalize having array from v4 schema

* fix: remove metric aggregation from non metric queries (malformed json)

* fix: normalize telemetry field keys in query

* chore: add temporary doc for other malformation handlers needed

* fix: add more malformed query handling

* fix: add more malformation handling

* chore: reuse transition package (adds dependency, need to see what to do)

* fix: fix go lint issue

* revert: remove md file

* fix: make none the default fill mode during migration

* fix: add nil check on limit value when normalizing page size

* fix: validate dashboard spec after migration

* fix: remove unused method

* fix: match shape safe version to existing version

* chore: improve variable names

* chore: better comments and names in layout migrator

* chore: add explanation comment for continuing on empty id widget

* fix: ditch dynamic vars with no attr, rearrange overlapping layouts

* fix: add default aggregation count() if none is present in logs/traces

* fix: add empty list tolerance to v1 variables

* fix: add fixes based on backup migration testing

* chore: skip over unreferenced widgets

* chore: remove widgets that have a single query that has no metric name

* chore: assign missing formula names like ui does

* chore: assign query names to queries if missing

* chore: drop legacy filter fields that ui also ignores

* chore: make migration drop widgets that have no aggregations for metrics

* chore: normalize group by

* chore: remove expressions from metric aggregations

* chore: add more error notes

* chore: replace em dash with hyphen in widget id

* chore: add more panel id sanitization

* chore: replace faulty order by keys with aggregation expression

* chore: fill valid span gaps

* chore: handle misformed threshold operators

* chore: remove repeat occurences of widgets in layouts

* chore: remove limits that cross max limit

* chore: restore function args malformed by v4->v5 migration

* chore: remove invalid functions

* chore: skip variables in wrong type

* chore: add wdiget id type check in layout migration

* chore: add a.count to malformed order by keys list

* chore: remove bad aggregations that accompany valid ones

* chore: drop trace operators with no expression

* chore: drop invalid formulas

* chore: skip unknown panel kind

* chore: drop widgets with >1 and all aggregations as malformed

* chore: skip empty type variables

* chore: pass threshold format raw without typecasting

* chore: add handling for trace operators around wrapinv5env

* chore: handle no string value for variable sort

* test: fix span gaps test

* chore: undo schema change in threshold value

* chore: final set of error fixes/hacks

* fix: fix build errors post merge conflicts

* test: remove test for floating pt value

* test: use appendf instead of sprintf

* feat: add scaffolding to migrate dashboards

* fix: fill in ref path when building grid layout

* feat: handle v1 payloads in v2 create api

* fix: return empty objects instead of null if no variables/panels/layouts are there

* test: fix unit test for empty layouts

* fix: add empty links array in migration

* test: add unit test to show v5  takes precedence over v4

* fix: change EXISTS to exists so that migrateCommon handles it properly

* test: add unit test for count with attributes

* fix: run order by handling after default aggregation handling

* fix: restore formula fields that wrapinv5envelope removes

* fix: keep thresholds that do not have an optional label

* fix: force allowAll to be false if allowMultiple is false

* fix: migrate name column even if data column migration fails

* fix: use slog for logging

* refactor(dashboard): drop ConvertAllV1ToV2 module scaffolding from base

Remove the temporary v1→v2 bulk-conversion module method (ConvertAllV1ToV2 +
migrateOneV1ToV2), its result types (V1ToV2MigrationResult / V1ToV2MigrationItem),
the Module interface entry, and the EE delegation. This scaffolding lives on the
migration feature branches, not the base branch that merges to main.

V1Name and store.UpdateName are kept (small helpers; UpdateName is on the shared
Store interface).

* fix: add image validation and base64 to asset conversion

---------

Co-authored-by: Srikanth Chekuri <srikanth.chekuri92@gmail.com>
2026-07-25 08:41:07 +00:00
Nikhil Mantri
d22ee8f7aa fix(telemetrymetadata): update fallback to false for positive operators in related values queries (#12250)
Some checks failed
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
build-staging / staging (push) Has been cancelled
* chore: update fallback to false for positive operators

* chore: changed to distributed table

* chore: flip AND to or in outside condition

* chore: added data_source condition

* chore: added integration tests

* chore: simplified comment

* chore: more simplified comment

* chore: test case both columns contain host.name -> resources take precedence
2026-07-25 04:08:25 +00:00
Ashwin Bhatkal
5b004d1efc refactor(dashboard): store system icons as SVG assets with a safe image resolver (#12259)
* feat(dashboard-v2): add file-based dashboard icons and logos with a safe image resolver

* refactor(dashboard-v2): render dashboard icons via the safe resolver

* fix(dashboard-v2): clamp dashboard icon size to 16px

* fix(dashboard-v2): reject invalid dashboard image before saving

* fix(dashboard-v2): validate dashboard image server-side

* test(dashboard-v2): stub the Vite-only icon glob for jest and align image fixtures

* test(dashboard-v2): update patch test for image validation

* refactor(dashboard-v2): hoist dashboard image consts/regex and tidy validation tests

* refactor(dashboard-v2): drop the validateImage doc comment
2026-07-25 03:19:19 +00:00
Naman Verma
ecb77af4cb chore: fix schema and validations based on migration errors (#12263)
Some checks failed
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / staging (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
* chore: fix schema based on migration errors

* test: add rejection based integration tests for new validations

* fix: remove datasource field from schema
2026-07-24 15:37:04 +00:00
Tushar Vats
973429a22f fix(logs): stop hasToken and hasAny/hasAll 500s on separator and big-int needles (#12261)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
hasToken(body, <needle>) returned a 500 whenever the needle contained a
token separator or whitespace (ClickHouse code 36). Validate the needle
up front in conditionForHasToken (the single choke point for both legacy
and JSON modes) and return a clean 400 pointing at CONTAINS instead.

hasAny/hasAll on a legacy body array returned a 500 for a quoted integer
needle >= 2^32: clickhouse-go binds it as a concrete Array(UInt64), which
has no supertype with the Array(Nullable(Int64)) extraction that the
function must unify (code 386). CAST the needle array to Array(Int64) to
match the extraction. Scalar has() and the scalar OR-fallback are
value-level and unaffected.
2026-07-24 12:56:56 +00:00
Swapnil Nakade
9f5ad1ba26 fix: list gcp accounts was missing config (#12175)
* fix: list gcp accounts was missing config

* refactor: updating cloudintegration integration tests

* refactor: moving variables to test file

* chore: removing verbose comments
2026-07-24 10:56:33 +00:00
Naman Verma
b2574e7797 fix: remove requirement for links to be always present (#12260)
* fix: remove requirement for links to be always present

* fix: make links use defined type

* test: add integration test for link omission roundtrip

* test: change not in to None for links in panel

---------

Co-authored-by: Ashwin Bhatkal <ashwin96@gmail.com>
2026-07-24 10:54:47 +00:00
Ashwin Bhatkal
cbde4dca41 fix(dashboard-v2): templates header, long-title handling, variable icon & unified delete modal (#12256)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(dashboard-v2): add "Request a new template" header to templates tab

* fix(dashboard-v2): keep toolbar tags intact when the dashboard title is long

* fix(dashboard-v2): use a subtle outline info icon in the variables bar

* fix(dashboard-v2): truncate long dashboard title in the breadcrumb

* refactor(dashboard-v2): share one delete-confirm modal for views and dashboards

* fix(dashboard-v2): reduce panel area horizontal padding

* fix(dashboard-v2): show loader while deleting a saved view

Deleting a saved view was fire-and-forget: the confirm modal closed instantly
and the row vanished later on refetch, with no loading feedback. Make the
delete awaitable end-to-end (deleteView -> removeView -> handleRemoveView ->
ViewsRail onConfirm) so the shared confirm modal keeps its Delete button
loading until the backend call settles, then closes — matching dashboard
delete. The active view resets only on success.

* fix(dashboard-v2): fire delete success toast after the list refetches

Move the dashboard delete success toast to after the list is invalidated and
refetched, so it appears once the row is gone, and use a plain success message.

* fix(dashboard-v2): centre the delete-confirm alert icon to match v1

The shared delete-confirm modal top-aligned the CircleAlert (align-items:
flex-start + a margin-top nudge) at size 20; centre it against the body
(align-items: center) at size 3xl, matching the v1 dashboard delete button.
2026-07-23 21:36:22 +00:00
Ashwin Bhatkal
aabe59103d fix(dashboard-v2): variable selection (default reset + multiselect commit-on-close) (#12254)
Some checks failed
build-staging / staging (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
* feat(NewSelect): make multi-select Only/All + Toggle real clickable pills

* fix(dashboard-v2): reset variable default only on real option-set changes

* fix(dashboard-v2): multiselect value selection — commit on close, skip unchanged cascade, allow clear

* fix(dashboard-v2): fix multiselect tag layout in the variable pill

* fix: format issue

* fix(dashboard-v2): clear invalid variable default and fix useVariableForm test OOM

Stabilize the useVariableForm test props so the reset-on-`initial` effect no
longer loops into a heap OOM (the hook relies on a stable `initial` reference,
which memoized callers already provide). With the suite now running, align the
QUERY/CUSTOM default-reset to clear an invalidated default to empty rather than
pinning the first option, matching the DYNAMIC path and letting resolution fall
back to the first option / ALL.

* refactor(NewSelect): extract option-row button handler out of JSX

Pull the repeated stopPropagation/preventDefault/select/reset logic on the
option row's checkbox, Only/All and Toggle buttons into a single
`selectFromButton(e, source)` helper so the JSX stays readable.
2026-07-23 20:08:56 +00:00
Ashwin Bhatkal
6c957d1e8b feat(dashboard-v2): ANTLR-composed apply-to-panels with an impact review dialog (#12181)
* fix(dashboard-v2): compose panel filter via ANTLR so apply/remove respect precedence

* feat(dashboard-v2): review apply-to-panels query changes in the impact dialog

* feat(dashboard-v2): surface non-builder panel queries in the apply-to-panels review

findApplyUsages now emits an editable row for every selected panel query that
does not yet reference the variable — PromQL/ClickHouse rows default to their
current text (never auto-injected) so the review dialog opens for them instead
of silently no-op'ing. Adds isVariableAppliedToAllPanels, a cheap boolean scan
(no ANTLR) for the 'apply to all' disabled state.

* feat(dashboard-v2): route apply-to-all through the review dialog

The list-row 'Apply to all' now opens the same VariableImpactDialog review (all
panels as target) instead of a plain confirm, and is disabled with an 'Already
applied to all panels' tooltip once every panel query references the variable.
Removes the now-redundant ApplyToAllDialog and the dead build*VariableToPanelsPatch
helpers.
2026-07-23 19:42:49 +00:00
Ashwin Bhatkal
de1e21a050 fix(dashboard-v2): variable, panel-layout, and dashboards-list bug-bash fixes (#12178)
* feat(dashboard-v2): compact panel layout before persisting drag and JSON edits

* feat(dashboard-v2): resolve and persist user-typed variable selection across refetch

* feat(dashboard-v2): expose image field and close-on-Escape in the JSON editor

* fix(dashboard-v2): keep a custom dashboard image selectable in the overview picker

* fix(dashboard-v2): variable form & bar fixes

* fix(dashboard-v2): unify section title modal, keep open on error

* refactor(dashboard-v2): dashboard-list tags (popover to tooltip, TagBadge, DashboardRowTags)

* fix(dashboard-v2): sync locked state to the detail cache after list-page toggle

* fix(dashboard-v2): stop the variable value control from showing scrollbars

* fix(dashboard-v2): hide the not-recommended pill below 1280px, not 1024px
2026-07-23 18:21:12 +00:00
Nikhil Soni
85abfd626e fix(tracedetail): correct EndTimestampMillis in flamegraph API response (#12251)
* fix(tracedetail): use span-derived bounds for flamegraph EndTimestampMillis

The trace_summary MV stores max(timestamp) as 'end', which is the maximum
span *start* time. For the final span of a trace, this misses the span's
duration entirely, making EndTimestampMillis shorter than the actual trace end.

FlamegraphTrace already computed the correct end via updateTimeRange
(end = max(span_start + duration)) but only used it internally for bucket
sampling. Add TimeRange() to expose these span-derived bounds and use them
in both getFullFlamegraph and getWindowedFlamegraph instead of summary.End.

The waterfall was unaffected — NewWaterfallTraceFromSpans already computes
endTime = max(span.TimeUnix + span.DurationNano) from actual spans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GxTqz7CqqmoFEPHFqhJUCa

* chore: remove unnecessary comments

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-23 17:21:29 +00:00
Aditya Singh
66c7e2d157 feat(trace-details): download full trace from trace details page (#12115)
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
Release Drafter / update_release_draft (push) Has been cancelled
* feat: add blob-stitching and part-fetch primitives for chunked downloads

* feat: add bounded worker-pool runner for chunked downloads

* feat: add trace download store, query and progress panel

* feat: add download trace option to trace options menu

* feat: update tests
2026-07-23 12:39:27 +00:00
Vinicius Lourenço
3c5c2cdcd9 refactor(infrastructure-monitoring-v2): remove map between dot and underscore attributes (#12214)
* refactor(infrastructure-monitoring-v2): remove map between dot and underscore attributes

* test(entity-metrics): fix test
2026-07-23 11:34:38 +00:00
Gaurav Tewari
cd1861ff87 chore: bump package versions (#12213)
* chore: bump axios version

* chore: bump more packages

---------

Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
2026-07-23 11:03:19 +00:00
Nikhil Mantri
f0a0b95ecd chore: removed code and tests (#12243)
Co-authored-by: Pandey <vibhupandey28@gmail.com>
2026-07-23 10:48:44 +00:00
Ashwin Bhatkal
38340b5027 fix(dashboards-v2): dedicated "all" dynamic-variable signal + required non-nullable links (#12201)
* fix(dashboards-v2): make panel/dashboard links required and non-nullable

* fix: validate links on read from db as well

* fix: allow all as value for signal

* fix: dont allow empty string for signal

* fix(dashboards-v2): dedicated `all` dynamic-variable signal (frontend + client)

* test: add empty links to new payloads in integration tests

---------

Co-authored-by: Naman Verma <naman.verma@signoz.io>
2026-07-23 10:34:18 +00:00
Abhi kumar
fba56e9a64 fix(dashboard): filter-quote escaping, panel export encoding, right-legend width, number-panel query warning (#12246)
* fix(qb): stop doubling escaped quotes on filter round-trip

formatSingleValue escapes ' -> \' when quoting a filter value, but its inverse
(unquote, in formatSingleValueForFilter) stripped the quotes without unescaping.
Each expression <-> filters round-trip therefore added a backslash. "Create
alert from panel" re-reads the query through the URL twice, so a filter like
name = 'it\'s a ribbon' arrived at the alert page as name = 'it\\'s a ribbon'
and failed the filter parser. Unescape \' -> ' so the round-trip is lossless.

* fix(dashboard): double-encode compositeQuery in panel export link

useGetCompositeQueryParam decodes the compositeQuery param twice, so a single
encode left a bare %/+ from a filter expression (e.g. ILIKE 'Inf%') that made
the reader's second decode throw and drop the whole query. Encode twice in
buildExportPanelLink so the query survives the round-trip.

* fix(dashboard): size right legend column to the longest label

A right-positioned legend is a single vertical column, so give it its own width
budget and size it to fit the longest series label, clamped to
[150px, min(320, 40% of container width)], instead of the shared bottom-legend
cap. Long service names no longer ellipsize in the pie/donut legend.

* feat(dashboard): warn when a number panel has more than one query enabled

A Number panel renders only the first query's value, so leaving extra
queries enabled (e.g. a formula plus its inputs) silently shows the wrong
value with no feedback. Surface a warning in the panel editor and on the
rendered panel when a Number panel has more than one enabled query.

Refs #9512
2026-07-23 08:47:25 +00:00
Tushar Vats
cbc8b5424b feat(qb): parse search() in the filter grammar (not yet implemented) (#12129)
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
Release Drafter / update_release_draft (push) Has been cancelled
Add a search('needle') function to the filter grammar, recognized but not
yet executed - using it returns "search is not yet supported" instead of a
syntax error. Alert links, dashboard variables, and the contradiction check
pass it through untouched. `search` is now reserved, like has/hasAny.
2026-07-22 17:03:30 +00:00
Aditya Singh
7ac6df6fb0 feat(query-builder): fieldContext prefixed key suggestions (#12200)
* feat(query-builder): fieldContext-prefixed key suggestions and type-aware operator filtering

* test(query-builder): extract shared CodeMirror DOM mocks for jsdom specs

* test(query-builder): add CodeMirror+MSW behavior specs for suggestion flows
2026-07-22 13:55:01 +00:00
Swapnil Nakade
8c6a0de8c3 refactor: gcp integration memorystore redis dashboard update (#12229)
Some checks failed
build-staging / staging (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
build-staging / prepare (push) Has been cancelled
build-staging / js-build (push) Has been cancelled
build-staging / go-build (push) Has been cancelled
2026-07-22 11:42:12 +00:00
Naman Verma
c7abe0fe7b fix: add rank column to tag relations to keep note of tag ordering in dashboards (#12170)
* fix: always sort dashboard tags in alphabetical order

* fix: add rank column to tag relations to keep note of tag ordering in dashboards

* fix: dont disable FK just for adding a new column

* fix: add back tag relation unique index
2026-07-22 11:34:25 +00:00
Abhi kumar
b236273c5f test(e2e): cover alert edit-page threshold persistence (#12227)
* test(e2e): cover alert edit-page threshold persistence

Adds an E2E test that seeds a notification channel + v2 threshold alert
(target 245) via API, opens the alert overview/edit page, and asserts the
condition editor shows the persisted threshold value once loaded.

New helper tests/e2e/helpers/alerts.ts (channel/alert seed + delete +
navigate) mirrors the existing dashboards seeding pattern; the test lives
alongside the existing tabs test in tests/e2e/tests/alerts/alerts.spec.ts.

Verified against a local --with-web stack (passes with the loaded value,
fails when the edit page resets it).

* chore: pr review fixes
2026-07-22 10:13:19 +00:00
Naman Verma
14bb4a8437 fix: remove omitempty/omitzero and explicit empty value handling in dashboard v2 spec (#12197)
* fix: remove omitempty and omitzero where not needed in dashboard v2 spec

* fix: reject explicitly empty enum values if invalid
2026-07-22 09:34:52 +00:00
Swapnil Nakade
842d44d874 feat: adding gcp memorystore redis service (#12118)
* feat: adding gcp memorystore redis service

* refactor: updating dashboard title

* refactor: extending width of uptime gauge panel

* refactor: updating cpu utilization panel

* refactor: updating dashboard panel to use rate function instead of hack
2026-07-22 07:08:26 +00:00
Abhi kumar
0d7aff9ce5 fix(alerts-v2): preserve loaded thresholds on edit-page refresh (#12226)
PR #12137 changed the URL-prefill effect dependency from the primitive
`thresholdsFromURL` string to the memoized `urlPrefill` object, which gets a
fresh reference on every `location.search` change. On the alert overview/edit
page the query builder normalizes the loaded query back into the URL after
mount, so the effect re-ran and its unconditional threshold RESET wiped the
value loaded via SET_INITIAL_STATE (245 -> 0 on refresh).

URL-declared prefill is a create-flow concern; in edit mode the alert state is
owned by SET_INITIAL_STATE. Skip the effect when isEditMode is true.
2026-07-22 06:32:55 +00:00
Naman Verma
fe101a1835 fix: fetch latest monotonicty for metrics instead of any random (#12205)
* fix: fetch latest monotonicty for metrics instead of any random

* test: add integration test for is monotonic flag
2026-07-22 06:01:23 +00:00
Nityananda Gohain
69c2f8d73f feat: span mapper test endpoint (#11795)
* feat: span mapper test endpoint

* feat: integrate with collector

* fix: more cleanup and integration test

* fix: lint

* fix: cleanup go.mod

* fix: update openapi

* fix: more cleanup

* fix: update go.mod

* fix: minor cleanup

* fix: update group_id to groupId

* fix: cleanup integration test

---------

Co-authored-by: Gaurav Tewari <gauravtewari111@gmail.com>
2026-07-22 04:52:13 +00:00
Ashwin Bhatkal
fc6f398828 fix: show the backend error message in the error modal instead of a generic axios error (#12215)
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
Release Drafter / update_release_draft (push) Has been cancelled
2026-07-21 19:29:13 +00:00
Ashwin Bhatkal
8cd2077522 fix(dashboard-v2): validate key:value tags in the editor (inline-edit errors + backend-rule space/char/length checks) (#12062) 2026-07-21 19:27:24 +00:00
Vinicius Lourenço
7b23286aab refactor(infrastructure-monitoring-v2): ui/ux improvements (#12191)
* fix(pods): min width for pod restarts

* fix(disk-usage): not have minimum width correctly

* perf(infra-monitoring-v2): set cache before opening details modal

* feat(tanstack): add custom markers for perf review

* feat(tanstack): add hover tooltip + integrate on infra monitoring

* perf(grouped-status-counts): use tanstack tooltip hover

* fix(k8s-base-list): adding table def to classnames

* perf(grouped-status-counts): reduce node count on DOM

* feat(infrastructure-monitoring): use single component instead of unmount/mount all content

* chore(infrastructure-monitoring-k8s): remove old list components

* fix(hosts): make it double line header

* fix(infrastructure-monitoring): show scrollbar only on hover

* chore(pod-metrics): add docs for charts

* fix(perf-lib): address comments

* fix(infra-monitoring): rename entityConfig to entity.config
2026-07-21 18:51:20 +00:00
Ashwin Bhatkal
6bdf054a05 test(e2e): fix flaky dashboards-detail specs (sidenav overlay + slow hydration) (#12180)
* test(e2e): pin the sidenav so its flyout stops occluding dashboard content

* test(e2e): harden dashboards-detail specs against slow CI hydration
2026-07-21 17:50:44 +00:00
Ashwin Bhatkal
c7f4aa01c4 feat(dashboard-v2): product analytics instrumentation for list + detail pages (#12199)
* feat(dashboard-v2): add analytics event catalog for list + detail

Introduce DashboardListEvents and DashboardDetailEvents enums as the single
source of truth for V2 dashboard product-analytics event names. Grouped
action-discriminator events (RowAction, SectionAction, PanelAction,
DrilldownAction, JsonEditorAction, PublicDashboardAction) mirror the existing
'Dashboard Detail: Panel action' pattern. PanelAction reuses the existing V1
string so panel actions stay comparable across versions.

* feat(dashboard-v2): instrument dashboards list page analytics

Fire logEvent for create/import funnel (tab switch, created success, import
failed), find/navigate (search, filter, sort, columns, pin, pagination),
Views rail (select/save/rename/delete/reset, rail toggle), row actions
(view/open-new-tab/copy-link/rename/edit-tags/duplicate/lock/delete),
legacy-dashboard dialog, and error-state actions.

* feat(dashboard-v2): instrument dashboard detail page analytics

Fire logEvent across the detail page: lifecycle (open, rename, clone, delete,
lock, full-screen, stale-reload, public-url), sections (add/rename/clone/delete,
collapse, reorder, first-section migration), panels (add, edit/clone/delete/move,
export csv/png/svg, view, editor save/discard, type change, search, no-data),
layout drag/resize (rate-limited), variables setup (save/delete/reorder/test/
apply-to-all) and runtime selection (value selected rate-limited, multiselect
clear, options-fetch failed), drill-down (open + view-logs/traces/breakout/
filter/set-variable/create-variable), panel zoom, JSON editor actions, settings
overview save/discard, cursor sync, and public-dashboard actions.

* fix: lint

* refactor(dashboard-v2): extract inline analytics handlers into named functions

Address PR review: pull the inline JSX arrow handlers that wrap logEvent out
into named handlers across both V2 dashboard pages (public-url open, cursor/
tooltip sync, settings/JSON/full-screen actions, panel search, drilldown
view-logs/traces, create-modal tab, error retry/contact-support, row open-in-
new-tab/copy-link, legacy contact-support, save-view actions). Behavior
unchanged. Handlers defined inside .map()/per-row renderers and pre-existing
complex value-mapping handlers (ValueSelector) are intentionally left inline.
2026-07-21 14:13:26 +00:00
Vinicius Lourenço
e93792d427 fix(overlayscrollbars): leaking memory due to img.load event listener (#12179)
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
Release Drafter / update_release_draft (push) Has been cancelled
2026-07-21 12:07:55 +00:00
Vinicius Lourenço
6d0f618a9e feat(infra-monitoring): persist custom time on drawer across categories (#12038)
* feat(globalTime): add method to reset time of child to parent

* refactor(infra-monitoring): extract content from K8sBaseDetails

* feat(entity-details): add support to persist custom time across categories of k8s

* test(entity-details): update existing tests

* test(entity-details): add regression test for time conversion

* fix(k8s-list): ensure default value for list is 30m

* fix(entity-counts-section): address todo around inherit selected time

* fix(pr): missing fix for tab validation, lost of moving/merge
2026-07-21 11:58:26 +00:00
Ashwin Bhatkal
564d479aae test(e2e): fix teardown crashes (network endpoint race + eager keeper lookup) (#12204)
* test(e2e): retry network teardown to survive async endpoint detach

`--teardown` fails on CI with `docker.errors.APIError: 403 ... error while
removing network ... has active endpoints`: containers are torn down before
the network, but Docker detaches endpoints asynchronously, so the network
can still report active endpoints for a moment. Retry the removal,
force-disconnecting any stragglers each round.

* test(e2e): resolve keeper coordinator lazily so --teardown doesn't crash

`create_clickhouse` / `create_clickhouse_cluster` computed
`next(iter(keeper.container_configs.values()))` eagerly. Under `--teardown`
the keeper fixture is empty, so it raised StopIteration during teardown
setup, before any finalizer ran. Move the lookup inside create().
2026-07-21 11:44:30 +00:00
462 changed files with 22034 additions and 9049 deletions

View File

@@ -58,6 +58,7 @@ jobs:
- role
- rootuser
- serviceaccount
- spanmapper
- querier_json_body
- querier_skip_resource_fingerprint
- ttl

View File

@@ -1495,6 +1495,7 @@ components:
- cassandradb
- redis
- cloudsql_postgres
- memorystore_redis
type: string
CloudintegrationtypesServiceMetadata:
properties:
@@ -1591,13 +1592,6 @@ components:
required:
- config
type: object
CommonDisplay:
properties:
description:
type: string
name:
type: string
type: object
CommonJSONRef:
properties:
$ref:
@@ -2731,11 +2725,6 @@ components:
type: object
DashboardtypesDashboardSpec:
properties:
datasources:
additionalProperties:
$ref: '#/components/schemas/DashboardtypesDatasourceSpec'
nullable: true
type: object
display:
$ref: '#/components/schemas/DashboardtypesDisplay'
duration:
@@ -2800,39 +2789,6 @@ components:
required:
- version
type: object
DashboardtypesDatasourcePlugin:
discriminator:
mapping:
signoz/Datasource: '#/components/schemas/DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpec'
propertyName: kind
oneOf:
- $ref: '#/components/schemas/DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpec'
type: object
DashboardtypesDatasourcePluginKind:
enum:
- signoz/Datasource
type: string
DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpec:
properties:
kind:
enum:
- signoz/Datasource
type: string
spec:
$ref: '#/components/schemas/DashboardtypesSigNozDatasourceSpec'
required:
- kind
- spec
type: object
DashboardtypesDatasourceSpec:
properties:
default:
type: boolean
display:
$ref: '#/components/schemas/CommonDisplay'
plugin:
$ref: '#/components/schemas/DashboardtypesDatasourcePlugin'
type: object
DashboardtypesDisplay:
properties:
description:
@@ -2842,14 +2798,22 @@ components:
required:
- name
type: object
DashboardtypesDynamicVariableSignal:
enum:
- traces
- logs
- metrics
- all
type: string
DashboardtypesDynamicVariableSpec:
properties:
name:
type: string
signal:
$ref: '#/components/schemas/TelemetrytypesSignal'
$ref: '#/components/schemas/DashboardtypesDynamicVariableSignal'
required:
- name
- signal
type: object
DashboardtypesFillMode:
enum:
@@ -3601,8 +3565,6 @@ components:
required:
- queryValue
type: object
DashboardtypesSigNozDatasourceSpec:
type: object
DashboardtypesSource:
enum:
- user
@@ -4298,8 +4260,6 @@ components:
type: object
nodeCountsByReadiness:
$ref: '#/components/schemas/InframonitoringtypesNodeCountsByReadiness'
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
required:
@@ -4309,7 +4269,6 @@ components:
- clusterMemory
- clusterMemoryAllocatable
- nodeCountsByReadiness
- podCountsByPhase
- podCountsByStatus
- counts
- meta
@@ -4519,8 +4478,6 @@ components:
type: object
misscheduledNodes:
type: integer
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
readyNodes:
@@ -4537,7 +4494,6 @@ components:
- currentNodes
- readyNodes
- misscheduledNodes
- podCountsByPhase
- podCountsByStatus
- meta
type: object
@@ -4592,8 +4548,6 @@ components:
type: string
nullable: true
type: object
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
required:
@@ -4606,7 +4560,6 @@ components:
- deploymentMemoryLimit
- desiredPods
- availablePods
- podCountsByPhase
- podCountsByStatus
- meta
type: object
@@ -4738,8 +4691,6 @@ components:
type: string
nullable: true
type: object
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
successfulPods:
@@ -4756,7 +4707,6 @@ components:
- activePods
- failedPods
- successfulPods
- podCountsByPhase
- podCountsByStatus
- meta
type: object
@@ -4866,15 +4816,12 @@ components:
type: number
namespaceName:
type: string
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
required:
- namespaceName
- namespaceCPU
- namespaceMemory
- podCountsByPhase
- podCountsByStatus
- counts
- meta
@@ -4940,15 +4887,12 @@ components:
type: number
nodeName:
type: string
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
required:
- nodeName
- condition
- nodeCountsByReadiness
- podCountsByPhase
- podCountsByStatus
- nodeCPU
- nodeCPUAllocatable
@@ -4976,25 +4920,6 @@ components:
- total
- endTimeBeforeRetention
type: object
InframonitoringtypesPodCountsByPhase:
properties:
failed:
type: integer
pending:
type: integer
running:
type: integer
succeeded:
type: integer
unknown:
type: integer
required:
- pending
- running
- succeeded
- failed
- unknown
type: object
InframonitoringtypesPodCountsByStatus:
properties:
completed:
@@ -5053,15 +4978,6 @@ components:
- shutdown
- unexpectedAdmissionError
type: object
InframonitoringtypesPodPhase:
enum:
- pending
- running
- succeeded
- failed
- unknown
- no_data
type: string
InframonitoringtypesPodRecord:
properties:
meta:
@@ -5081,8 +4997,6 @@ components:
podCPURequest:
format: double
type: number
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
podMemory:
@@ -5094,8 +5008,6 @@ components:
podMemoryRequest:
format: double
type: number
podPhase:
$ref: '#/components/schemas/InframonitoringtypesPodPhase'
podRestarts:
format: int64
type: integer
@@ -5111,8 +5023,6 @@ components:
- podMemory
- podMemoryRequest
- podMemoryLimit
- podPhase
- podCountsByPhase
- podStatus
- podCountsByStatus
- podRestarts
@@ -5463,8 +5373,6 @@ components:
type: string
nullable: true
type: object
podCountsByPhase:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByPhase'
podCountsByStatus:
$ref: '#/components/schemas/InframonitoringtypesPodCountsByStatus'
statefulSetCPU:
@@ -5497,7 +5405,6 @@ components:
- statefulSetMemoryLimit
- desiredPods
- currentPods
- podCountsByPhase
- podCountsByStatus
- meta
type: object
@@ -8084,6 +7991,19 @@ components:
required:
- items
type: object
SpantypesGettableSpanMapperTest:
properties:
collectorLogs:
items:
type: string
nullable: true
type: array
spans:
items:
$ref: '#/components/schemas/SpantypesSpanMapperTestSpan'
nullable: true
type: array
type: object
SpantypesGettableSpanMappers:
properties:
items:
@@ -8180,6 +8100,39 @@ components:
- name
- condition
type: object
SpantypesPostableSpanMapperTest:
properties:
groups:
items:
$ref: '#/components/schemas/SpantypesPostableSpanMapperTestGroup'
nullable: true
type: array
spans:
items:
$ref: '#/components/schemas/SpantypesSpanMapperTestSpan'
nullable: true
type: array
required:
- spans
- groups
type: object
SpantypesPostableSpanMapperTestGroup:
properties:
condition:
$ref: '#/components/schemas/SpantypesSpanMapperGroupCondition'
enabled:
type: boolean
mappers:
items:
$ref: '#/components/schemas/SpantypesPostableSpanMapper'
nullable: true
type: array
name:
type: string
required:
- name
- condition
type: object
SpantypesPostableTraceAggregations:
properties:
aggregations:
@@ -8341,6 +8294,17 @@ components:
- operation
- priority
type: object
SpantypesSpanMapperTestSpan:
properties:
attributes:
additionalProperties: {}
nullable: true
type: object
resource:
additionalProperties: {}
nullable: true
type: object
type: object
SpantypesUpdatableSpanMapper:
properties:
config:
@@ -14074,6 +14038,69 @@ paths:
summary: Update a span mapper
tags:
- spanmapper
/api/v1/span_mapper_groups/test:
post:
deprecated: false
description: Tests how span mappers would transform sample spans
operationId: TestSpanMappers
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SpantypesPostableSpanMapperTest'
responses:
"200":
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/SpantypesGettableSpanMapperTest'
status:
type: string
required:
- status
- data
type: object
description: OK
"400":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Bad Request
"401":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Unauthorized
"403":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Forbidden
"404":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Not Found
"500":
content:
application/json:
schema:
$ref: '#/components/schemas/RenderErrorResponse'
description: Internal Server Error
security:
- api_key:
- VIEWER
- tokenizer:
- VIEWER
summary: Test span mappers against sample spans
tags:
- spanmapper
/api/v1/stats:
get:
deprecated: false
@@ -16031,17 +16058,19 @@ paths:
metrics derived by summing per-node values within the group: CPU usage, CPU
allocatable, memory working set, memory allocatable. Each row also reports
per-group nodeCountsByReadiness ({ ready, notReady } from each node''s latest
k8s.node.condition_ready value) and per-group podCountsByPhase ({ pending,
running, succeeded, failed, unknown } from each pod''s latest k8s.pod.phase
value). Each cluster includes metadata attributes (k8s.cluster.name). The
response type is ''list'' for the default k8s.cluster.name grouping or ''grouped_list''
for custom groupBy keys; in both modes every row aggregates nodes and pods
in the group. Supports filtering via a filter expression, custom groupBy,
ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination
via offset/limit. Also reports whether the requested time range falls before
the data retention boundary. Numeric metric fields (clusterCPU, clusterCPUAllocatable,
clusterMemory, clusterMemoryAllocatable) return -1 as a sentinel when no data
is available for that field.'
k8s.node.condition_ready value) and per-group podCountsByStatus ({ pending,
running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull,
createContainerConfigError, containerCreating, oomKilled, completed, error,
containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError
} reflecting each pod''s latest kubectl-style display status). Each cluster
includes metadata attributes (k8s.cluster.name). The response type is ''list''
for the default k8s.cluster.name grouping or ''grouped_list'' for custom groupBy
keys; in both modes every row aggregates nodes and pods in the group. Supports
filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable
/ memory / memory_allocatable, and pagination via offset/limit. Also reports
whether the requested time range falls before the data retention boundary.
Numeric metric fields (clusterCPU, clusterCPUAllocatable, clusterMemory, clusterMemoryAllocatable)
return -1 as a sentinel when no data is available for that field.'
operationId: ListClusters
requestBody:
content:
@@ -16109,13 +16138,16 @@ paths:
the number of nodes running at least one ready daemon pod) and misscheduledNodes
(k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon
pod but not supposed to) — note these are node counts, not pod counts. It
also reports per-group podCountsByPhase ({ pending, running, succeeded, failed,
unknown } from each pod''s latest k8s.pod.phase value). Each daemonset includes
metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name).
The response type is ''list'' for the default k8s.daemonset.name grouping
or ''grouped_list'' for custom groupBy keys; in both modes every row aggregates
pods owned by daemonsets in the group. Supports filtering via a filter expression,
custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request
also reports per-group podCountsByStatus ({ pending, running, failed, unknown,
crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError,
containerCreating, oomKilled, completed, error, containerCannotRun, evicted,
nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each
pod''s latest kubectl-style display status). Each daemonset includes metadata
attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The
response type is ''list'' for the default k8s.daemonset.name grouping or ''grouped_list''
for custom groupBy keys; in both modes every row aggregates pods owned by
daemonsets in the group. Supports filtering via a filter expression, custom
groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request
/ memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes,
and pagination via offset/limit. Also reports whether the requested time range
falls before the data retention boundary. Numeric metric fields (daemonSetCPU,
@@ -16183,17 +16215,19 @@ paths:
the deployment, plus average CPU/memory request and limit utilization (deploymentCPURequest,
deploymentCPULimit, deploymentMemoryRequest, deploymentMemoryLimit). Each
row also reports the latest known desiredPods (k8s.deployment.desired) and
availablePods (k8s.deployment.available) replica counts and per-group podCountsByPhase
({ pending, running, succeeded, failed, unknown } from each pod''s latest
k8s.pod.phase value). Each deployment includes metadata attributes (k8s.deployment.name,
k8s.namespace.name, k8s.cluster.name). The response type is ''list'' for the
default k8s.deployment.name grouping or ''grouped_list'' for custom groupBy
keys; in both modes every row aggregates pods owned by deployments in the
group. Supports filtering via a filter expression, custom groupBy, ordering
by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit
/ desired_pods / available_pods, and pagination via offset/limit. Also reports
whether the requested time range falls before the data retention boundary.
Numeric metric fields (deploymentCPU, deploymentCPURequest, deploymentCPULimit,
availablePods (k8s.deployment.available) replica counts and per-group podCountsByStatus
({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff,
errImagePull, createContainerConfigError, containerCreating, oomKilled, completed,
error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError
} reflecting each pod''s latest kubectl-style display status). Each deployment
includes metadata attributes (k8s.deployment.name, k8s.namespace.name, k8s.cluster.name).
The response type is ''list'' for the default k8s.deployment.name grouping
or ''grouped_list'' for custom groupBy keys; in both modes every row aggregates
pods owned by deployments in the group. Supports filtering via a filter expression,
custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request
/ memory_limit / desired_pods / available_pods, and pagination via offset/limit.
Also reports whether the requested time range falls before the data retention
boundary. Numeric metric fields (deploymentCPU, deploymentCPURequest, deploymentCPULimit,
deploymentMemory, deploymentMemoryRequest, deploymentMemoryLimit, desiredPods,
availablePods) return -1 as a sentinel when no data is available for that
field.'
@@ -16328,9 +16362,12 @@ paths:
(k8s.job.desired_successful_pods, the target completion count), activePods
(k8s.job.active_pods), failedPods (k8s.job.failed_pods, cumulative across
the lifetime of the job), and successfulPods (k8s.job.successful_pods, cumulative).
It also reports per-group podCountsByPhase ({ pending, running, succeeded,
failed, unknown } from each pod''s latest k8s.pod.phase value); note podCountsByPhase.failed
(current pod-phase) is distinct from failedPods (cumulative job kube-state-metric).
It also reports per-group podCountsByStatus ({ pending, running, failed, unknown,
crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError,
containerCreating, oomKilled, completed, error, containerCannotRun, evicted,
nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each
pod''s latest kubectl-style display status); note podCountsByStatus.failed
(current pod status) is distinct from failedPods (cumulative job kube-state-metric).
Each job includes metadata attributes (k8s.job.name, k8s.namespace.name, k8s.cluster.name).
The response type is ''list'' for the default k8s.job.name grouping or ''grouped_list''
for custom groupBy keys; in both modes every row aggregates pods owned by
@@ -16480,16 +16517,18 @@ paths:
deprecated: false
description: 'Returns a paginated list of Kubernetes namespaces with key aggregated
pod metrics: CPU usage and memory working set (summed across pods in the group),
plus per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown
} from each pod''s latest k8s.pod.phase value in the window). Each namespace
includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response
type is ''list'' for the default k8s.namespace.name grouping or ''grouped_list''
for custom groupBy keys; in both modes every row aggregates pods in the group.
Supports filtering via a filter expression, custom groupBy, ordering by cpu
/ memory, and pagination via offset/limit. Also reports whether the requested
time range falls before the data retention boundary. Numeric metric fields
(namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available
for that field.'
plus per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff,
imagePullBackOff, errImagePull, createContainerConfigError, containerCreating,
oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost,
shutdown, unexpectedAdmissionError } reflecting each pod''s latest kubectl-style
display status in the window). Each namespace includes metadata attributes
(k8s.namespace.name, k8s.cluster.name). The response type is ''list'' for
the default k8s.namespace.name grouping or ''grouped_list'' for custom groupBy
keys; in both modes every row aggregates pods in the group. Supports filtering
via a filter expression, custom groupBy, ordering by cpu / memory, and pagination
via offset/limit. Also reports whether the requested time range falls before
the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory)
return -1 as a sentinel when no data is available for that field.'
operationId: ListNamespaces
requestBody:
content:
@@ -16549,18 +16588,21 @@ paths:
description: 'Returns a paginated list of Kubernetes nodes with key metrics:
CPU usage, CPU allocatable, memory working set, memory allocatable, per-group
nodeCountsByReadiness ({ ready, notReady } from each node''s latest k8s.node.condition_ready
in the window) and per-group podCountsByPhase ({ pending, running, succeeded,
failed, unknown } for pods scheduled on the listed nodes). Each node includes
metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is
''list'' for the default k8s.node.name grouping (each row is one node with
its current condition string: ready / not_ready / no_data) or ''grouped_list''
for custom groupBy keys (each row aggregates nodes in the group; condition
stays no_data). Supports filtering via a filter expression, custom groupBy,
ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination
via offset/limit. Also reports whether the requested time range falls before
the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable,
nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is
available for that field.'
in the window) and per-group podCountsByStatus ({ pending, running, failed,
unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError,
containerCreating, oomKilled, completed, error, containerCannotRun, evicted,
nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } for pods scheduled
on the listed nodes, reflecting each pod''s latest kubectl-style display status).
Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The
response type is ''list'' for the default k8s.node.name grouping (each row
is one node with its current condition string: ready / not_ready / no_data)
or ''grouped_list'' for custom groupBy keys (each row aggregates nodes in
the group; condition stays no_data). Supports filtering via a filter expression,
custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable,
and pagination via offset/limit. Also reports whether the requested time range
falls before the data retention boundary. Numeric metric fields (nodeCPU,
nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel
when no data is available for that field.'
operationId: ListNodes
requestBody:
content:
@@ -16619,20 +16661,23 @@ paths:
deprecated: false
description: 'Returns a paginated list of Kubernetes pods with key metrics:
CPU usage, CPU request/limit utilization, memory working set, memory request/limit
utilization, current pod phase (pending/running/succeeded/failed/unknown/no_data),
and pod age (ms since start time). Each pod includes metadata attributes (namespace,
node, workload owner such as deployment/statefulset/daemonset/job/cronjob,
utilization, current pod status (kubectl-style display status such as Running/Pending/CrashLoopBackOff,
or no_data), and pod age (ms since start time). Each pod includes metadata
attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob,
cluster). Supports filtering via a filter expression, custom groupBy to aggregate
pods by any attribute, ordering by any of the six metrics (cpu, cpu_request,
cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit.
The response type is ''list'' for the default k8s.pod.uid grouping (each row
is one pod with its current phase) or ''grouped_list'' for custom groupBy
keys (each row aggregates pods in the group with per-phase counts under podCountsByPhase:
{ pending, running, succeeded, failed, unknown } derived from each pod''s
latest phase in the window). Also reports whether the requested time range
falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest,
podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1
as a sentinel when no data is available for that field.'
is one pod with its current status) or ''grouped_list'' for custom groupBy
keys (each row aggregates pods in the group with per-status counts under podCountsByStatus:
{ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull,
createContainerConfigError, containerCreating, oomKilled, completed, error,
containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError
} derived from each pod''s latest kubectl-style display status in the window).
Also reports whether the requested time range falls before the data retention
boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory,
podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no
data is available for that field.'
operationId: ListPods
requestBody:
content:
@@ -16765,16 +16810,19 @@ paths:
statefulSetCPULimit, statefulSetMemoryRequest, statefulSetMemoryLimit). Each
row also reports the latest known desiredPods (k8s.statefulset.desired_pods)
and currentPods (k8s.statefulset.current_pods) replica counts and per-group
podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each
pod''s latest k8s.pod.phase value). Each statefulset includes metadata attributes
(k8s.statefulset.name, k8s.namespace.name, k8s.cluster.name). The response
type is ''list'' for the default k8s.statefulset.name grouping or ''grouped_list''
for custom groupBy keys; in both modes every row aggregates pods owned by
statefulsets in the group. Supports filtering via a filter expression, custom
groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request
/ memory_limit / desired_pods / current_pods, and pagination via offset/limit.
Also reports whether the requested time range falls before the data retention
boundary. Numeric metric fields (statefulSetCPU, statefulSetCPURequest, statefulSetCPULimit,
podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff,
imagePullBackOff, errImagePull, createContainerConfigError, containerCreating,
oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost,
shutdown, unexpectedAdmissionError } reflecting each pod''s latest kubectl-style
display status). Each statefulset includes metadata attributes (k8s.statefulset.name,
k8s.namespace.name, k8s.cluster.name). The response type is ''list'' for the
default k8s.statefulset.name grouping or ''grouped_list'' for custom groupBy
keys; in both modes every row aggregates pods owned by statefulsets in the
group. Supports filtering via a filter expression, custom groupBy, ordering
by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit
/ desired_pods / current_pods, and pagination via offset/limit. Also reports
whether the requested time range falls before the data retention boundary.
Numeric metric fields (statefulSetCPU, statefulSetCPURequest, statefulSetCPULimit,
statefulSetMemory, statefulSetMemoryRequest, statefulSetMemoryLimit, desiredPods,
currentPods) return -1 as a sentinel when no data is available for that field.'
operationId: ListStatefulSets

View File

@@ -0,0 +1,11 @@
// Test stub for the Vite-only icon glob module: `import.meta.glob` can't be
// parsed by jest, so every test that transitively imports it is redirected here
// (see moduleNameMapper in jest.config.ts). Provides a minimal name → URL map so
// the resolver stays deterministic under test.
export const ICON_URLS: Record<string, string> = {
'eight-ball': 'mock-eight-ball-url',
};
export const LOGO_URLS: Record<string, string> = {
'aws-dark': 'mock-logo-url',
};

View File

@@ -15,6 +15,9 @@ const config: Config.InitialOptions = {
moduleNameMapper: {
'\\.(png|jpg|jpeg|gif|svg|webp|avif|ico|bmp|tiff)$':
'<rootDir>/__mocks__/fileMock.ts',
// The icon glob module uses `import.meta.glob` (Vite-only); jest can't parse
// it, so redirect any import of it to a stub.
'(^|/)iconAssets$': '<rootDir>/__mocks__/iconAssetsMock.ts',
'^@/(.*)$': '<rootDir>/src/$1',
'\\.(css|less|scss)$': '<rootDir>/__mocks__/cssMock.ts',
'\\.module\\.mjs$': '<rootDir>/__mocks__/cssMock.ts',

View File

@@ -64,7 +64,7 @@
"antd": "5.11.0",
"antd-table-saveas-excel": "2.2.1",
"antlr4": "4.13.2",
"axios": "1.16.0",
"axios": "1.18.0",
"babel-jest": "^29.6.4",
"chart.js": "3.9.1",
"chartjs-adapter-date-fns": "^2.0.0",
@@ -74,7 +74,7 @@
"crypto-js": "4.2.0",
"d3-hierarchy": "3.1.2",
"dayjs": "^1.10.7",
"dompurify": "3.4.11",
"dompurify": "3.4.12",
"event-source-polyfill": "1.0.31",
"eventemitter3": "5.0.1",
"history": "4.10.1",
@@ -89,7 +89,7 @@
"lodash-es": "^4.17.21",
"motion": "12.4.13",
"nuqs": "2.8.8",
"overlayscrollbars": "^2.8.1",
"overlayscrollbars": "^2.16.0",
"overlayscrollbars-react": "^0.5.6",
"papaparse": "5.4.1",
"posthog-js": "1.298.0",
@@ -199,9 +199,9 @@
"react-resizable": "3.0.4",
"redux-mock-store": "1.5.4",
"sass": "1.97.3",
"sharp": "0.34.5",
"sharp": "0.35.0",
"stylelint": "17.7.0",
"svgo": "4.0.1",
"svgo": "4.0.2",
"ts-jest": "29.4.9",
"typescript-plugin-css-modules": "5.2.0",
"use-sync-external-store": "1.6.0",
@@ -221,5 +221,18 @@
"*.(scss|css)": [
"stylelint"
]
},
"overrides": {
"@babel/core@<=7.29.0": ">=7.29.6 <8",
"@istanbuljs/load-nyc-config>js-yaml": ">=4.2.0 <5",
"cookie@<0.7.0": ">=0.7.1 <1",
"dompurify@<=3.4.10": ">=3.4.11 <4",
"esbuild@>=0.27.3 <0.28.1": ">=0.28.1 <0.29.0",
"js-cookie@<=3.0.5": ">=3.0.7 <4",
"js-yaml@>=4.0.0 <=4.1.1": ">=4.2.0 <5",
"prismjs@<1.30.0": ">=1.30.0 <2",
"react-router@>=6.7.0 <6.30.4": ">=6.30.4 <7",
"tmp@<0.2.6": ">=0.2.6 <0.3.0",
"yaml@>=1.0.0 <1.10.3": ">=1.10.3 <2"
}
}
}

461
frontend/pnpm-lock.yaml generated
View File

@@ -112,8 +112,8 @@ importers:
specifier: 4.13.2
version: 4.13.2
axios:
specifier: 1.16.0
version: 1.16.0
specifier: 1.18.0
version: 1.18.0
babel-jest:
specifier: ^29.6.4
version: 29.6.4(@babel/core@7.29.7)
@@ -142,8 +142,8 @@ importers:
specifier: ^1.10.7
version: 1.11.20
dompurify:
specifier: 3.4.11
version: 3.4.11
specifier: 3.4.12
version: 3.4.12
event-source-polyfill:
specifier: 1.0.31
version: 1.0.31
@@ -187,11 +187,11 @@ importers:
specifier: 2.8.8
version: 2.8.8(react-router-dom@5.3.4(react@18.2.0))(react-router@6.30.4(react@18.2.0))(react@18.2.0)
overlayscrollbars:
specifier: ^2.8.1
version: 2.9.2
specifier: ^2.16.0
version: 2.16.0
overlayscrollbars-react:
specifier: ^0.5.6
version: 0.5.6(overlayscrollbars@2.9.2)(react@18.2.0)
version: 0.5.6(overlayscrollbars@2.16.0)(react@18.2.0)
papaparse:
specifier: 5.4.1
version: 5.4.1
@@ -476,14 +476,14 @@ importers:
specifier: 1.97.3
version: 1.97.3
sharp:
specifier: 0.34.5
version: 0.34.5
specifier: 0.35.0
version: 0.35.0
stylelint:
specifier: 17.7.0
version: 17.7.0(typescript@5.9.3)
svgo:
specifier: 4.0.1
version: 4.0.1
specifier: 4.0.2
version: 4.0.2
ts-jest:
specifier: 29.4.9
version: 29.4.9(@babel/core@7.29.7)(@jest/transform@30.4.1)(@jest/types@30.2.0)(babel-jest@29.6.4(@babel/core@7.29.7))(esbuild@0.28.1)(jest-util@30.4.1)(jest@30.2.0(@types/node@16.18.25)(babel-plugin-macros@3.1.0)(ts-node@10.9.1(@types/node@16.18.25)(typescript@5.9.3)))(typescript@5.9.3)
@@ -501,7 +501,7 @@ importers:
version: 0.5.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))
vite-plugin-image-optimizer:
specifier: 2.0.3
version: 2.0.3(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(sharp@0.34.5)(svgo@4.0.1)
version: 2.0.3(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(sharp@0.35.0)(svgo@4.0.2)
vite-tsconfig-paths:
specifier: 6.1.1
version: 6.1.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(typescript@5.9.3)
@@ -1460,6 +1460,9 @@ packages:
'@emnapi/core@1.8.1':
resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
'@emnapi/runtime@1.11.2':
resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==}
'@emnapi/runtime@1.8.1':
resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
@@ -1714,156 +1717,165 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
'@img/colour@1.0.0':
resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==}
'@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'}
'@img/sharp-darwin-arm64@0.34.5':
resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-darwin-arm64@0.35.0':
resolution: {integrity: sha512-ZgaYEwaj+lx/5n4W8GmZ2IYz0PQHjN5eqRcfijWGB+2Aq7ZInZGa0qJyAn6DEtyLuWHRSrmWOqT9q3qqTBvmUQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [darwin]
'@img/sharp-darwin-x64@0.34.5':
resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-darwin-x64@0.35.0':
resolution: {integrity: sha512-c1z9LFpKB0slQW3RchwBE8iSVzGp70TNjUUO9k4BZwwW4HH7JBGHeIy4b+kk4n/kcBASb9evKCE3/7Slmslgiw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-darwin-arm64@1.2.4':
resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==}
'@img/sharp-freebsd-wasm32@0.35.0':
resolution: {integrity: sha512-Li2KTev0H90kEtnJHkI9xQojXt1AqWmFBMXiPw5kqd1jQgP7gi5HVK/qC5Rmh/59NuAwUuPzzPITmX22NomYYQ==}
engines: {node: '>=20.9.0'}
os: [freebsd]
'@img/sharp-libvips-darwin-arm64@1.3.0':
resolution: {integrity: sha512-EKbmBKtyTH+GPFDRw2TgK2oV6hyxxlJVIar4hoTYSNmIwipgMFdxPQqR392GmfdsPGWga0mCFN1cCKjRb9cljw==}
cpu: [arm64]
os: [darwin]
'@img/sharp-libvips-darwin-x64@1.2.4':
resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==}
'@img/sharp-libvips-darwin-x64@1.3.0':
resolution: {integrity: sha512-Pl2OmOvrJ42adUllESxBsG54PfXLo1OYg9i3c5/5Ln/qJ0gZuTM9YMhQJPIbXqwidLRc/c2zuHt4RsrymmNv7A==}
cpu: [x64]
os: [darwin]
'@img/sharp-libvips-linux-arm64@1.2.4':
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
'@img/sharp-libvips-linux-arm64@1.3.0':
resolution: {integrity: sha512-C0SqjoFKnszqa44EQ7xoaT48nnO0lOyXEULfXMWi8krrjOPGYkeK30Okzla6ATbBYsyZ0ySinK0FVkpv3DwzfQ==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-arm@1.2.4':
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
'@img/sharp-libvips-linux-arm@1.3.0':
resolution: {integrity: sha512-A8UpHoUDW4DwnXoV6+q3C1s7QLRAHtPDEjWuNZjwHMyoCNZnm0GeNN8ls9f/bsEYTRQRW96C/n34XJQHJ2fT7A==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-ppc64@1.2.4':
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
'@img/sharp-libvips-linux-ppc64@1.3.0':
resolution: {integrity: sha512-WOpkVxAjFd369iaIzEgNRreFD+gWdUMIGD5zplhNKNeqS6mm5dac3q2AFyCBmzYoAdouzZvRBgxy4z8QHZb4/A==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-riscv64@1.2.4':
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
'@img/sharp-libvips-linux-riscv64@1.3.0':
resolution: {integrity: sha512-DRWw0mOHusrCCuw2rqP87oLg6PGlkomVDFqw2hIwsSfwWpu4k3XLcBPaKKl6ct/GtL/cwNkgwjV/tc0Mqht3VA==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-s390x@1.2.4':
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
'@img/sharp-libvips-linux-s390x@1.3.0':
resolution: {integrity: sha512-9APy+nFWhHS+kzLgWZfLcyrUd7YqnAQVa4BPOo4xkoHpdoktOAPG4cEr9+Jpl0TtqfVmcMJimNL5qNTyyOHZNA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linux-x64@1.2.4':
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
'@img/sharp-libvips-linux-x64@1.3.0':
resolution: {integrity: sha512-y9RNUYDe2A1UAdhLyfeOodGRszQdaEoe4nfOpp/sNVPl2CWIcUyFaDoCh4vPLPxu19803j2naLqZup2WxDXCLA==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
'@img/sharp-libvips-linuxmusl-arm64@1.3.0':
resolution: {integrity: sha512-cC1wkC0Mlucd0KSiGrLkJnB/ZqPvZCntc/Lk7ZnYO5ZSbF2euNek4Xvxafojq+wN1q/W0eprdpUIjUr/EV2PBg==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
'@img/sharp-libvips-linuxmusl-x64@1.3.0':
resolution: {integrity: sha512-LiYMhUZicB1QG//+RvmYZpXJO8fYRENfp+MZUCnG9aw+AKvGAy9gPaCnuwsPcBFs8EV66M0NNxj9VHcNklE8zw==}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-linux-arm64@0.34.5':
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-arm64@0.35.0':
resolution: {integrity: sha512-4+4XHLNT5wDT0roYlHTEmH9lDKt0acf9Tv+3hM3iceOirkxrR404/3WjAYZ9F9CkHrxeRcGLJXbi4vluMZ9O+A==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-arm@0.34.5':
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-arm@0.35.0':
resolution: {integrity: sha512-VVlpEWwizEFIOom0zdoeKuO5nuTswzVE5uHcBNvHzmeHUpNFajY3HFfbQ+zIH4E2kVaZ/yVxmsShW56TtEy4uA==}
engines: {node: '>=20.9.0'}
cpu: [arm]
os: [linux]
libc: [glibc]
'@img/sharp-linux-ppc64@0.34.5':
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-ppc64@0.35.0':
resolution: {integrity: sha512-N3hzbEpUTJC8pWpPVJvgzGxM+so/MAXc8O2s/53B0LL9ZGpfXpME7Wizkc5d/8fRBlBtkDjzoZGDCqqNDHqLEw==}
engines: {node: '>=20.9.0'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-riscv64@0.34.5':
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-riscv64@0.35.0':
resolution: {integrity: sha512-l6vmKVPnbS0RhVMbyxP5meAARsbhCnBN4fy31qz0+3a6Rv4jEqfzDrT89y6ZPkCi0AJGnwp2En528yXo401Hpw==}
engines: {node: '>=20.9.0'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@img/sharp-linux-s390x@0.34.5':
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-s390x@0.35.0':
resolution: {integrity: sha512-MYlMiPFiv/EKPAHnp3yNZ9AAWFsxga9c5Bkc6wkar6bqzHLlkGVJHRm0u1ei+VXnZxp3Mz9MG9ZIsI8vSOf3sQ==}
engines: {node: '>=20.9.0'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@img/sharp-linux-x64@0.34.5':
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linux-x64@0.35.0':
resolution: {integrity: sha512-TYaItB5oj1ioXjhyn2xrR208vf+YuIIcHptQWRRaBmFhvIvL9D72DXN8w75xup0KXA8UdEAhQ9Qb2S49FD/9Cw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@img/sharp-linuxmusl-arm64@0.34.5':
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linuxmusl-arm64@0.35.0':
resolution: {integrity: sha512-DSTb6ijQzqe6DdAaOBVqJ/SYf1vO8EW5bK6X6LRXufEBebf2722VCdvBUtZ3rtV0x2ApfPNDy/p7LrrjaWjiyQ==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@img/sharp-linuxmusl-x64@0.34.5':
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-linuxmusl-x64@0.35.0':
resolution: {integrity: sha512-K7ykQ+26Rt6+4BTU80AuGgTPIYX86UxiAKT4rcXX/WNTo7k1ZxpKz+TguHnwVpCqQK3B5PK0vZ0ZBe6nz/ib1w==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [linux]
libc: [musl]
'@img/sharp-wasm32@0.34.5':
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-wasm32@0.35.0':
resolution: {integrity: sha512-9woLIFORERCr+6cWu87dQ22J34EExkhc73U1kZW0c+RclQqWetoodByp4dWZ/hN8/KVmTRAx2HOnUwib8AwZdA==}
engines: {node: '>=20.9.0'}
'@img/sharp-webcontainers-wasm32@0.35.0':
resolution: {integrity: sha512-t+kie1TOyaDM6Dho+f+y0VqIUNhYQaKCUahuZVi0E0frgdiaOaPsDxDW3wfKacUdaNBCnK/ZDBMg33ydvHj8uA==}
engines: {node: '>=20.9.0'}
cpu: [wasm32]
'@img/sharp-win32-arm64@0.34.5':
resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-win32-arm64@0.35.0':
resolution: {integrity: sha512-M5eKxug0dabbaWgFKvPa3odNs2OpaP+81NASfGKkt4GcYXpNhSu7CaeYxWkLNV6vHmUp4hnCxnxrUyhUJhXbKA==}
engines: {node: '>=20.9.0'}
cpu: [arm64]
os: [win32]
'@img/sharp-win32-ia32@0.34.5':
resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-win32-ia32@0.35.0':
resolution: {integrity: sha512-z0+pZ03QCDvdVN0Ez9IX/yjWC19ikMlXrmdYMwYNLTh2BLPx3hXWPvyqWfquZ0BTO9O6GVOjIVoTcyyacMnWlQ==}
engines: {node: ^20.9.0}
cpu: [ia32]
os: [win32]
'@img/sharp-win32-x64@0.34.5':
resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
'@img/sharp-win32-x64@0.35.0':
resolution: {integrity: sha512-feNnlz5ZHKr0MY1LPHvZQyJeBkbo4ctsn0D8FvA53VTw5TC63rfEL2UrWbkSBR19htSE7Mw78xYVwdJqoMWVHw==}
engines: {node: '>=20.9.0'}
cpu: [x64]
os: [win32]
@@ -4075,8 +4087,8 @@ packages:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
axios@1.16.0:
resolution: {integrity: sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==}
axios@1.18.0:
resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==}
babel-jest@29.6.4:
resolution: {integrity: sha512-meLj23UlSLddj6PC+YTOFRgDAtjnZom8w/ACsrx0gtPtv5cJZk0A5Unk5bV4wixD7XaPCN1fQvpww8czkZURmw==}
@@ -4881,8 +4893,8 @@ packages:
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
engines: {node: '>= 4'}
dompurify@3.4.11:
resolution: {integrity: sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==}
dompurify@3.4.12:
resolution: {integrity: sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==}
domutils@2.8.0:
resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==}
@@ -5448,10 +5460,6 @@ packages:
resolution: {integrity: sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==}
engines: {node: '>=20'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
@@ -6941,8 +6949,8 @@ packages:
overlayscrollbars: ^2.0.0
react: '>=16.8.0'
overlayscrollbars@2.9.2:
resolution: {integrity: sha512-iDT84r39i7oWP72diZN2mbJUsn/taCq568aQaIrc84S87PunBT7qtsVltAF2esk7ORTRjQDnfjVYoqqTzgs8QA==}
overlayscrollbars@2.16.0:
resolution: {integrity: sha512-N03oje/q7j93D0aLZtoCdsDSYLmhheSsv8H7oSLE7HhdV9P/bmCURtLV/KbPye7P/bpfyt/obSfDpGUYoJ0OWg==}
oxfmt@0.54.0:
resolution: {integrity: sha512-DjnMwn7smSLF+Mc2+pRItnuPftm/dkUFpY/d4+33y9TfKrsHZo8GLhmUg9BrOIUEy94Rlom1Q11N6vuhE+e0oQ==}
@@ -8062,10 +8070,6 @@ packages:
sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
sax@1.4.4:
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
engines: {node: '>=11.0.0'}
sax@1.6.0:
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
engines: {node: '>=11.0.0'}
@@ -8122,9 +8126,9 @@ packages:
shallowequal@1.1.0:
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
sharp@0.34.5:
resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==}
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
sharp@0.35.0:
resolution: {integrity: sha512-BqvG5XbwPZ4NV0DK90d86leEECMsoa8bO0nqnKWlBDYxri4GJ7c4EDInaF6q20lTh/mATmnDIKWJFfXnoVfH5g==}
engines: {node: '>=20.9.0'}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@@ -8376,8 +8380,8 @@ packages:
svg-tags@1.0.0:
resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==}
svgo@4.0.1:
resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==}
svgo@4.0.2:
resolution: {integrity: sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng==}
engines: {node: '>=16'}
hasBin: true
@@ -10281,6 +10285,11 @@ snapshots:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.11.2':
dependencies:
tslib: 2.8.1
optional: true
'@emnapi/runtime@1.8.1':
dependencies:
tslib: 2.8.1
@@ -10444,7 +10453,7 @@ snapshots:
'@types/string-hash': 1.1.3
d3-interpolate: 3.0.1
date-fns: 4.1.0
dompurify: 3.4.11
dompurify: 3.4.12
eventemitter3: 5.0.1
fast_array_intersect: 1.1.0
history: 4.10.1
@@ -10485,100 +10494,110 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
'@img/colour@1.0.0': {}
'@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5':
'@img/sharp-darwin-arm64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-arm64': 1.3.0
optional: true
'@img/sharp-darwin-x64@0.34.5':
'@img/sharp-darwin-x64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.3.0
optional: true
'@img/sharp-libvips-darwin-arm64@1.2.4':
optional: true
'@img/sharp-libvips-darwin-x64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linux-arm@1.2.4':
optional: true
'@img/sharp-libvips-linux-ppc64@1.2.4':
optional: true
'@img/sharp-libvips-linux-riscv64@1.2.4':
optional: true
'@img/sharp-libvips-linux-s390x@1.2.4':
optional: true
'@img/sharp-libvips-linux-x64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
optional: true
'@img/sharp-linux-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.2.4
optional: true
'@img/sharp-linux-arm@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.2.4
optional: true
'@img/sharp-linux-ppc64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.2.4
optional: true
'@img/sharp-linux-riscv64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.2.4
optional: true
'@img/sharp-linux-s390x@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.2.4
optional: true
'@img/sharp-linux-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.2.4
optional: true
'@img/sharp-linuxmusl-arm64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
optional: true
'@img/sharp-linuxmusl-x64@0.34.5':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
optional: true
'@img/sharp-wasm32@0.34.5':
'@img/sharp-freebsd-wasm32@0.35.0':
dependencies:
'@emnapi/runtime': 1.8.1
'@img/sharp-wasm32': 0.35.0
optional: true
'@img/sharp-win32-arm64@0.34.5':
'@img/sharp-libvips-darwin-arm64@1.3.0':
optional: true
'@img/sharp-win32-ia32@0.34.5':
'@img/sharp-libvips-darwin-x64@1.3.0':
optional: true
'@img/sharp-win32-x64@0.34.5':
'@img/sharp-libvips-linux-arm64@1.3.0':
optional: true
'@img/sharp-libvips-linux-arm@1.3.0':
optional: true
'@img/sharp-libvips-linux-ppc64@1.3.0':
optional: true
'@img/sharp-libvips-linux-riscv64@1.3.0':
optional: true
'@img/sharp-libvips-linux-s390x@1.3.0':
optional: true
'@img/sharp-libvips-linux-x64@1.3.0':
optional: true
'@img/sharp-libvips-linuxmusl-arm64@1.3.0':
optional: true
'@img/sharp-libvips-linuxmusl-x64@1.3.0':
optional: true
'@img/sharp-linux-arm64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-arm64': 1.3.0
optional: true
'@img/sharp-linux-arm@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-arm': 1.3.0
optional: true
'@img/sharp-linux-ppc64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-ppc64': 1.3.0
optional: true
'@img/sharp-linux-riscv64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-riscv64': 1.3.0
optional: true
'@img/sharp-linux-s390x@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-s390x': 1.3.0
optional: true
'@img/sharp-linux-x64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linux-x64': 1.3.0
optional: true
'@img/sharp-linuxmusl-arm64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-arm64': 1.3.0
optional: true
'@img/sharp-linuxmusl-x64@0.35.0':
optionalDependencies:
'@img/sharp-libvips-linuxmusl-x64': 1.3.0
optional: true
'@img/sharp-wasm32@0.35.0':
dependencies:
'@emnapi/runtime': 1.11.2
optional: true
'@img/sharp-webcontainers-wasm32@0.35.0':
dependencies:
'@img/sharp-wasm32': 0.35.0
optional: true
'@img/sharp-win32-arm64@0.35.0':
optional: true
'@img/sharp-win32-ia32@0.35.0':
optional: true
'@img/sharp-win32-x64@0.35.0':
optional: true
'@isaacs/cliui@8.0.2':
@@ -12983,13 +13002,15 @@ snapshots:
dependencies:
possible-typed-array-names: 1.1.0
axios@1.16.0:
axios@1.18.0:
dependencies:
follow-redirects: 1.16.0
form-data: 4.0.6
https-proxy-agent: 5.0.1
proxy-from-env: 2.1.0
transitivePeerDependencies:
- debug
- supports-color
babel-jest@29.6.4(@babel/core@7.29.7):
dependencies:
@@ -13843,7 +13864,7 @@ snapshots:
dependencies:
domelementtype: 2.3.0
dompurify@3.4.11:
dompurify@3.4.12:
optionalDependencies:
'@types/trusted-types': 2.0.7
@@ -14498,10 +14519,6 @@ snapshots:
dependencies:
hookified: 1.15.1
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@@ -14738,7 +14755,7 @@ snapshots:
internal-slot@1.1.0:
dependencies:
es-errors: 1.3.0
hasown: 2.0.2
hasown: 2.0.4
side-channel: 1.1.0
internmap@2.0.3: {}
@@ -14792,7 +14809,7 @@ snapshots:
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
hasown: 2.0.4
is-date-object@1.1.0:
dependencies:
@@ -14861,7 +14878,7 @@ snapshots:
call-bound: 1.0.4
gopd: 1.2.0
has-tostringtag: 1.0.2
hasown: 2.0.2
hasown: 2.0.4
is-set@2.0.3: {}
@@ -16177,7 +16194,7 @@ snapshots:
monaco-editor@0.55.1:
dependencies:
dompurify: 3.4.11
dompurify: 3.4.12
marked: 14.0.0
motion-dom@11.18.1:
@@ -16272,7 +16289,7 @@ snapshots:
dependencies:
debug: 3.2.7
iconv-lite: 0.6.3
sax: 1.4.4
sax: 1.6.0
transitivePeerDependencies:
- supports-color
optional: true
@@ -16456,12 +16473,12 @@ snapshots:
outvariant@1.4.0: {}
overlayscrollbars-react@0.5.6(overlayscrollbars@2.9.2)(react@18.2.0):
overlayscrollbars-react@0.5.6(overlayscrollbars@2.16.0)(react@18.2.0):
dependencies:
overlayscrollbars: 2.9.2
overlayscrollbars: 2.16.0
react: 18.2.0
overlayscrollbars@2.9.2: {}
overlayscrollbars@2.16.0: {}
oxfmt@0.54.0:
dependencies:
@@ -17741,9 +17758,6 @@ snapshots:
sax@1.3.0:
optional: true
sax@1.4.4:
optional: true
sax@1.6.0: {}
saxes@6.0.0:
@@ -17797,36 +17811,37 @@ snapshots:
shallowequal@1.1.0: {}
sharp@0.34.5:
sharp@0.35.0:
dependencies:
'@img/colour': 1.0.0
'@img/colour': 1.1.0
detect-libc: 2.1.2
semver: 7.8.5
optionalDependencies:
'@img/sharp-darwin-arm64': 0.34.5
'@img/sharp-darwin-x64': 0.34.5
'@img/sharp-libvips-darwin-arm64': 1.2.4
'@img/sharp-libvips-darwin-x64': 1.2.4
'@img/sharp-libvips-linux-arm': 1.2.4
'@img/sharp-libvips-linux-arm64': 1.2.4
'@img/sharp-libvips-linux-ppc64': 1.2.4
'@img/sharp-libvips-linux-riscv64': 1.2.4
'@img/sharp-libvips-linux-s390x': 1.2.4
'@img/sharp-libvips-linux-x64': 1.2.4
'@img/sharp-libvips-linuxmusl-arm64': 1.2.4
'@img/sharp-libvips-linuxmusl-x64': 1.2.4
'@img/sharp-linux-arm': 0.34.5
'@img/sharp-linux-arm64': 0.34.5
'@img/sharp-linux-ppc64': 0.34.5
'@img/sharp-linux-riscv64': 0.34.5
'@img/sharp-linux-s390x': 0.34.5
'@img/sharp-linux-x64': 0.34.5
'@img/sharp-linuxmusl-arm64': 0.34.5
'@img/sharp-linuxmusl-x64': 0.34.5
'@img/sharp-wasm32': 0.34.5
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
'@img/sharp-darwin-arm64': 0.35.0
'@img/sharp-darwin-x64': 0.35.0
'@img/sharp-freebsd-wasm32': 0.35.0
'@img/sharp-libvips-darwin-arm64': 1.3.0
'@img/sharp-libvips-darwin-x64': 1.3.0
'@img/sharp-libvips-linux-arm': 1.3.0
'@img/sharp-libvips-linux-arm64': 1.3.0
'@img/sharp-libvips-linux-ppc64': 1.3.0
'@img/sharp-libvips-linux-riscv64': 1.3.0
'@img/sharp-libvips-linux-s390x': 1.3.0
'@img/sharp-libvips-linux-x64': 1.3.0
'@img/sharp-libvips-linuxmusl-arm64': 1.3.0
'@img/sharp-libvips-linuxmusl-x64': 1.3.0
'@img/sharp-linux-arm': 0.35.0
'@img/sharp-linux-arm64': 0.35.0
'@img/sharp-linux-ppc64': 0.35.0
'@img/sharp-linux-riscv64': 0.35.0
'@img/sharp-linux-s390x': 0.35.0
'@img/sharp-linux-x64': 0.35.0
'@img/sharp-linuxmusl-arm64': 0.35.0
'@img/sharp-linuxmusl-x64': 0.35.0
'@img/sharp-webcontainers-wasm32': 0.35.0
'@img/sharp-win32-arm64': 0.35.0
'@img/sharp-win32-ia32': 0.35.0
'@img/sharp-win32-x64': 0.35.0
shebang-command@2.0.0:
dependencies:
@@ -18124,7 +18139,7 @@ snapshots:
svg-tags@1.0.0: {}
svgo@4.0.1:
svgo@4.0.2:
dependencies:
commander: 11.1.0
css-select: 5.2.2
@@ -18591,14 +18606,14 @@ snapshots:
pathe: 0.2.0
vite: rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4)
vite-plugin-image-optimizer@2.0.3(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(sharp@0.34.5)(svgo@4.0.1):
vite-plugin-image-optimizer@2.0.3(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(sharp@0.35.0)(svgo@4.0.2):
dependencies:
ansi-colors: 4.1.3
pathe: 2.0.3
vite: rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4)
optionalDependencies:
sharp: 0.34.5
svgo: 4.0.1
sharp: 0.35.0
svgo: 4.0.2
vite-tsconfig-paths@6.1.1(rolldown-vite@7.3.1(@types/node@16.18.25)(esbuild@0.28.1)(jiti@2.6.1)(less@4.4.0)(sass@1.97.3)(stylus@0.62.0)(terser@5.46.2)(yaml@2.8.4))(typescript@5.9.3):
dependencies:

View File

@@ -0,0 +1,20 @@
import { ENVIRONMENT } from 'constants/env';
import { server } from 'mocks-server/server';
import { rest } from 'msw';
export function mockFieldsAPIsWithEmptyResponse(): void {
server.use(
rest.get(`${ENVIRONMENT.baseURL}/api/v1/fields/keys`, (_, res, ctx) =>
res(
ctx.status(200),
ctx.json({ status: 'success', data: { keys: {}, complete: true } }),
),
),
rest.get(`${ENVIRONMENT.baseURL}/api/v1/fields/values`, (_, res, ctx) =>
res(
ctx.status(200),
ctx.json({ status: 'success', data: { values: {}, complete: true } }),
),
),
);
}

View File

@@ -2,29 +2,6 @@ import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import APIError from 'types/api/error';
// The wire shape these handlers can actually rely on. The generated
// RenderErrorResponseDTO marks code/message/url/errors as required, but the
// server omits any of them even on valid errors (e.g. a 400 with just a
// message), so a present `error` object is all the guard can guarantee.
type ErrorEnvelope = {
error: {
code?: string;
message?: string;
url?: string;
errors?: { message?: string }[];
};
};
function isErrorEnvelope(data: unknown): data is ErrorEnvelope {
return (
typeof data === 'object' &&
data !== null &&
'error' in data &&
typeof (data as ErrorEnvelope).error === 'object' &&
(data as ErrorEnvelope).error !== null
);
}
// @deprecated Use convertToApiError instead
export function ErrorResponseHandlerForGeneratedAPIs(
error: AxiosError<RenderErrorResponseDTO>,
@@ -33,29 +10,15 @@ export function ErrorResponseHandlerForGeneratedAPIs(
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (response) {
// The body isn't guaranteed to be an error envelope — e.g. a gateway 5xx
// with an HTML/empty body during a deploy. Verify the shape before reading
// it; otherwise synthesize a consistent error from the status.
const data: unknown = response.data;
if (isErrorEnvelope(data)) {
const { code, message, url, errors } = data.error;
throw new APIError({
httpStatusCode: response.status || 500,
error: {
code: code ?? '',
message: message ?? '',
url: url ?? '',
errors: (errors ?? []).map((e) => ({ message: e.message ?? '' })),
},
});
}
throw new APIError({
httpStatusCode: response.status || 500,
error: {
code: 'UPSTREAM_UNAVAILABLE',
message: error.message || 'Something went wrong',
url: '',
errors: [],
code: response.data.error.code,
message: response.data.error.message,
url: response.data.error.url ?? '',
errors: (response.data.error.errors ?? []).map((e) => ({
message: e.message ?? '',
})),
},
});
}
@@ -99,7 +62,9 @@ export function convertToApiError(
return new APIError({
httpStatusCode: response?.status || error.status || 500,
error: {
code: errorData?.code || 'UPSTREAM_UNAVAILABLE',
code:
errorData?.code ||
String(response?.status || error.code || 'unknown_error'),
message:
errorData?.message ||
response?.statusText ||

View File

@@ -2,38 +2,19 @@ import { AxiosError } from 'axios';
import { ErrorV2Resp } from 'types/api';
import APIError from 'types/api/error';
function isErrorV2Resp(data: unknown): data is ErrorV2Resp {
return (
typeof data === 'object' &&
data !== null &&
'error' in data &&
typeof (data as ErrorV2Resp).error?.code === 'string'
);
}
// reference - https://axios-http.com/docs/handling_errors
export function ErrorResponseHandlerV2(error: AxiosError<ErrorV2Resp>): never {
const { response, request } = error;
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
if (response) {
// response.data isn't guaranteed to be a V2 envelope (e.g. a gateway 5xx
// with an HTML/empty body during a deploy), so verify the shape first.
const data: unknown = response.data;
if (isErrorV2Resp(data)) {
const { code, message, url, errors } = data.error;
throw new APIError({
httpStatusCode: response.status || 500,
error: { code, message, url, errors },
});
}
throw new APIError({
httpStatusCode: response.status || 500,
error: {
code: 'UPSTREAM_UNAVAILABLE',
message: error.message || 'Something went wrong',
url: '',
errors: [],
code: response.data.error.code,
message: response.data.error.message,
url: response.data.error.url,
errors: response.data.error.errors,
},
});
}

View File

@@ -1,126 +0,0 @@
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { ErrorV2Resp } from 'types/api';
import APIError from 'types/api/error';
function asAxiosError(partial: Partial<AxiosError>): AxiosError<ErrorV2Resp> {
return partial as AxiosError<ErrorV2Resp>;
}
// ErrorResponseHandlerV2 always throws — capture the APIError so assertions stay
// unconditional.
function runHandler(error: AxiosError<ErrorV2Resp>): APIError {
try {
ErrorResponseHandlerV2(error);
} catch (thrown) {
return thrown as APIError;
}
throw new Error('expected ErrorResponseHandlerV2 to throw');
}
type ExpectedError = {
httpStatusCode: number;
code: string;
message: string;
errors: { message: string }[];
};
// One row per response shape the handler must normalize. New shapes (with
// different bodies) can be added here without a new test block.
const cases: {
name: string;
error: AxiosError<ErrorV2Resp>;
expected: ExpectedError;
}[] = [
{
name: 'well-formed V2 error envelope',
error: asAxiosError({
message: 'Request failed with status code 400',
response: {
status: 400,
data: {
error: {
code: 'bad_request',
message: 'Invalid dashboard payload',
url: 'https://signoz.io/docs',
errors: [{ message: 'name is required' }, { message: 'name too long' }],
},
},
} as AxiosError['response'],
}),
expected: {
httpStatusCode: 400,
code: 'bad_request',
message: 'Invalid dashboard payload',
errors: [{ message: 'name is required' }, { message: 'name too long' }],
},
},
{
// Regression: during a deployment the gateway returns a 5xx with a
// non-envelope body. Reading response.data.error.code used to throw a
// TypeError from inside the handler itself. See engineering-pod#5760.
name: '5xx with a non-envelope HTML body',
error: asAxiosError({
message: 'Request failed with status code 503',
response: {
status: 503,
data: '<html><body>503 Service Temporarily Unavailable</body></html>',
} as AxiosError['response'],
}),
expected: {
httpStatusCode: 503,
code: 'UPSTREAM_UNAVAILABLE',
message: 'Request failed with status code 503',
errors: [],
},
},
{
name: '5xx with an empty body',
error: asAxiosError({
message: 'Request failed with status code 502',
response: {
status: 502,
data: undefined,
} as AxiosError['response'],
}),
expected: {
httpStatusCode: 502,
code: 'UPSTREAM_UNAVAILABLE',
message: 'Request failed with status code 502',
errors: [],
},
},
{
name: 'no response received (network error)',
error: asAxiosError({
message: 'Network Error',
code: 'ERR_NETWORK',
name: 'AxiosError',
request: {},
}),
expected: {
httpStatusCode: 500,
code: 'ERR_NETWORK',
message: 'Network Error',
errors: [],
},
},
];
describe('ErrorResponseHandlerV2', () => {
it.each(cases)(
'normalizes $name into a consistent APIError',
({ error, expected }) => {
const apiError = runHandler(error);
expect(apiError).toBeInstanceOf(APIError);
expect(apiError.getHttpStatusCode()).toBe(expected.httpStatusCode);
expect(apiError.getErrorCode()).toBe(expected.code);
expect(apiError.getErrorMessage()).toBe(expected.message);
// The sub-error messages feed several parts of the UI, so assert them.
expect(apiError.getErrorDetails().error.errors).toStrictEqual(
expected.errors,
);
},
);
});

View File

@@ -136,7 +136,7 @@ export const invalidateGetChecks = async (
};
/**
* Returns a paginated list of Kubernetes clusters with key aggregated metrics derived by summing per-node values within the group: CPU usage, CPU allocatable, memory working set, memory allocatable. Each row also reports per-group nodeCountsByReadiness ({ ready, notReady } from each node's latest k8s.node.condition_ready value) and per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each cluster includes metadata attributes (k8s.cluster.name). The response type is 'list' for the default k8s.cluster.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates nodes and pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (clusterCPU, clusterCPUAllocatable, clusterMemory, clusterMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes clusters with key aggregated metrics derived by summing per-node values within the group: CPU usage, CPU allocatable, memory working set, memory allocatable. Each row also reports per-group nodeCountsByReadiness ({ ready, notReady } from each node's latest k8s.node.condition_ready value) and per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status). Each cluster includes metadata attributes (k8s.cluster.name). The response type is 'list' for the default k8s.cluster.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates nodes and pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (clusterCPU, clusterCPUAllocatable, clusterMemory, clusterMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
* @summary List Clusters for Infra Monitoring
*/
export const listClusters = (
@@ -219,7 +219,7 @@ export const useListClusters = <
return useMutation(getListClustersMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on), currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on), readyNodes (k8s.daemonset.ready_nodes, the number of nodes running at least one ready daemon pod) and misscheduledNodes (k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon pod but not supposed to) — note these are node counts, not pod counts. It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes, readyNodes, misscheduledNodes) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes DaemonSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the daemonset, plus average CPU/memory request and limit utilization (daemonSetCPURequest, daemonSetCPULimit, daemonSetMemoryRequest, daemonSetMemoryLimit). Each row also reports the latest known node-level counters from kube-state-metrics: desiredNodes (k8s.daemonset.desired_scheduled_nodes, the number of nodes the daemonset wants to run on), currentNodes (k8s.daemonset.current_scheduled_nodes, the number of nodes the daemonset currently runs on), readyNodes (k8s.daemonset.ready_nodes, the number of nodes running at least one ready daemon pod) and misscheduledNodes (k8s.daemonset.misscheduled_nodes, the number of nodes running the daemon pod but not supposed to) — note these are node counts, not pod counts. It also reports per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status). Each daemonset includes metadata attributes (k8s.daemonset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.daemonset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by daemonsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_nodes / current_nodes / ready_nodes / misscheduled_nodes, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (daemonSetCPU, daemonSetCPURequest, daemonSetCPULimit, daemonSetMemory, daemonSetMemoryRequest, daemonSetMemoryLimit, desiredNodes, currentNodes, readyNodes, misscheduledNodes) return -1 as a sentinel when no data is available for that field.
* @summary List DaemonSets for Infra Monitoring
*/
export const listDaemonSets = (
@@ -302,7 +302,7 @@ export const useListDaemonSets = <
return useMutation(getListDaemonSetsMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes Deployments with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the deployment, plus average CPU/memory request and limit utilization (deploymentCPURequest, deploymentCPULimit, deploymentMemoryRequest, deploymentMemoryLimit). Each row also reports the latest known desiredPods (k8s.deployment.desired) and availablePods (k8s.deployment.available) replica counts and per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each deployment includes metadata attributes (k8s.deployment.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.deployment.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by deployments in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_pods / available_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (deploymentCPU, deploymentCPURequest, deploymentCPULimit, deploymentMemory, deploymentMemoryRequest, deploymentMemoryLimit, desiredPods, availablePods) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes Deployments with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the deployment, plus average CPU/memory request and limit utilization (deploymentCPURequest, deploymentCPULimit, deploymentMemoryRequest, deploymentMemoryLimit). Each row also reports the latest known desiredPods (k8s.deployment.desired) and availablePods (k8s.deployment.available) replica counts and per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status). Each deployment includes metadata attributes (k8s.deployment.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.deployment.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by deployments in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_pods / available_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (deploymentCPU, deploymentCPURequest, deploymentCPULimit, deploymentMemory, deploymentMemoryRequest, deploymentMemoryLimit, desiredPods, availablePods) return -1 as a sentinel when no data is available for that field.
* @summary List Deployments for Infra Monitoring
*/
export const listDeployments = (
@@ -468,7 +468,7 @@ export const useListHosts = <
return useMutation(getListHostsMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes Jobs with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the job, plus average CPU/memory request and limit utilization (jobCPURequest, jobCPULimit, jobMemoryRequest, jobMemoryLimit). Each row also reports the latest known job-level counters from kube-state-metrics: desiredSuccessfulPods (k8s.job.desired_successful_pods, the target completion count), activePods (k8s.job.active_pods), failedPods (k8s.job.failed_pods, cumulative across the lifetime of the job), and successfulPods (k8s.job.successful_pods, cumulative). It also reports per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value); note podCountsByPhase.failed (current pod-phase) is distinct from failedPods (cumulative job kube-state-metric). Each job includes metadata attributes (k8s.job.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.job.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by jobs in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_successful_pods / active_pods / failed_pods / successful_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (jobCPU, jobCPURequest, jobCPULimit, jobMemory, jobMemoryRequest, jobMemoryLimit, desiredSuccessfulPods, activePods, failedPods, successfulPods) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes Jobs with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the job, plus average CPU/memory request and limit utilization (jobCPURequest, jobCPULimit, jobMemoryRequest, jobMemoryLimit). Each row also reports the latest known job-level counters from kube-state-metrics: desiredSuccessfulPods (k8s.job.desired_successful_pods, the target completion count), activePods (k8s.job.active_pods), failedPods (k8s.job.failed_pods, cumulative across the lifetime of the job), and successfulPods (k8s.job.successful_pods, cumulative). It also reports per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status); note podCountsByStatus.failed (current pod status) is distinct from failedPods (cumulative job kube-state-metric). Each job includes metadata attributes (k8s.job.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.job.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by jobs in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_successful_pods / active_pods / failed_pods / successful_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (jobCPU, jobCPURequest, jobCPULimit, jobMemory, jobMemoryRequest, jobMemoryLimit, desiredSuccessfulPods, activePods, failedPods, successfulPods) return -1 as a sentinel when no data is available for that field.
* @summary List Jobs for Infra Monitoring
*/
export const listJobs = (
@@ -634,7 +634,7 @@ export const useListContainers = <
return useMutation(getListContainersMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes namespaces with key aggregated pod metrics: CPU usage and memory working set (summed across pods in the group), plus per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value in the window). Each namespace includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.namespace.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / memory, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes namespaces with key aggregated pod metrics: CPU usage and memory working set (summed across pods in the group), plus per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status in the window). Each namespace includes metadata attributes (k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.namespace.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / memory, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (namespaceCPU, namespaceMemory) return -1 as a sentinel when no data is available for that field.
* @summary List Namespaces for Infra Monitoring
*/
export const listNamespaces = (
@@ -717,7 +717,7 @@ export const useListNamespaces = <
return useMutation(getListNamespacesMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes nodes with key metrics: CPU usage, CPU allocatable, memory working set, memory allocatable, per-group nodeCountsByReadiness ({ ready, notReady } from each node's latest k8s.node.condition_ready in the window) and per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } for pods scheduled on the listed nodes). Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is 'list' for the default k8s.node.name grouping (each row is one node with its current condition string: ready / not_ready / no_data) or 'grouped_list' for custom groupBy keys (each row aggregates nodes in the group; condition stays no_data). Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes nodes with key metrics: CPU usage, CPU allocatable, memory working set, memory allocatable, per-group nodeCountsByReadiness ({ ready, notReady } from each node's latest k8s.node.condition_ready in the window) and per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } for pods scheduled on the listed nodes, reflecting each pod's latest kubectl-style display status). Each node includes metadata attributes (k8s.node.uid, k8s.cluster.name). The response type is 'list' for the default k8s.node.name grouping (each row is one node with its current condition string: ready / not_ready / no_data) or 'grouped_list' for custom groupBy keys (each row aggregates nodes in the group; condition stays no_data). Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_allocatable / memory / memory_allocatable, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (nodeCPU, nodeCPUAllocatable, nodeMemory, nodeMemoryAllocatable) return -1 as a sentinel when no data is available for that field.
* @summary List Nodes for Infra Monitoring
*/
export const listNodes = (
@@ -800,7 +800,7 @@ export const useListNodes = <
return useMutation(getListNodesMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod phase (pending/running/succeeded/failed/unknown/no_data), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current phase) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-phase counts under podCountsByPhase: { pending, running, succeeded, failed, unknown } derived from each pod's latest phase in the window). Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes pods with key metrics: CPU usage, CPU request/limit utilization, memory working set, memory request/limit utilization, current pod status (kubectl-style display status such as Running/Pending/CrashLoopBackOff, or no_data), and pod age (ms since start time). Each pod includes metadata attributes (namespace, node, workload owner such as deployment/statefulset/daemonset/job/cronjob, cluster). Supports filtering via a filter expression, custom groupBy to aggregate pods by any attribute, ordering by any of the six metrics (cpu, cpu_request, cpu_limit, memory, memory_request, memory_limit), and pagination via offset/limit. The response type is 'list' for the default k8s.pod.uid grouping (each row is one pod with its current status) or 'grouped_list' for custom groupBy keys (each row aggregates pods in the group with per-status counts under podCountsByStatus: { pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } derived from each pod's latest kubectl-style display status in the window). Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (podCPU, podCPURequest, podCPULimit, podMemory, podMemoryRequest, podMemoryLimit, podAge) return -1 as a sentinel when no data is available for that field.
* @summary List Pods for Infra Monitoring
*/
export const listPods = (
@@ -966,7 +966,7 @@ export const useListVolumes = <
return useMutation(getListVolumesMutationOptions(options));
};
/**
* Returns a paginated list of Kubernetes StatefulSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the statefulset, plus average CPU/memory request and limit utilization (statefulSetCPURequest, statefulSetCPULimit, statefulSetMemoryRequest, statefulSetMemoryLimit). Each row also reports the latest known desiredPods (k8s.statefulset.desired_pods) and currentPods (k8s.statefulset.current_pods) replica counts and per-group podCountsByPhase ({ pending, running, succeeded, failed, unknown } from each pod's latest k8s.pod.phase value). Each statefulset includes metadata attributes (k8s.statefulset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.statefulset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by statefulsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_pods / current_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (statefulSetCPU, statefulSetCPURequest, statefulSetCPULimit, statefulSetMemory, statefulSetMemoryRequest, statefulSetMemoryLimit, desiredPods, currentPods) return -1 as a sentinel when no data is available for that field.
* Returns a paginated list of Kubernetes StatefulSets with key aggregated pod metrics: CPU usage and memory working set summed across pods owned by the statefulset, plus average CPU/memory request and limit utilization (statefulSetCPURequest, statefulSetCPULimit, statefulSetMemoryRequest, statefulSetMemoryLimit). Each row also reports the latest known desiredPods (k8s.statefulset.desired_pods) and currentPods (k8s.statefulset.current_pods) replica counts and per-group podCountsByStatus ({ pending, running, failed, unknown, crashLoopBackOff, imagePullBackOff, errImagePull, createContainerConfigError, containerCreating, oomKilled, completed, error, containerCannotRun, evicted, nodeAffinity, nodeLost, shutdown, unexpectedAdmissionError } reflecting each pod's latest kubectl-style display status). Each statefulset includes metadata attributes (k8s.statefulset.name, k8s.namespace.name, k8s.cluster.name). The response type is 'list' for the default k8s.statefulset.name grouping or 'grouped_list' for custom groupBy keys; in both modes every row aggregates pods owned by statefulsets in the group. Supports filtering via a filter expression, custom groupBy, ordering by cpu / cpu_request / cpu_limit / memory / memory_request / memory_limit / desired_pods / current_pods, and pagination via offset/limit. Also reports whether the requested time range falls before the data retention boundary. Numeric metric fields (statefulSetCPU, statefulSetCPURequest, statefulSetCPULimit, statefulSetMemory, statefulSetMemoryRequest, statefulSetMemoryLimit, desiredPods, currentPods) return -1 as a sentinel when no data is available for that field.
* @summary List StatefulSets for Infra Monitoring
*/
export const listStatefulSets = (

View File

@@ -2814,6 +2814,7 @@ export enum CloudintegrationtypesServiceIDDTO {
cassandradb = 'cassandradb',
redis = 'redis',
cloudsql_postgres = 'cloudsql_postgres',
memorystore_redis = 'memorystore_redis',
}
export type CloudintegrationtypesCloudIntegrationServiceDTOAnyOf = {
/**
@@ -3235,17 +3236,6 @@ export interface CloudintegrationtypesUpdatableServiceDTO {
config: CloudintegrationtypesServiceConfigDTO;
}
export interface CommonDisplayDTO {
/**
* @type string
*/
description?: string;
/**
* @type string
*/
name?: string;
}
export interface CommonJSONRefDTO {
/**
* @type string
@@ -3989,44 +3979,6 @@ export interface DashboardtypesDashboardPanelRefDTO {
panelName: string;
}
export enum DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpecDTOKind {
'signoz/Datasource' = 'signoz/Datasource',
}
export interface DashboardtypesSigNozDatasourceSpecDTO {
[key: string]: unknown;
}
export interface DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpecDTO {
/**
* @enum signoz/Datasource
* @type string
*/
kind: DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpecDTOKind;
spec: DashboardtypesSigNozDatasourceSpecDTO;
}
export type DashboardtypesDatasourcePluginDTO =
DashboardtypesDatasourcePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesSigNozDatasourceSpecDTO;
export interface DashboardtypesDatasourceSpecDTO {
/**
* @type boolean
*/
default?: boolean;
display?: CommonDisplayDTO;
plugin?: DashboardtypesDatasourcePluginDTO;
}
export type DashboardtypesDashboardSpecDTODatasourcesAnyOf = {
[key: string]: DashboardtypesDatasourceSpecDTO;
};
/**
* @nullable
*/
export type DashboardtypesDashboardSpecDTODatasources =
DashboardtypesDashboardSpecDTODatasourcesAnyOf | null;
export enum DashboardtypesPanelKindDTO {
Panel = 'Panel',
}
@@ -4667,12 +4619,18 @@ export type DashboardtypesVariableDefaultValueDTO = string | string[];
export enum DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDynamicVariableSpecDTOKind {
'signoz/DynamicVariable' = 'signoz/DynamicVariable',
}
export enum DashboardtypesDynamicVariableSignalDTO {
traces = 'traces',
logs = 'logs',
metrics = 'metrics',
all = 'all',
}
export interface DashboardtypesDynamicVariableSpecDTO {
/**
* @type string
*/
name: string;
signal?: TelemetrytypesSignalDTO;
signal: DashboardtypesDynamicVariableSignalDTO;
}
export interface DashboardtypesVariablePluginVariantGithubComSigNozSignozPkgTypesDashboardtypesDynamicVariableSpecDTO {
@@ -4800,10 +4758,6 @@ export type DashboardtypesVariableDTO =
| DashboardtypesVariableEnvelopeGithubComSigNozSignozPkgTypesDashboardtypesTextVariableSpecDTO;
export interface DashboardtypesDashboardSpecDTO {
/**
* @type object,null
*/
datasources?: DashboardtypesDashboardSpecDTODatasources;
display: DashboardtypesDisplayDTO;
/**
* @type string
@@ -4879,9 +4833,6 @@ export interface DashboardtypesDashboardViewDTO {
updatedAt?: string;
}
export enum DashboardtypesDatasourcePluginKindDTO {
'signoz/Datasource' = 'signoz/Datasource',
}
export interface TagtypesGettableTagDTO {
/**
* @type string
@@ -5735,29 +5686,6 @@ export interface InframonitoringtypesNodeCountsByReadinessDTO {
ready: number;
}
export interface InframonitoringtypesPodCountsByPhaseDTO {
/**
* @type integer
*/
failed: number;
/**
* @type integer
*/
pending: number;
/**
* @type integer
*/
running: number;
/**
* @type integer
*/
succeeded: number;
/**
* @type integer
*/
unknown: number;
}
export interface InframonitoringtypesPodCountsByStatusDTO {
/**
* @type integer
@@ -5867,7 +5795,6 @@ export interface InframonitoringtypesClusterRecordDTO {
*/
meta: InframonitoringtypesClusterRecordDTOMeta;
nodeCountsByReadiness: InframonitoringtypesNodeCountsByReadinessDTO;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
}
@@ -6143,7 +6070,6 @@ export interface InframonitoringtypesDaemonSetRecordDTO {
* @type integer
*/
misscheduledNodes: number;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
/**
* @type integer
@@ -6225,7 +6151,6 @@ export interface InframonitoringtypesDeploymentRecordDTO {
* @type object,null
*/
meta: InframonitoringtypesDeploymentRecordDTOMeta;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
}
@@ -6392,7 +6317,6 @@ export interface InframonitoringtypesJobRecordDTO {
* @type object,null
*/
meta: InframonitoringtypesJobRecordDTOMeta;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
/**
* @type integer
@@ -6473,7 +6397,6 @@ export interface InframonitoringtypesNamespaceRecordDTO {
* @type string
*/
namespaceName: string;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
}
@@ -6540,7 +6463,6 @@ export interface InframonitoringtypesNodeRecordDTO {
* @type string
*/
nodeName: string;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
}
@@ -6561,14 +6483,6 @@ export interface InframonitoringtypesNodesDTO {
warning?: Querybuildertypesv5QueryWarnDataDTO;
}
export enum InframonitoringtypesPodPhaseDTO {
pending = 'pending',
running = 'running',
succeeded = 'succeeded',
failed = 'failed',
unknown = 'unknown',
no_data = 'no_data',
}
export type InframonitoringtypesPodRecordDTOMetaAnyOf = {
[key: string]: string;
};
@@ -6625,7 +6539,6 @@ export interface InframonitoringtypesPodRecordDTO {
* @format double
*/
podCPURequest: number;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
/**
* @type number
@@ -6642,7 +6555,6 @@ export interface InframonitoringtypesPodRecordDTO {
* @format double
*/
podMemoryRequest: number;
podPhase: InframonitoringtypesPodPhaseDTO;
/**
* @type integer
* @format int64
@@ -6992,7 +6904,6 @@ export interface InframonitoringtypesStatefulSetRecordDTO {
* @type object,null
*/
meta: InframonitoringtypesStatefulSetRecordDTOMeta;
podCountsByPhase: InframonitoringtypesPodCountsByPhaseDTO;
podCountsByStatus: InframonitoringtypesPodCountsByStatusDTO;
/**
* @type number
@@ -9270,6 +9181,48 @@ export interface SpantypesGettableSpanMapperGroupsDTO {
items: SpantypesSpanMapperGroupDTO[];
}
export type SpantypesSpanMapperTestSpanDTOAttributesAnyOf = {
[key: string]: unknown;
};
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOAttributes =
SpantypesSpanMapperTestSpanDTOAttributesAnyOf | null;
export type SpantypesSpanMapperTestSpanDTOResourceAnyOf = {
[key: string]: unknown;
};
/**
* @nullable
*/
export type SpantypesSpanMapperTestSpanDTOResource =
SpantypesSpanMapperTestSpanDTOResourceAnyOf | null;
export interface SpantypesSpanMapperTestSpanDTO {
/**
* @type object,null
*/
attributes?: SpantypesSpanMapperTestSpanDTOAttributes;
/**
* @type object,null
*/
resource?: SpantypesSpanMapperTestSpanDTOResource;
}
export interface SpantypesGettableSpanMapperTestDTO {
/**
* @type array,null
*/
collectorLogs?: string[] | null;
/**
* @type array,null
*/
spans?: SpantypesSpanMapperTestSpanDTO[] | null;
}
export enum SpantypesSpanMapperOperationDTO {
move = 'move',
copy = 'copy',
@@ -9611,6 +9564,33 @@ export interface SpantypesPostableSpanMapperGroupDTO {
name: string;
}
export interface SpantypesPostableSpanMapperTestGroupDTO {
condition: SpantypesSpanMapperGroupConditionDTO | null;
/**
* @type boolean
*/
enabled?: boolean;
/**
* @type array,null
*/
mappers?: SpantypesPostableSpanMapperDTO[] | null;
/**
* @type string
*/
name: string;
}
export interface SpantypesPostableSpanMapperTestDTO {
/**
* @type array,null
*/
groups: SpantypesPostableSpanMapperTestGroupDTO[] | null;
/**
* @type array,null
*/
spans: SpantypesSpanMapperTestSpanDTO[] | null;
}
export interface SpantypesSpanAggregationDTO {
aggregation: SpantypesSpanAggregationTypeDTO;
field: TelemetrytypesTelemetryFieldKeyDTO;
@@ -10961,6 +10941,14 @@ export type UpdateSpanMapperPathParameters = {
groupId: string;
mapperId: string;
};
export type TestSpanMappers200 = {
data: SpantypesGettableSpanMapperTestDTO;
/**
* @type string
*/
status: string;
};
export type GetStats200Data = { [key: string]: unknown };
export type GetStats200 = {

View File

@@ -30,8 +30,10 @@ import type {
RenderErrorResponseDTO,
SpantypesPostableSpanMapperDTO,
SpantypesPostableSpanMapperGroupDTO,
SpantypesPostableSpanMapperTestDTO,
SpantypesUpdatableSpanMapperDTO,
SpantypesUpdatableSpanMapperGroupDTO,
TestSpanMappers200,
UpdateSpanMapperGroupPathParameters,
UpdateSpanMapperPathParameters,
} from '../sigNoz.schemas';
@@ -780,3 +782,86 @@ export const useUpdateSpanMapper = <
> => {
return useMutation(getUpdateSpanMapperMutationOptions(options));
};
/**
* Tests how span mappers would transform sample spans
* @summary Test span mappers against sample spans
*/
export const testSpanMappers = (
spantypesPostableSpanMapperTestDTO?: BodyType<SpantypesPostableSpanMapperTestDTO>,
signal?: AbortSignal,
) => {
return GeneratedAPIInstance<TestSpanMappers200>({
url: `/api/v1/span_mapper_groups/test`,
method: 'POST',
headers: { 'Content-Type': 'application/json' },
data: spantypesPostableSpanMapperTestDTO,
signal,
});
};
export const getTestSpanMappersMutationOptions = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testSpanMappers>>,
TError,
{ data?: BodyType<SpantypesPostableSpanMapperTestDTO> },
TContext
>;
}): UseMutationOptions<
Awaited<ReturnType<typeof testSpanMappers>>,
TError,
{ data?: BodyType<SpantypesPostableSpanMapperTestDTO> },
TContext
> => {
const mutationKey = ['testSpanMappers'];
const { mutation: mutationOptions } = options
? options.mutation &&
'mutationKey' in options.mutation &&
options.mutation.mutationKey
? options
: { ...options, mutation: { ...options.mutation, mutationKey } }
: { mutation: { mutationKey } };
const mutationFn: MutationFunction<
Awaited<ReturnType<typeof testSpanMappers>>,
{ data?: BodyType<SpantypesPostableSpanMapperTestDTO> }
> = (props) => {
const { data } = props ?? {};
return testSpanMappers(data);
};
return { mutationFn, ...mutationOptions };
};
export type TestSpanMappersMutationResult = NonNullable<
Awaited<ReturnType<typeof testSpanMappers>>
>;
export type TestSpanMappersMutationBody =
| BodyType<SpantypesPostableSpanMapperTestDTO>
| undefined;
export type TestSpanMappersMutationError = ErrorType<RenderErrorResponseDTO>;
/**
* @summary Test span mappers against sample spans
*/
export const useTestSpanMappers = <
TError = ErrorType<RenderErrorResponseDTO>,
TContext = unknown,
>(options?: {
mutation?: UseMutationOptions<
Awaited<ReturnType<typeof testSpanMappers>>,
TError,
{ data?: BodyType<SpantypesPostableSpanMapperTestDTO> },
TContext
>;
}): UseMutationResult<
Awaited<ReturnType<typeof testSpanMappers>>,
TError,
{ data?: BodyType<SpantypesPostableSpanMapperTestDTO> },
TContext
> => {
return useMutation(getTestSpanMappersMutationOptions(options));
};

View File

@@ -1,26 +1,50 @@
import axios from 'api';
import { ErrorResponseHandlerV2 } from 'api/ErrorResponseHandlerV2';
import { AxiosError } from 'axios';
import { AxiosError, AxiosResponse } from 'axios';
import { ErrorV2Resp } from 'types/api';
import { ExportRawDataProps } from 'types/api/exportRawData/getExportRawData';
export interface FetchExportDataProps extends ExportRawDataProps {
signal?: AbortSignal;
}
async function postExportRawData({
format,
body,
signal,
}: FetchExportDataProps): Promise<AxiosResponse<Blob>> {
return axios.post<Blob>(
`export_raw_data?format=${encodeURIComponent(format)}`,
body,
{
responseType: 'blob',
decompress: true,
headers: {
Accept: 'application/octet-stream',
'Content-Type': 'application/json',
},
timeout: 0,
signal,
},
);
}
/**
* Fetches a single export_raw_data page and returns it as a Blob.
* Callers own retry/cancel/error UX.
*/
export async function fetchExportData(
props: FetchExportDataProps,
): Promise<Blob> {
const response = await postExportRawData(props);
return response.data;
}
export const downloadExportData = async (
props: ExportRawDataProps,
): Promise<void> => {
try {
const response = await axios.post<Blob>(
`export_raw_data?format=${encodeURIComponent(props.format)}`,
props.body,
{
responseType: 'blob',
decompress: true,
headers: {
Accept: 'application/octet-stream',
'Content-Type': 'application/json',
},
timeout: 0,
},
);
const response = await postExportRawData(props);
if (response.status !== 200) {
throw new Error(

View File

@@ -0,0 +1,7 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.9844 5.0511C7.9844 5.0511 3.17466 6.38853 2.79968 6.90476C2.4247 7.42098 1.77848 8.37718 1.71974 10.4246C1.67224 12.067 2.58844 16.2905 7.09195 16.4305C11.6905 16.5742 13.4029 13.5444 14.2241 11.6683C14.964 9.97584 14.9515 8.0322 15.4203 7.13974C15.889 6.24729 16.3115 5.47358 16.289 4.46488C16.2665 3.45619 15.8902 2.58748 15.2328 2.16501C14.5753 1.74253 14.0128 1.81252 13.7316 1.74253C13.4504 1.67253 7.9844 5.0511 7.9844 5.0511Z" fill="#757E40"/>
<path d="M8.82817 3.36241C6.83328 4.18361 4.18217 4.95732 2.91599 6.74098C1.7723 8.3509 1.39107 10.3533 2.93973 12.6994C4.35341 14.8418 8.12196 15.7555 10.9406 13.7319C13.5217 11.8782 13.7554 9.1571 14.5304 7.3497C15.3053 5.54229 16.9352 3.16742 14.9766 1.99998C12.888 0.757545 10.4043 2.71369 8.82817 3.36241Z" fill="#AFB42A"/>
<path d="M14.5304 2.51733C13.5817 1.96986 12.3717 2.21234 11.0343 2.86981C9.69688 3.52728 8.66444 4.01975 8.19571 4.18349C7.72574 4.34723 3.73345 5.80966 2.96349 7.74955C2.32977 9.34447 2.49476 11.4331 4.15967 12.958C5.82459 14.4829 10.0256 14.3892 11.6443 12.0893C13.2629 9.78945 13.3329 7.93704 13.6854 7.11584C14.0379 6.29463 14.8054 5.09219 15.0229 4.44098C15.2803 3.66727 15.1403 2.86981 14.5304 2.51733Z" fill="#FFF69D"/>
<path d="M9.93064 6.97475C9.17943 6.08355 6.31708 6.36478 5.33214 7.77221C4.34719 9.17963 4.75717 10.3233 5.23839 10.9395C5.82461 11.6908 7.42078 12.3707 8.87445 11.362C10.3281 10.3533 10.7543 7.9522 9.93064 6.97475Z" fill="#855C52"/>
<path d="M6.91449 8.57723C6.54451 9.1197 6.7295 9.99465 6.09328 10.0359C5.45707 10.0772 5.08708 9.05595 5.53831 8.201C6.02579 7.27855 7.18072 6.86732 7.50946 7.3048C7.83944 7.74352 7.26572 8.06226 6.91449 8.57723Z" fill="#D67659"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1,47 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.7781 9.56835C12.7781 9.56835 16.2542 8.88588 16.4192 10.1983C16.5154 10.972 16.2729 12.3944 14.963 13.5631C13.8756 14.5331 12.0582 15.3493 9.05957 15.2943C6.61595 15.2505 4.95354 14.6643 3.82485 14.0156C1.93495 12.9294 1.56122 11.457 1.65996 10.5333C1.80121 9.20836 3.92484 8.6009 4.02109 8.5459L12.7781 9.56835Z" fill="#F1B34F"/>
<path d="M1.91504 10.8869C1.91504 10.8869 3.91993 14.2142 9.13716 14.2317C14.7856 14.2517 16.5118 10.8781 16.418 10.1969C16.2405 8.89075 13.2307 8.62451 13.2307 8.62451L6.04857 10.3169L1.91504 9.8632V10.8869Z" fill="#FADFB1"/>
<path d="M1.93617 9.68343C1.93617 9.68343 1.61119 10.0734 1.66868 10.8171C1.71993 11.4771 2.3399 12.4495 3.42359 12.8008C4.38229 13.112 5.086 12.7545 6.07845 13.0757C7.0709 13.397 7.98585 14.3719 9.25203 14.2957C10.5182 14.2194 11.1907 13.3145 12.1044 13.182C13.4768 12.9833 14.133 13.352 15.1705 12.3283C16.3142 11.1996 16.3204 10.1484 16.3142 9.88716C16.3017 9.35969 15.9279 9.07471 15.9279 9.07471L3.3311 9.53593L1.93617 9.68343Z" fill="white"/>
<path d="M9.07957 2.68481C4.12483 2.68481 1.64246 5.27593 1.44747 8.23077C1.37248 9.36821 1.74246 10.1132 2.54867 10.8231C3.95609 12.0606 6.52471 12.8355 9.17832 12.8643C12.0219 12.8955 14.1793 12.1756 15.8305 9.99693C16.3692 9.28697 16.4279 8.88324 16.4504 8.09328C16.5192 5.61591 14.2905 2.68481 9.07957 2.68481Z" fill="url(#paint0_radial_721_145)"/>
<path d="M6.53842 7.24459C6.48217 7.12084 6.91715 6.9696 7.13339 6.81211C7.37088 6.63837 7.41462 6.43338 7.50087 6.42213C7.58711 6.41088 7.96584 6.52837 8.93954 6.54087C9.7945 6.55212 10.3357 6.32463 10.4107 6.38963C10.487 6.45463 10.5945 6.63837 10.8332 6.87711C11.0394 7.08334 11.2769 7.22334 11.2882 7.29958C11.2994 7.37583 10.7519 7.49207 10.6557 7.51332C10.5582 7.53457 7.43462 7.52582 7.43462 7.52582C7.43462 7.52582 7.25213 7.47207 7.10089 7.42958C6.94965 7.38458 6.59217 7.36333 6.53842 7.24459Z" fill="#FCDE8C"/>
<path d="M8.97213 7.99091C9.52335 7.99091 9.82458 7.82967 10.1946 7.69842C10.5808 7.56218 10.8983 7.44969 10.8983 7.44969C10.8983 7.44969 10.3246 6.86597 9.05837 6.86597C7.79219 6.86597 7.24097 7.46094 7.24097 7.46094C7.24097 7.46094 8.32341 7.99091 8.97213 7.99091Z" fill="white"/>
<path d="M5.67215 4.62494C5.52341 4.65869 5.51841 4.88618 5.47341 5.10117C5.38217 5.53365 5.58716 5.88363 5.97589 5.77238C6.42086 5.64489 6.31962 5.20866 6.05838 4.91868C5.90589 4.75119 5.82464 4.58994 5.67215 4.62494Z" fill="#F5E5C7"/>
<path d="M7.75965 4.31862C7.71965 4.42611 7.36842 4.49986 7.01094 4.45986C6.74595 4.42986 6.52346 4.16488 6.6647 3.86989C6.80845 3.57116 7.16593 3.53616 7.40841 3.77365C7.65715 4.01738 7.80339 4.19987 7.75965 4.31862Z" fill="#F5E5C7"/>
<path d="M8.52576 4.12493C8.44077 4.10743 8.16203 4.34617 8.10079 4.6124C8.03079 4.90989 8.23078 5.03238 8.39577 5.04988C8.59951 5.07113 8.747 4.91364 8.7595 4.7099C8.7695 4.50741 8.61701 4.14368 8.52576 4.12493Z" fill="#F5E5C7"/>
<path d="M4.20737 7.25711C4.26611 7.39835 4.4911 7.3621 4.71109 7.36835C5.15232 7.38085 5.46105 7.11836 5.28356 6.75463C5.07982 6.3384 4.66734 6.51715 4.42985 6.82463C4.29111 7.00462 4.14737 7.11336 4.20737 7.25711Z" fill="#F5E5C7"/>
<path d="M2.6774 7.23603C2.52491 7.22853 2.46116 7.44727 2.36117 7.64351C2.15993 8.03599 2.26492 8.42846 2.66865 8.42347C3.13113 8.41847 3.14738 7.96974 2.97239 7.62226C2.86989 7.41977 2.83364 7.24228 2.6774 7.23603Z" fill="#F5E5C7"/>
<path d="M14.0117 6.92965C14.163 6.9109 14.1892 6.68466 14.2567 6.47467C14.3892 6.05344 14.2205 5.68471 13.823 5.75721C13.3668 5.8397 13.4255 6.28468 13.6567 6.59841C13.7917 6.7809 13.8567 6.9484 14.0117 6.92965Z" fill="#F5E5C7"/>
<path d="M5.79346 9.21074C5.72096 9.15824 5.49097 9.22698 5.28598 9.30823C4.876 9.47072 4.69601 9.83445 5.00225 10.0969C5.35348 10.3982 5.66221 10.0719 5.75971 9.69571C5.8172 9.47697 5.88095 9.27448 5.79346 9.21074Z" fill="#F5E5C7"/>
<path d="M5.07238 8.79214C5.10738 8.72214 5.01364 8.53215 4.91989 8.38091C4.81115 8.20467 4.6874 8.02468 4.51491 7.97593C3.95994 7.81969 3.75995 8.52715 4.13243 8.75339C4.26868 8.83588 4.41242 8.84088 4.57116 8.84963C4.80865 8.85963 5.03864 8.85963 5.07238 8.79214Z" fill="#F5E5C7"/>
<path d="M3.81117 6.75339C3.78867 6.89463 3.16996 7.01212 2.85372 6.82088C2.47499 6.5909 2.67873 6.06092 3.12996 6.09967C3.51619 6.13467 3.83617 6.5934 3.81117 6.75339Z" fill="#F5E5C7"/>
<path d="M4.13105 5.98216C4.27729 6.02216 4.69977 5.70593 4.82351 5.47594C4.91351 5.30845 4.986 4.89222 4.69352 4.76098C4.36729 4.61474 4.08855 4.82972 4.00606 5.09846C3.90106 5.45344 4.00106 5.94717 4.13105 5.98216Z" fill="#F5E5C7"/>
<path d="M6.631 7.70587C6.65475 7.81462 6.44476 8.40959 5.95478 8.43208C5.6173 8.44708 5.32482 7.99336 5.74604 7.69962C6.06603 7.47838 6.61475 7.62713 6.631 7.70587Z" fill="#F5E5C7"/>
<path d="M7.05344 8.65468C6.9572 8.57343 6.45098 8.93591 6.29848 9.2009C6.20849 9.35839 6.14599 9.64588 6.51847 9.81462C6.87845 9.97836 7.07594 9.70212 7.12094 9.47713C7.16594 9.25215 7.12719 8.71717 7.05344 8.65468Z" fill="#F5E5C7"/>
<path d="M7.56088 10.4421C7.68462 10.6733 8.32584 10.5733 8.47833 10.5271C8.69832 10.4596 9.00955 10.2546 8.81081 9.82835C8.64707 9.47962 8.21959 9.55212 7.96085 9.7721C7.73712 9.96084 7.47588 10.2846 7.56088 10.4421Z" fill="#F5E5C7"/>
<path d="M9.62712 9.62576C9.49463 9.72326 9.59963 10.3195 9.67837 10.4982C9.77962 10.7295 10.1058 10.8195 10.3258 10.6507C10.5796 10.4557 10.5396 10.1332 10.3771 9.95825C10.2533 9.827 9.75587 9.52952 9.62712 9.62576Z" fill="#F5E5C7"/>
<path d="M10.5782 9.25952C10.5295 9.30327 10.0945 9.33326 9.78451 9.06828C9.60577 8.91579 9.61077 8.55455 9.79076 8.42081C9.98825 8.27457 10.2582 8.31957 10.432 8.5733C10.5407 8.72955 10.6345 9.20827 10.5782 9.25952Z" fill="#F5E5C7"/>
<path d="M11.2369 9.04573C11.3244 9.12198 11.8781 9.02823 12.0868 8.787C12.2006 8.6545 12.3631 8.37077 12.1431 8.16828C11.9231 7.96579 11.6643 8.00454 11.4394 8.28077C11.2719 8.48451 11.1469 8.96699 11.2369 9.04573Z" fill="#F5E5C7"/>
<path d="M13.3819 7.3848C13.3394 7.55479 12.7182 7.62479 12.4695 7.50854C12.2158 7.3898 12.2607 7.10357 12.312 6.97982C12.3632 6.85608 12.487 6.70984 12.8132 6.79983C13.1582 6.89483 13.4107 7.27231 13.3819 7.3848Z" fill="#F5E5C7"/>
<path d="M12.5143 9.30959C12.3756 9.27084 12.2943 9.46083 12.2043 9.54583C12.0918 9.65332 11.6194 10.1995 12.2218 10.3395C12.8256 10.4808 12.6606 9.81581 12.6381 9.68082C12.6231 9.58708 12.6156 9.33834 12.5143 9.30959Z" fill="#F5E5C7"/>
<path d="M13.1619 8.95576C13.1119 9.0995 13.3931 9.38324 13.5281 9.54698C13.6631 9.71072 13.9618 9.86196 14.1356 9.53573C14.3106 9.2095 14.0393 9.00701 13.7981 8.95076C13.5569 8.89326 13.2069 8.82577 13.1619 8.95576Z" fill="#F5E5C7"/>
<path d="M15.3192 7.79581C15.3192 7.89206 14.9817 8.16204 14.6042 8.17329C14.323 8.18204 14.1143 8.07955 14.1143 7.80206C14.1143 7.52083 14.368 7.36333 14.683 7.43083C14.8642 7.46833 15.3192 7.64332 15.3192 7.79581Z" fill="#F5E5C7"/>
<path d="M14.6654 6.97962C14.6942 7.09211 15.0317 7.12586 15.2729 7.13211C15.5154 7.13836 15.8129 7.05336 15.7004 6.72088C15.5879 6.3884 15.2554 6.47339 15.1316 6.53464C15.0092 6.59589 14.6279 6.82712 14.6654 6.97962Z" fill="#F5E5C7"/>
<path d="M13.6131 4.7547C13.5119 4.90094 13.7031 5.20468 13.8781 5.33967C14.0531 5.47466 14.2943 5.55966 14.4356 5.32217C14.5768 5.08593 14.3281 4.87219 14.1768 4.77595C14.0243 4.6822 13.7119 4.61221 13.6131 4.7547Z" fill="#F5E5C7"/>
<path d="M13.0444 5.03096C12.9781 4.98971 12.6281 5.07845 12.4257 5.26219C12.2157 5.45218 12.1382 5.72967 12.3919 5.92716C12.6369 6.11715 12.9031 5.93216 13.0106 5.67967C13.1119 5.44218 13.1194 5.0772 13.0444 5.03096Z" fill="#F5E5C7"/>
<path d="M10.3083 4.87363C10.3758 4.77989 10.2521 4.27742 10.0946 4.08493C9.93711 3.89369 9.73962 3.81994 9.52088 3.98368C9.34839 4.11242 9.28465 4.46241 9.53213 4.6424C9.80087 4.83989 10.2346 4.97488 10.3083 4.87363Z" fill="#F5E5C7"/>
<path d="M10.5044 4.09607C10.5882 4.15732 10.8932 4.11357 11.1006 3.97733C11.3081 3.84109 11.3394 3.62485 11.2194 3.47611C11.0731 3.29611 10.8369 3.36361 10.6682 3.5836C10.5207 3.77484 10.3982 4.01733 10.5044 4.09607Z" fill="#F5E5C7"/>
<path d="M11.0059 4.82243C11.0734 5.00242 11.7496 4.96368 11.9458 4.87868C12.1433 4.79369 12.2808 4.61495 12.1708 4.35496C12.0471 4.06247 11.8521 4.06122 11.7096 4.10122C11.4283 4.18122 10.9396 4.64619 11.0059 4.82243Z" fill="#F5E5C7"/>
<defs>
<radialGradient id="paint0_radial_721_145" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.02655 6.51166) rotate(-0.374469) scale(8.19673 5.34427)">
<stop offset="0.1616" stop-color="#F1B14A"/>
<stop offset="0.1688" stop-color="#F1B049"/>
<stop offset="0.3704" stop-color="#E89825"/>
<stop offset="0.5354" stop-color="#E28810"/>
<stop offset="0.6425" stop-color="#E08308"/>
<stop offset="0.7125" stop-color="#E1860D"/>
<stop offset="0.7924" stop-color="#E5901B"/>
<stop offset="0.877" stop-color="#EBA031"/>
<stop offset="0.947" stop-color="#F1B14A"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

View File

@@ -0,0 +1,8 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.78857 14.527C3.78857 14.527 6.19095 17.0543 10.312 16.4131C14.433 15.7731 16.0854 12.1596 16.4254 10.0997C16.8579 7.47608 15.6592 5.67242 15.5955 5.57368C15.548 5.50118 3.78857 14.527 3.78857 14.527Z" fill="#DC5819"/>
<path d="M3.60104 3.68857C1.13242 6.28719 0.713693 10.0832 2.56984 13.0168C3.43355 14.3818 3.96227 14.6592 3.96227 14.6592C3.96227 14.6592 8.00081 17.2491 12.5393 13.3231C16.4266 9.96074 15.6029 5.58222 15.6029 5.58222C15.6029 5.58222 15.1179 4.52228 14.098 3.51608C11.8344 1.2812 6.77462 0.346251 3.60104 3.68857Z" fill="#EF6C32"/>
<path d="M13.4956 3.00498C13.4594 3.00748 9.84832 3.26746 6.02852 8.40219C3.61615 11.6445 3.73489 13.9332 3.82864 14.5606C3.82864 14.5606 3.6449 14.4094 3.5149 14.2694C3.38491 14.1306 3.28366 14.0069 3.28366 14.0069C3.27366 12.9082 3.6024 10.8258 5.62854 8.10346C8.71213 3.95493 11.6607 2.87374 12.8969 2.59375C12.8969 2.59375 13.0481 2.6825 13.1944 2.77374C13.3019 2.84124 13.4956 3.00498 13.4956 3.00498Z" fill="#2D3130"/>
<path d="M2.94616 4.47241C3.34738 5.13113 4.31608 6.5548 5.99349 8.04097C8.10463 9.91337 10.2995 11.4358 12.5144 12.5645C13.5606 13.0982 14.3643 13.4107 14.933 13.5944C14.933 13.5944 14.8505 13.7207 14.773 13.8244C14.6955 13.9282 14.6105 14.0157 14.6105 14.0157C14.0318 13.8194 13.2594 13.5069 12.2857 13.0095C10.0333 11.8608 7.8034 10.3146 5.66101 8.4147C4.0736 7.00728 3.11115 5.67485 2.63867 4.92864C2.63867 4.92864 2.70492 4.79739 2.79241 4.68115C2.87991 4.56491 2.94616 4.47241 2.94616 4.47241Z" fill="#2D3130"/>
<path d="M7.64719 1.59619C7.46845 1.79993 7.21346 2.13116 6.91598 2.63239C6.64349 3.08986 6.5035 3.54484 6.34101 4.07231C6.07477 4.93727 5.77353 5.91721 4.78734 7.16965C3.41741 8.90956 2.07123 9.49078 1.44376 9.67702C1.44376 9.67702 1.42626 9.51452 1.42002 9.40078C1.41377 9.28704 1.41627 9.15829 1.41627 9.15829C2.01248 8.95455 3.19242 8.38458 4.39361 6.86091C5.32231 5.68098 5.60854 4.74853 5.86228 3.92607C6.02727 3.3911 6.18226 2.88612 6.486 2.3774C6.62974 2.13616 6.76473 1.92992 6.88848 1.75743C6.88848 1.75743 7.02722 1.71119 7.21721 1.66869C7.3647 1.63369 7.64719 1.59619 7.64719 1.59619Z" fill="#2D3130"/>
<path d="M9.58958 16.5004C9.17085 16.1217 8.30589 15.2568 7.67468 14.0531C6.97596 12.7219 6.64848 10.7195 7.58093 9.46831C8.64588 8.04088 10.6345 7.84714 12.4419 7.84714C12.4444 7.84714 12.4469 7.84714 12.4494 7.84714C14.7506 7.84839 16.4817 8.32962 16.5017 8.33587C16.5017 8.33587 16.488 8.16712 16.4692 8.02338C16.453 7.89964 16.4355 7.78464 16.4355 7.78464C16.348 7.76215 14.8181 7.34842 12.4494 7.34717C12.4469 7.34717 12.4444 7.34717 12.4419 7.34717C10.5108 7.34717 8.37589 7.56466 7.1797 9.16957C6.40974 10.202 6.12726 12.1844 7.2322 14.2856C7.75217 15.2755 8.40714 16.0392 8.88586 16.5204C8.88586 16.5204 9.02586 16.5254 9.2046 16.5229C9.38084 16.5204 9.58958 16.5004 9.58958 16.5004Z" fill="#2D3130"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -0,0 +1,11 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.53866 2.43392C1.32492 2.80765 1.6599 3.60261 3.29731 3.92759C5.33596 4.33257 7.53209 3.51261 9.18825 3.80135C10.1307 3.96634 10.7282 4.18008 11.0707 4.33507C11.3369 4.45631 11.3369 4.46631 11.3369 4.46631L11.7594 3.12638C11.7594 3.12638 10.5832 2.48892 9.00576 2.41892C7.42835 2.34893 6.25591 2.86515 4.69224 2.8239C2.33487 2.7614 1.87364 1.8477 1.53866 2.43392Z" fill="#966738"/>
<path d="M13.7792 6.71613L15.0767 6.18616C15.0767 6.18616 15.7491 7.12486 15.9866 8.96726C16.2704 11.1684 15.5816 12.6246 15.8054 13.867C16.0291 15.1094 16.5178 15.7794 16.4753 16.0731C16.4328 16.3669 16.2241 16.6869 15.9454 16.5344C15.6666 16.3806 14.7967 15.2632 14.7592 13.7145C14.7167 11.9971 15.1929 10.5647 14.8017 9.03851C14.3667 7.34735 13.7792 6.71613 13.7792 6.71613Z" fill="#966738"/>
<path d="M11.6594 3.07629L11.0981 4.34873C11.0981 4.34873 11.8494 4.67996 12.6781 5.43492C13.4018 6.09488 13.9205 6.90109 13.9205 6.90109L15.0792 6.18863C15.0792 6.18863 14.478 5.12369 13.5993 4.35998C12.5531 3.45002 11.6594 3.07629 11.6594 3.07629Z" fill="#D29F6C"/>
<path d="M13.8368 3.62756L5.21472 12.4546L5.25222 12.8646L5.6247 12.9208L14.3405 4.11254L13.8368 3.62756Z" fill="#F3C976"/>
<path d="M14.2167 3.2026L13.768 3.70382L14.2605 4.1963L14.7805 3.72132C14.7805 3.72132 15.6079 3.88006 15.6967 3.82757C15.8179 3.75507 16.5441 1.50894 16.5066 1.45019C16.4454 1.35395 15.6867 1.65768 15.2042 1.82892C14.6317 2.03141 14.148 2.2264 14.1042 2.2864C14.0055 2.41889 14.2167 3.2026 14.2167 3.2026Z" fill="#858585"/>
<path d="M15.4192 16.2531C15.3979 16.2531 15.3754 16.2506 15.3542 16.2444L4.93848 13.422C4.85349 13.3983 4.78599 13.3321 4.76349 13.2471L2.10863 3.61756C2.09613 3.57132 2.09613 3.52382 2.11113 3.47757L2.43361 2.41138L2.91109 2.56637L2.6636 3.73006L5.18722 12.9146L15.3192 15.7457L16.0741 14.8745L16.2641 15.3657L15.6129 16.1644C15.5629 16.2219 15.4929 16.2531 15.4192 16.2531Z" fill="#B8CED4"/>
<path d="M5.22598 12.5245L5.6247 12.9208C5.6247 12.9208 5.20348 13.432 5.13598 13.4382C5.09598 13.442 4.74975 13.1583 4.77725 13.0958C4.80475 13.0345 5.22598 12.5245 5.22598 12.5245Z" fill="#5D6265"/>
<path d="M7.46208 10.221C7.45833 10.231 6.79712 10.9272 6.18715 11.5709C5.71468 12.0697 5.2672 12.5534 5.23345 12.5534C5.15721 12.5534 4.46849 11.9034 4.45724 11.7522C4.44599 11.6009 5.49969 10.6922 5.99966 10.3385C7.03211 9.60728 7.46208 10.221 7.46208 10.221Z" fill="#DB0D2A"/>
<path d="M7.85453 10.6246C7.82829 10.6484 7.07083 11.4208 6.44211 12.0621C6.01088 12.502 5.6284 12.8783 5.6234 12.9145C5.61715 12.9658 6.20087 13.6407 6.26212 13.6407C6.37461 13.6407 7.4783 12.4633 7.82079 11.8696C8.32576 10.9946 7.85453 10.6246 7.85453 10.6246Z" fill="#DB0D2A"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,39 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.97592 1.44005C6.96717 1.62005 6.80718 16.5155 6.82593 16.5918C6.84468 16.668 7.93962 16.6468 7.93962 16.603C7.93962 16.5068 8.13336 1.5488 8.12961 1.4513C8.12586 1.39131 6.97842 1.37256 6.97592 1.44005Z" fill="#FFD51D"/>
<path d="M6.97341 1.55371C6.97341 1.55371 7.35339 2.06868 7.59088 2.37742C7.80337 2.65365 8.1121 3.00363 8.1121 3.00363L8.1021 3.75359C8.1021 3.75359 7.62213 3.21612 7.37964 2.92739C7.11715 2.61615 6.96466 2.36867 6.96466 2.36867L6.97341 1.55371Z" fill="#FDA726"/>
<path d="M6.93969 4.33862C6.93969 4.33862 7.18218 4.76485 7.49716 5.16858C7.7534 5.49731 8.07338 5.79355 8.07338 5.79355L8.05713 7.27597L6.90344 7.05973L6.93969 4.33862Z" fill="#FDA726"/>
<path d="M6.86471 11.6094L6.86096 12.1306C6.86096 12.1306 7.09095 12.5144 7.38343 12.8468C7.62717 13.1231 7.9784 13.4968 7.9784 13.4968L8.00215 11.6094L7.52968 11.3782L6.86471 11.6094Z" fill="#FDA726"/>
<path d="M6.84093 14.1719C6.84093 14.1719 7.20967 14.6531 7.4659 14.9406C7.66964 15.1693 7.95463 15.4693 7.95463 15.4693L7.94463 16.1855C7.94463 16.1855 7.4659 15.7018 7.24966 15.4693C7.03342 15.2368 6.83344 15.0043 6.83344 15.0043L6.84093 14.1719Z" fill="#FDA726"/>
<path d="M2.10621 13.4207C2.10621 13.4207 1.91872 13.1407 2.03371 12.782C2.15496 12.402 2.42994 12.262 2.55869 11.9583C2.73618 11.5383 2.69493 11.1983 3.02741 10.8833C3.32615 10.6008 3.73862 10.7058 4.10236 10.6008C4.46609 10.4958 4.61983 10.2771 4.61983 10.2771L5.46853 10.5358C5.46853 10.5358 5.35604 11.4808 5.01606 11.4645C4.67608 11.4483 3.50489 11.667 3.48114 11.7145C3.45739 11.7633 2.41494 13.4032 2.41494 13.4032L2.10621 13.4207Z" fill="#8A867E"/>
<path d="M9.99951 11.3682C10.0158 11.4407 10.2907 11.8369 10.727 12.1919C11.1632 12.5469 11.8094 12.8056 12.0282 12.9681C12.2469 13.1294 12.4081 13.2019 12.6744 13.5581C12.9406 13.9143 13.0294 14.1318 13.2243 14.4068C13.4181 14.6818 13.6206 14.8755 13.7981 15.013C13.9818 15.1543 14.3555 15.3767 14.4205 15.368C14.4855 15.3605 14.768 14.7705 14.768 14.7705C14.768 14.7705 13.9143 13.7981 13.7231 13.4681C13.5318 13.1394 13.1493 12.6181 12.7244 12.3744C12.5119 12.2519 11.1232 10.5208 11.1232 10.5208L9.99951 11.3682Z" fill="#8A867E"/>
<path d="M12.6732 7.68727C12.6732 7.68727 12.8119 7.52478 12.9019 7.46228C13.0156 7.38228 13.1081 7.33604 13.2881 7.33229C13.4506 7.32979 13.9506 7.76476 13.9506 7.76476C13.9506 7.76476 15.133 9.9559 15.1368 9.95215C15.1405 9.9484 15.9055 10.8246 15.8917 10.8071C15.878 10.7884 16.3617 11.0371 16.3617 11.0371C16.3617 11.0371 16.2792 11.5821 15.3143 11.5533C14.3706 11.5258 13.7743 10.9896 13.4394 10.2771C13.2781 9.93465 13.2531 9.31343 13.2531 9.31343L12.6732 7.68727Z" fill="#7A57BD"/>
<path d="M13.0581 7.3709C13.0581 7.3709 13.1256 7.67214 13.3706 7.88963C13.6156 8.10587 13.8306 8.29586 14.0006 8.56834C14.1705 8.84083 14.5543 9.83452 15.0468 10.4145C15.5392 10.9945 15.7255 11.032 15.9167 11.102C16.0779 11.1607 16.2342 11.1545 16.2917 11.132C16.3804 11.0995 16.4642 10.927 16.0592 10.5532C15.6542 10.1795 15.4005 9.73703 15.178 9.29205C14.9555 8.84708 14.8443 7.85338 14.233 7.4409C13.6218 7.02967 13.0581 7.3709 13.0581 7.3709Z" fill="#A47FCC"/>
<path d="M13.0418 7.92847C13.0418 7.92847 13.4293 8.43219 13.4131 9.12215C13.3968 9.81212 13.0606 10.3008 13.0606 10.7096C13.0606 11.1183 13.1493 11.3995 13.5493 11.5195C13.9505 11.6395 14.1668 11.712 14.2393 11.8808C14.3118 12.0495 14.2955 12.4745 14.4793 12.827C14.663 13.1794 14.803 13.2607 14.888 13.4444C14.9842 13.6494 15.0005 13.9894 15.0005 13.9894C15.0005 13.9894 14.8005 14.9593 14.568 14.5506C14.3355 14.1419 13.5331 12.482 13.3093 12.2895C13.0843 12.097 11.8819 10.7733 11.8819 10.7021C11.8819 10.6308 13.0418 7.92847 13.0418 7.92847Z" fill="#ACA399"/>
<path d="M2.77615 13.5869L3.23113 13.6507L3.25238 13.322C3.25238 13.322 3.40112 13.0145 3.54861 12.8132C3.69735 12.612 3.83485 12.3895 4.11983 12.347C4.40607 12.3045 6.21597 12.347 6.36471 12.262C6.51346 12.177 6.81969 11.7958 6.81969 11.7958C6.81969 11.7958 7.91338 11.8908 9.04332 11.6583C9.84828 11.492 10.377 11.4358 10.377 11.4358L11.1707 11.3621C11.1707 11.3621 12.0069 10.9921 11.9857 10.9171C11.9644 10.8433 11.3395 9.70964 11.2757 9.74214C11.212 9.77339 6.43721 10.2184 6.36346 10.3034C6.28972 10.3884 5.42101 11.2771 5.42101 11.2771C5.42101 11.2771 3.51486 11.7745 3.48362 11.7858C3.45237 11.7958 2.65741 13.4482 2.65741 13.4482L2.77615 13.5869Z" fill="#ACA399"/>
<path d="M4.02607 9.23706C4.02607 9.23706 4.02107 8.84833 4.08232 8.36336C4.14106 7.89338 4.24981 7.37216 4.26106 6.96968C4.27356 6.52095 4.22231 6.25472 4.17481 6.23972C4.12857 6.22472 3.90233 6.75969 3.90233 6.75969C3.90233 6.75969 3.91608 7.00218 3.81233 7.24092C3.68984 7.52215 3.49485 7.42216 3.49485 7.42216L3.26361 6.6372L3.71484 5.39101L3.95607 3.6936L4.75853 4.26232L5.296 6.40471L5.10851 8.4371L4.56979 9.44955L4.02607 9.23706Z" fill="#ACA399"/>
<path d="M11.2245 7.05347L10.6982 8.3184L11.287 11.0195L10.7832 11.412C10.7832 11.412 11.1532 12.1219 11.7294 12.4019C12.3069 12.6819 12.8206 12.6369 12.9919 12.6269C13.1631 12.6182 13.2781 12.6919 13.3794 12.8344C13.8593 13.5031 13.8306 14.1093 13.9643 14.3581C14.1981 14.7906 14.488 14.8268 14.488 14.8268C14.488 14.8268 15.0105 14.6368 14.993 14.6018C14.9755 14.5656 14.7493 14.1593 14.7493 14.1593C14.7493 14.1593 14.6593 13.7631 14.4518 13.4019C14.2443 13.0407 14.1268 12.8607 14.0731 12.4819C14.0193 12.1032 14.0006 11.9232 13.5956 11.8332C13.1894 11.7432 12.6669 11.4907 12.7031 10.977C12.7394 10.4633 13.3844 9.37084 13.2894 8.57839C13.2081 7.90217 12.6219 7.26221 11.7019 7.11721C11.4482 7.07722 11.2245 7.05347 11.2245 7.05347Z" fill="#F1EDEC"/>
<path d="M15.0118 13.7907C14.9555 13.8007 14.813 14.1419 14.7055 14.3231C14.6206 14.4656 14.3993 14.7919 14.4168 14.8369C14.4343 14.8819 15.1393 15.5631 15.183 15.5943C15.2468 15.6393 15.373 15.5943 15.4268 15.3956C15.4805 15.1968 15.743 14.0607 15.743 14.0607C15.743 14.0607 15.1205 13.7719 15.0118 13.7907Z" fill="#5E6367"/>
<path d="M11.8107 7.56722C11.7644 7.70471 11.8919 7.80221 12.1169 8.01845C12.3419 8.23468 12.4782 8.49592 12.5044 8.8034C12.5319 9.10964 12.5831 9.33713 12.7481 9.34463C12.9469 9.35337 13.1669 8.8934 12.8294 8.20843C12.4694 7.47722 11.8382 7.48597 11.8107 7.56722Z" fill="white"/>
<path d="M11.2957 7.2373L11.2782 10.9621C11.2782 10.9621 10.967 11.0834 10.577 10.9621C10.187 10.8409 10.057 10.5984 10.057 10.5984C10.057 10.5984 9.77955 11.0659 9.02584 11.0571C8.27213 11.0484 8.08214 10.6934 8.08214 10.6934C8.08214 10.6934 7.95089 10.9859 7.39842 11.0571C7.08594 11.0971 6.8197 11.0971 6.7672 10.9759C6.71471 10.8546 5.841 8.14476 5.841 8.14476L8.80085 7.37605L11.2957 7.2373Z" fill="#B0E4FE"/>
<path d="M7.16468 8.23345L7.13843 10.6058C7.13843 10.6058 7.39716 10.6083 7.63215 10.4496C7.87714 10.2833 7.98713 10.1171 8.03088 10.1209C8.09088 10.1259 8.40336 10.6071 9.00083 10.6233C9.66829 10.6408 9.96203 10.0696 10.0145 10.0696C10.0833 10.0696 10.2295 10.3171 10.3783 10.3983C10.647 10.5458 10.8457 10.4758 10.8457 10.4758L10.8545 7.78223L7.16468 8.23345Z" fill="#34B6E2"/>
<path d="M2.01994 4.46994C2.01994 4.46994 1.9012 3.72873 2.17744 3.29375C2.45367 2.85877 2.87865 2.74003 2.87865 2.74003L3.65986 2.54254C3.65986 2.54254 3.96234 2.41629 4.53981 2.43379C5.10978 2.45129 5.59851 2.74378 5.86349 2.89877C6.16973 3.07626 6.45596 3.46249 6.35722 3.62998C6.25847 3.79747 5.99223 3.67998 5.99223 3.67998C5.99223 3.67998 6.71345 4.1837 7.05968 5.0924C7.34716 5.84861 7.38591 6.61357 7.38591 6.61357L8.31461 8.066L11.1207 6.68232C11.1207 6.68232 11.3095 6.67107 11.4269 6.76107C11.5944 6.88981 11.6257 7.0948 11.6257 7.0948C11.6257 7.0948 11.4444 7.14479 11.2482 7.42353C10.7045 8.19474 10.3883 10.0521 8.88708 9.9234C7.58215 9.81215 7.35591 8.50097 7.26592 8.16474C7.17717 7.82851 6.00848 6.75732 6.00848 6.75732L4.89479 4.21245L3.7186 3.13501L2.44367 4.38994L2.01994 4.46994Z" fill="#7A57BD"/>
<path d="M4.64982 10.9221C4.64982 10.9221 4.39733 11.0658 4.07235 11.1121C3.74736 11.1571 3.40488 11.1033 3.14365 11.3821C2.88241 11.6608 2.9899 12.0583 2.78241 12.3645C2.57493 12.6708 2.40368 12.8607 2.23244 13.0495C2.0612 13.2382 2.02495 13.4557 2.0337 13.6357C2.04245 13.8157 2.1962 14.1319 2.1962 14.1319L3.0624 14.0682L3.05365 13.5095C3.05365 13.5095 2.93741 13.3082 3.09865 13.032C3.20239 12.8545 3.48613 12.3833 3.57612 12.2658C3.66612 12.1483 3.72612 12.0108 3.93735 11.977C4.16609 11.9395 5.9935 11.9233 6.16474 11.8421C6.33598 11.7608 6.70596 11.3646 6.73346 11.0671C6.76096 10.7696 6.77845 8.35224 6.77845 8.35224L6.9472 7.02106C6.9472 7.02106 6.84845 6.75107 6.74721 6.47983C6.40472 5.55988 5.98475 4.59743 5.18979 3.68248C4.40858 2.78378 3.62237 2.87877 3.62237 2.87877C3.62237 2.87877 2.40493 1.77008 2.34119 1.67884C2.27744 1.58759 2.1887 1.49885 2.16995 1.57009C2.1512 1.64134 2.2062 2.41755 2.2062 2.41755L2.65742 3.19251C2.65742 3.19251 2.56743 3.56249 2.41368 3.93247C2.25994 4.30245 2.01996 4.46869 2.01996 4.46869L1.92871 6.1086C1.92871 6.1086 1.73747 6.40734 1.73122 6.79607C1.72497 7.17855 3.24989 6.75607 3.25614 6.72357C3.26239 6.69107 3.77736 5.28865 3.77111 5.2174C3.76486 5.14616 4.15484 4.02371 4.15484 4.02371C4.15484 4.02371 4.84731 5.01866 4.73731 6.17485C4.69981 6.57483 4.63357 6.99856 4.57732 7.33979C4.39358 8.46223 4.25859 9.21844 4.25859 9.21844L4.64982 10.6221V10.9221Z" fill="#F1EDEC"/>
<path d="M3.60862 13.8983C3.60862 13.952 3.44488 14.232 3.1024 14.692C2.75992 15.1519 2.63742 15.3019 2.50868 15.1194C2.37994 14.9369 2.33244 14.627 2.28369 14.4632C2.23994 14.3157 2.1662 14.0782 2.1662 14.0782L3.25239 13.332C3.25364 13.332 3.60862 13.7808 3.60862 13.8983Z" fill="#5E6367"/>
<path d="M2.79364 5.25611C2.73739 5.2811 2.6874 5.52984 2.88239 5.77483C3.07738 6.01981 3.30111 6.13731 3.30111 6.13731L3.47361 5.75858C3.47361 5.75858 3.23487 5.60859 3.09987 5.48484C2.96113 5.3561 2.84364 5.23361 2.79364 5.25611Z" fill="white"/>
<path d="M2.16995 1.56992C2.13496 1.58617 1.92622 1.85616 2.06996 2.40238C2.15495 2.72611 2.76242 3.32608 2.76242 3.32608L3.1249 3.57982C3.1249 3.57982 3.07116 3.20234 2.85492 2.9486C2.63868 2.69486 2.38494 2.40988 2.3162 2.17114C2.24745 1.93241 2.2862 1.51618 2.16995 1.56992Z" fill="#ACA399"/>
<path d="M1.92498 6.10737C1.92498 6.10737 1.71374 6.4811 1.72249 6.84483C1.72999 7.17731 1.83624 7.35605 2.04123 7.53729C2.24497 7.71728 2.59745 7.84853 2.81744 7.75978C2.84743 7.74728 2.85993 7.53729 2.93118 7.38105C2.98618 7.26231 3.17742 7.07107 3.17742 7.07107C3.17742 7.07107 3.26991 7.27106 3.28491 7.37105C3.29991 7.47105 3.30991 7.66979 3.34616 7.67854C3.4849 7.71353 3.67614 7.57479 3.79738 7.27481C3.92738 6.95357 3.90113 6.76608 3.90113 6.76608L4.10862 6.29111C4.10862 6.29111 4.00112 5.4599 3.29241 5.26741C2.5837 5.07492 1.96373 5.4299 1.96373 5.4299L1.92498 6.10737Z" fill="url(#paint0_radial_715_295)"/>
<path d="M2.22246 6.3922C2.02497 6.36971 1.94623 6.59094 1.94623 6.83718C1.94623 7.08342 2.07872 7.18591 2.20496 7.18591C2.33121 7.18591 2.4462 7.02342 2.4462 6.79468C2.44495 6.5672 2.37871 6.41095 2.22246 6.3922Z" fill="#845B51"/>
<path d="M2.35742 4.74364C2.37992 4.79989 2.44742 4.88989 2.54366 4.92363C2.63991 4.95738 2.7524 4.94113 2.7524 4.94113C2.7524 4.94113 2.78115 5.18362 2.95489 5.18987C3.13738 5.19612 3.15238 4.87364 3.12363 4.71615C3.09488 4.55865 2.99989 4.45116 2.99989 4.45116C2.99989 4.45116 2.7124 4.41116 2.53116 4.53616C2.39867 4.62615 2.35742 4.74364 2.35742 4.74364Z" fill="#464D50"/>
<path d="M3.26488 2.55373C3.26488 2.55373 3.56736 2.79622 3.60486 3.21495C3.64611 3.68367 3.64486 3.78992 3.64486 3.78992L2.54367 3.09745C2.54367 3.09745 2.49117 3.2187 2.48867 3.35369C2.48617 3.49993 2.50742 3.65118 2.50742 3.65118L3.65111 4.35364C3.65111 4.35364 3.60736 4.97986 3.42237 5.63482C3.25863 6.21229 3.03364 6.64227 2.99614 6.72226C2.95864 6.80226 2.8274 7.14474 3.13863 7.22224C3.41112 7.28973 3.46612 6.901 3.54611 6.72726C3.62611 6.55352 3.81985 6.0648 3.93484 5.63357C4.24983 4.45363 4.18858 3.74867 4.11983 3.2612C4.04734 2.73997 3.88734 2.63998 3.69611 2.55873C3.55861 2.50249 3.35612 2.46499 3.26488 2.55373Z" fill="#34B6E2"/>
<path d="M4.01483 8.99843C4.01483 8.99843 3.98609 9.34841 4.06233 9.87089C4.12358 10.2846 4.44356 10.9633 4.6548 10.9121C4.70605 10.8996 4.57356 10.2534 4.77729 9.99588C4.91604 9.81964 5.16852 9.78839 5.81724 9.42091C6.35971 9.11343 6.84094 8.51221 6.84094 8.51221L6.73969 7.62476L6.05598 7.79225C6.05598 7.79225 5.556 8.27597 4.96978 8.5922C4.44856 8.87469 4.01483 8.99843 4.01483 8.99843Z" fill="#D853C9"/>
<path d="M6.45474 7.58598C6.45099 7.58723 6.211 7.48974 6.05601 7.74973C5.86477 8.07096 5.86352 8.65218 5.84477 8.69592C5.82602 8.73967 5.70978 8.87842 5.82602 8.90216C5.94101 8.92591 6.38849 8.88716 6.67598 8.68093C6.96471 8.47469 7.01721 8.19095 7.01721 8.19095L6.45474 7.58598Z" fill="#34B6E2"/>
<path d="M11.2569 6.6974C11.2569 6.6974 11.0832 6.68865 10.6982 7.17862C10.1758 7.84233 9.90827 9.20726 8.97582 9.20726C8.04337 9.20726 7.89463 8.3948 7.64839 7.76734C7.40215 7.13987 6.94718 6.79739 6.94718 6.79739C6.94718 6.79739 6.70844 6.64865 6.83593 6.45491C6.93343 6.30617 7.25091 6.43991 7.42965 6.5224C7.60839 6.6049 7.93088 6.81364 8.13336 6.89363C8.69209 7.11487 9.20831 6.97738 9.49954 6.91113C9.79078 6.84364 10.0608 6.71864 10.4995 6.6724C10.9895 6.61865 11.2569 6.6974 11.2569 6.6974Z" fill="#A47FCC"/>
<path d="M6.66972 6.9986C6.42599 7.16734 6.1635 7.55357 6.46848 8.04729C6.74722 8.49727 7.33969 8.35353 7.50968 8.18353C7.72342 7.9698 7.80841 7.55607 7.58593 7.19859C7.36344 6.84111 6.89721 6.83986 6.66972 6.9986Z" fill="#FFA6A3"/>
<path d="M6.94471 7.32231C6.81596 7.35481 6.69347 7.53855 6.75846 7.73604C6.81471 7.90978 7.01845 7.98103 7.14844 7.91853C7.28594 7.85228 7.37343 7.66729 7.29594 7.5023C7.23219 7.36356 7.09345 7.28481 6.94471 7.32231Z" fill="#F182A7"/>
<defs>
<radialGradient id="paint0_radial_715_295" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(2.61485 7.97649) scale(2.18616)">
<stop offset="0.6623" stop-color="#FFA8A6"/>
<stop offset="0.9142" stop-color="#FFA8A6" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,17 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.02217 9.55581L1.83744 9.7433C1.83744 9.7433 1.50871 9.41456 1.55621 9.29707C1.6037 9.17958 5.89723 5.82475 6.06097 5.8485C6.22471 5.87225 6.60094 6.27098 6.90592 6.12974C7.21091 5.98849 6.90592 5.16729 6.99967 5.05104C7.09341 4.93355 10.4482 3.12739 10.6595 3.03365C10.8707 2.9399 11.0582 2.72867 11.4807 2.82241C11.9032 2.91616 12.9594 3.10365 14.108 3.8786C15.2567 4.65356 16.4542 5.54477 16.3604 6.03724C16.2667 6.52972 7.02217 9.55581 7.02217 9.55581Z" fill="#FFE265"/>
<path d="M3.43233 15.4455C4.58227 15.3042 15.3042 13.0056 15.7504 12.9356C16.1967 12.8656 16.4779 12.4431 16.4779 11.9732C16.4779 11.5032 16.3604 6.03723 16.3604 6.03723C16.3604 6.03723 15.8442 6.20097 15.2342 6.34222C14.6242 6.48221 9.62576 7.70214 7.65586 8.14837C5.68596 8.5946 2.54113 9.4158 2.18865 9.4158C1.83617 9.4158 1.55493 9.29831 1.55493 9.29831C1.55493 9.29831 1.64868 14.1781 1.67243 14.5305C1.69617 14.883 1.85992 15.3042 2.25864 15.4217C2.65737 15.5392 2.91736 15.508 3.43233 15.4455Z" fill="#FEB502"/>
<path d="M10.3708 4.19483C10.3708 4.49482 10.6758 4.69356 11.1808 4.67856C11.732 4.66231 11.8845 4.33483 11.867 4.12484C11.8495 3.9136 11.5958 3.72861 11.0933 3.73736C10.6271 3.74611 10.3708 3.9311 10.3708 4.19483Z" fill="#FF8B0D"/>
<path d="M4.73988 7.93458C3.92742 8.20832 4.01867 8.91953 4.25616 9.07827C4.49364 9.23701 4.80988 9.22826 5.17986 9.09577C5.54984 8.96328 5.78483 8.81203 5.82232 8.56455C5.85732 8.32956 5.54984 7.66084 4.73988 7.93458Z" fill="#ED6B31"/>
<path d="M12.122 5.90225C11.5771 5.92725 10.9746 6.31848 10.7496 6.69471C10.5121 7.09094 10.4946 7.55716 11.0396 7.84715C11.5846 8.13713 13.512 7.39842 13.4157 6.72096C13.3195 6.04224 12.6945 5.876 12.122 5.90225Z" fill="#ED6B31"/>
<path d="M10.2307 11.3832C10.2307 11.3832 11.0045 10.8107 11.0045 10.407C11.0045 10.002 10.6707 9.04583 9.25329 8.97334C7.92461 8.90459 7.10591 10.7157 7.97711 11.6482C8.84832 12.5806 9.37579 12.1582 9.70202 12.0707C10.0283 11.9819 10.2307 11.3832 10.2307 11.3832Z" fill="#ED6B31"/>
<path d="M6.40339 13.0106C6.40339 13.0106 7.17209 12.5294 6.58213 12.0019C6.16715 11.6307 5.61218 11.7982 5.32969 12.0869C4.96846 12.4569 5.06596 13.0456 5.39969 13.3093C5.73467 13.5743 6.40339 13.0106 6.40339 13.0106Z" fill="#ED6B31"/>
<path d="M3.79857 10.8469C3.79857 10.8469 3.71108 10.5707 3.39734 10.4594C3.04486 10.3345 2.51864 10.7732 2.74363 11.2419C2.94612 11.6644 3.39484 11.5406 3.39484 11.5406L3.79857 10.8469Z" fill="#ED6B31"/>
<path d="M15.0518 11.2507C15.1281 11.1682 15.6268 10.8083 15.6268 10.5345C15.6268 10.262 14.8756 9.41958 13.9169 10.0533C12.9582 10.687 13.4594 11.6457 13.7232 11.8045C13.9869 11.9632 14.7181 11.612 15.0518 11.2507Z" fill="#ED6B31"/>
<path d="M4.85735 8.36585C4.32363 8.55084 4.12239 8.92832 4.25613 9.07831C4.39737 9.23705 4.70486 9.34205 5.24108 9.14831C5.65606 8.99831 5.86605 8.74708 5.83105 8.52334C5.80605 8.36085 5.34108 8.19836 4.85735 8.36585Z" fill="#FF8B0D"/>
<path d="M3.07736 10.8282C2.84362 11.1207 3.03361 11.4532 3.2536 11.5319C3.47359 11.6107 3.86106 11.3732 3.83482 10.9782C3.80732 10.582 3.2886 10.5645 3.07736 10.8282Z" fill="#FF8B0D"/>
<path d="M5.79599 12.4219C5.29352 12.8294 5.27977 13.2281 5.47351 13.3631C5.6085 13.4581 6.10597 13.6556 6.56095 13.3194C6.89843 13.0706 6.90468 12.5482 6.79844 12.2982C6.70969 12.0857 6.21847 12.0782 5.79599 12.4219Z" fill="#FF8B0D"/>
<path d="M8.84581 10.2007C8.17334 10.8544 8.01335 11.9156 8.67332 12.1656C9.25454 12.3856 10.0895 12.2531 10.582 11.6469C11.0994 11.0094 11.1544 10.3057 10.822 9.89072C10.4245 9.392 9.36578 9.69698 8.84581 10.2007Z" fill="#FF8B0D"/>
<path d="M11.9019 6.55347C11.022 6.85221 10.5033 7.48592 10.952 7.80341C11.4007 8.11964 11.8669 8.07339 12.3769 7.89965C12.9556 7.70341 13.3981 7.24844 13.4156 6.81721C13.4294 6.46348 12.6769 6.28974 11.9019 6.55347Z" fill="#FF8B0D"/>
<path d="M14.2518 10.5995C13.6531 11.087 13.4781 11.5757 13.7243 11.8044C13.9706 12.0332 14.5606 12.1382 15.1055 11.7607C15.6717 11.3682 15.713 10.9232 15.6505 10.5645C15.5805 10.1608 14.8668 10.0983 14.2518 10.5995Z" fill="#FF8B0D"/>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -0,0 +1,42 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.99861 5.46985L2.46614 5.59234C2.46614 5.59234 1.72742 6.73228 1.56618 7.94472C1.46494 8.70593 1.46119 9.69962 1.53119 10.1946C1.60243 10.6896 1.85742 11.7433 2.25865 12.4857C2.64488 13.2007 3.02111 13.4819 3.43233 13.7307C3.60858 13.8369 3.76607 14.2006 4.04105 14.3769C4.29854 14.5419 4.63852 14.5744 4.63852 14.5744C4.63852 14.5744 5.57847 16.0505 7.61211 16.3068C9.99824 16.6068 11.1657 15.5293 11.1657 15.5293C11.1657 15.5293 12.1206 15.5268 13.0218 15.1406C14.033 14.7069 14.573 13.8044 14.573 13.8044C14.573 13.8044 15.8392 12.7145 16.2404 11.4095C16.5629 10.3621 16.5716 8.37094 16.3242 7.43349C16.0767 6.49604 15.5979 5.70233 15.218 5.19361C14.9155 4.78863 11.5382 2.99373 11.1307 2.83373C10.7245 2.67499 6.9409 2.42751 6.79966 2.53375C6.65841 2.64125 2.99861 5.46985 2.99861 5.46985Z" fill="#AC5811"/>
<path d="M4.31614 3.46869C3.43494 4.18615 2.97871 4.56113 2.52124 5.45858C2.06376 6.35603 2.21125 7.54722 2.29375 7.9872C2.375 8.42718 2.52249 9.27588 2.60373 9.69961C2.68498 10.1246 2.90122 11.132 3.49994 11.7558C4.33864 12.6295 5.0986 12.9307 5.0986 12.9307C5.0986 12.9307 5.71482 13.8856 7.20349 14.4319C9.16089 15.1493 10.0421 14.1056 10.7921 13.8281C11.542 13.5507 12.4395 13.5344 13.2069 12.9957C13.9731 12.457 14.8218 11.8045 15.3768 10.4346C15.9318 9.06464 15.8168 8.46092 15.833 8.11844C15.8493 7.77596 15.8005 6.12855 15.1643 5.11735C14.5281 4.10615 13.7631 3.49868 13.4682 3.30619C12.8157 2.88247 10.4858 1.86877 9.8996 1.75503C9.31213 1.64128 7.21724 1.81877 6.38603 2.16125C5.55483 2.50374 4.31614 3.46869 4.31614 3.46869Z" fill="url(#paint0_radial_718_89)"/>
<path d="M8.03476 9.93212C7.89601 9.88837 7.74977 9.68213 7.71477 9.2409C7.66602 8.61219 8.12975 8.43845 8.12975 8.43845C8.12975 8.43845 8.68222 8.2922 9.07095 8.15346C9.43218 8.02472 9.69217 7.85973 9.69217 7.85973C9.69217 7.85973 9.72717 7.18601 9.9509 6.98727C10.1759 6.78853 10.5721 6.80603 11.0133 6.85728C11.4533 6.90853 12.162 7.20226 12.0583 7.46975C11.9545 7.73723 11.8333 8.00472 11.4108 8.24721C10.9883 8.48844 10.6859 8.71343 10.4696 8.90342C10.2534 9.09466 9.85716 9.33465 9.20094 9.57714C8.54473 9.81962 8.23474 9.99461 8.03476 9.93212Z" fill="#FFC86E"/>
<path d="M4.31864 4.30862C3.90366 4.22363 3.31744 5.12233 3.2087 5.41981C3.0987 5.7173 2.98621 6.44226 2.97371 6.77349C2.95746 7.18472 2.94996 7.57095 3.12245 7.88468C3.25495 8.12717 3.57618 8.26341 3.86491 8.01343C4.15365 7.76344 4.65487 7.07848 4.78736 6.98223C4.91986 6.88599 5.30734 6.75849 5.15485 6.12853C5.09235 5.87104 4.45863 5.33857 4.42738 5.26607C4.39739 5.19483 4.55363 4.35737 4.31864 4.30862Z" fill="#FFC86E"/>
<path d="M7.25225 3.17991C7.2685 3.42115 7.46599 3.80738 7.67973 4.01611C7.99971 4.32735 9.46713 5.37979 9.84712 5.3198C10.2271 5.2598 10.6408 4.57733 10.7621 4.38734C10.8833 4.19736 11.1245 3.92987 11.2195 3.80863C11.3145 3.68738 11.5645 3.41115 11.3583 3.13491C11.1508 2.85868 10.6333 2.67744 10.2883 2.65119C9.94336 2.62494 9.40714 2.4012 8.94966 2.4187C8.49219 2.4362 7.20975 2.55744 7.25225 3.17991Z" fill="#FFC86E"/>
<path d="M13.292 6.39853C13.3208 6.66102 13.6895 6.71851 13.9832 6.9085C14.277 7.09849 14.4757 7.29723 14.6482 7.24474C14.8207 7.19349 15.0557 6.91725 15.0019 6.43353C14.9332 5.81231 14.5444 5.01735 14.0007 5.0436C13.672 5.05985 13.717 5.63107 13.717 5.63107C13.717 5.63107 13.2745 6.24354 13.292 6.39853Z" fill="#FFC86E"/>
<path d="M6.1836 4.77845C6.1836 4.77845 5.00491 4.77095 5.02616 4.88844C5.04866 5.00594 5.15116 5.39342 5.45864 5.40842C5.76612 5.42341 6.16235 5.45216 6.35234 5.39342C6.54233 5.33467 7.06981 4.90969 7.06981 4.7197C7.06981 4.52971 6.85732 4.25098 6.85732 4.25098L6.1836 4.77845Z" fill="#CF701E"/>
<path d="M7.86715 6.93968C7.86715 6.93968 7.8534 6.94842 7.8334 6.96467C7.77466 7.01342 7.66216 7.12717 7.70591 7.25466C7.76466 7.4234 8.10839 7.73838 8.44587 7.73838C8.78335 7.73838 8.96584 7.4234 9.02459 7.21841C9.08334 7.01342 9.05334 6.88843 9.05334 6.88843L7.86715 6.93968Z" fill="#CF701E"/>
<path d="M5.23871 7.89969C5.23871 7.89969 5.03372 7.97344 5.04122 8.10468C5.04872 8.23593 5.09996 8.47841 5.3637 8.73465C5.62994 8.99339 6.01616 9.06463 6.3674 8.96964C6.71863 8.87464 6.97486 8.61091 6.94612 8.25967C6.91737 7.90844 6.57989 7.63721 6.57989 7.63721L5.23871 7.89969Z" fill="#CF701E"/>
<path d="M3.72254 9.8409C3.72254 9.8409 3.50255 9.89215 3.52505 10.0384C3.54755 10.1846 3.79629 10.6459 4.19127 10.5946C4.58625 10.5434 5.20996 10.0896 5.20996 9.9134C5.20996 9.73716 4.89498 9.50342 4.89498 9.50342L3.72254 9.8409Z" fill="#CF701E"/>
<path d="M6.41717 11.6719C6.41717 11.6719 6.33592 12.1444 6.97339 12.3168C8.03584 12.6031 8.14583 11.9581 8.14583 11.9581L6.41717 11.6719Z" fill="#CF701E"/>
<path d="M10.5908 11.8906C10.5908 11.8906 10.3058 12.1756 10.6295 12.3993C10.9532 12.6231 11.322 12.6993 11.6695 12.6606C12.0169 12.6218 12.2544 12.3444 12.2544 12.3444L10.5908 11.8906Z" fill="#CF701E"/>
<path d="M13.2507 8.72583C13.2507 8.72583 12.7133 8.99707 13.1295 9.46579C13.4457 9.82327 14.1694 9.75078 14.4657 9.54954C14.6594 9.41829 14.7169 9.18581 14.6444 9.06831C14.5707 8.95082 13.2507 8.72583 13.2507 8.72583Z" fill="#CF701E"/>
<path d="M11.1708 5.62103C11.1708 5.62103 11.142 6.23474 11.9695 6.21475C13.1269 6.186 12.9807 5.46729 12.9807 5.46729L11.1708 5.62103Z" fill="#CF701E"/>
<path d="M11.7058 4.85228C11.7058 4.85228 11.1633 5.18976 11.1046 5.32851C11.0459 5.46725 10.9209 5.90722 11.5508 5.87848C12.1808 5.84973 12.8108 5.82723 13.0232 5.57099C13.2357 5.31476 13.1845 5.07977 13.097 4.88978C13.0095 4.69979 12.517 3.98108 12.1258 3.98983C11.8696 3.99483 11.7058 4.85228 11.7058 4.85228Z" fill="#593329"/>
<path d="M12.1233 3.98744C12.1233 3.98744 11.7795 3.94369 11.4783 4.47866C11.1783 5.01363 11.1008 5.33237 11.1008 5.33237C11.1008 5.33237 11.712 5.33487 12.0783 5.29112C12.4445 5.24737 12.6932 5.01988 12.6932 5.01988C12.6932 5.01988 12.3945 4.05368 12.1233 3.98744Z" fill="#925849"/>
<path d="M7.96335 6.48593C7.96335 6.48593 7.66336 6.46593 7.64087 6.58217C7.61837 6.69967 7.68461 6.9984 7.78711 7.12965C7.8896 7.26089 8.19709 7.55462 8.50457 7.49588C8.81205 7.43713 8.99454 7.1359 9.04579 7.03215C9.09204 6.93466 9.14204 6.82966 9.05454 6.69842C8.9658 6.56593 7.96335 6.48593 7.96335 6.48593Z" fill="#593329"/>
<path d="M8.40971 6.01723C8.15473 5.98973 7.53976 6.47845 7.64975 6.64595C7.77225 6.82969 7.7935 6.71344 8.13098 6.88218C8.40971 7.02093 8.53096 7.14967 8.70595 7.12717C8.88094 7.10467 9.08968 6.81594 9.05343 6.69844C9.01718 6.58095 8.7547 6.05348 8.40971 6.01723Z" fill="#925849"/>
<path d="M5.52366 4.03867L4.9237 4.45364C4.9237 4.45364 4.9012 4.73488 5.02619 4.88862C5.15118 5.04236 5.18743 5.13736 5.77365 5.13736C6.35987 5.13736 6.90859 5.05736 7.01109 4.63988C7.11358 4.22241 6.93359 3.95492 6.8236 3.80118C6.7136 3.64744 5.52366 4.03867 5.52366 4.03867Z" fill="#593329"/>
<path d="M5.53104 3.38745C5.39604 3.44494 5.07731 3.87867 5.00357 4.06866C4.92982 4.25865 4.91357 4.46114 4.92357 4.46364C5.04481 4.49364 5.75853 4.55989 6.07351 4.50864C6.38849 4.45739 6.64473 4.31865 6.74722 4.13491C6.82847 3.98867 6.82222 3.79493 6.82222 3.79493C6.82222 3.79493 6.60848 3.40995 6.31475 3.29245C6.02226 3.17496 5.58228 3.36495 5.53104 3.38745Z" fill="#925949"/>
<path d="M5.02853 7.92721C5.02853 7.92721 4.97853 8.00346 5.07353 8.1497C5.16852 8.29594 5.35476 8.47093 5.6335 8.55218C5.91223 8.63218 6.10972 8.74217 6.29221 8.72092C6.47595 8.69842 6.83468 8.55218 6.84843 8.04721C6.86343 7.54223 6.76094 7.30725 6.54095 7.08726C6.32096 6.86727 6.16222 6.90477 6.16222 6.90477L5.02853 7.92721Z" fill="#593329"/>
<path d="M5.09246 7.45956C5.01496 7.58955 4.95746 8.01203 5.05246 8.10702C5.14745 8.20202 5.71867 8.20827 5.86866 8.19202C6.01991 8.17577 6.51363 7.94328 6.52113 7.76704C6.52863 7.5908 6.43363 7.35706 6.43363 7.26957C6.43363 7.18207 6.54363 6.97708 6.24364 6.90334C5.94366 6.82959 5.25245 7.18832 5.09246 7.45956Z" fill="#925849"/>
<path d="M4.1024 9.357C4.1024 9.357 3.63117 9.47324 3.60118 9.63449C3.57243 9.79573 3.63992 9.95822 3.80116 10.112C3.96241 10.2657 4.0799 10.4269 4.20364 10.4119C4.32864 10.3969 4.65737 10.1045 4.75986 10.0095C4.86236 9.91447 5.0086 9.74573 4.99485 9.60699C4.9811 9.46824 4.92111 9.32825 4.78986 9.21826C4.65987 9.10826 4.1024 9.357 4.1024 9.357Z" fill="#593329"/>
<path d="M4.2199 8.9396C3.99991 8.9446 3.83117 9.21083 3.75867 9.30583C3.68492 9.40082 3.56118 9.62831 3.61243 9.66456C3.66368 9.70081 3.87616 9.72331 3.97866 9.78955C4.08115 9.8558 4.2349 9.95829 4.35239 9.8633C4.46988 9.7683 4.57238 9.50457 4.63112 9.41707C4.68987 9.32958 4.79237 9.21958 4.79237 9.21958C4.79237 9.21958 4.57113 8.9321 4.2199 8.9396Z" fill="#925849"/>
<path d="M6.9084 10.7044C6.86465 10.7556 6.24219 11.2606 6.24219 11.2606C6.24219 11.2606 6.26469 11.7006 6.40343 11.8031C6.54217 11.9056 6.97465 12.0518 7.25338 12.1106C7.53212 12.1693 8.02959 12.2356 8.15459 12.1843C8.27958 12.1331 8.41082 11.8618 8.41832 11.5106C8.42582 11.1594 8.30083 10.8969 8.16209 10.7356C8.02334 10.5744 7.9121 10.5144 7.9121 10.5144L6.9084 10.7044Z" fill="#593329"/>
<path d="M7.73588 11.3633C7.81712 11.272 7.91212 10.5133 7.91212 10.5133C7.91212 10.5133 7.3484 10.0808 7.11341 10.0521C6.87842 10.0233 6.60094 10.0446 6.51969 10.1321C6.43969 10.2196 6.12471 10.5346 6.15346 10.7395C6.18221 10.9445 6.24095 11.2595 6.24095 11.2595C6.24095 11.2595 7.56713 11.5545 7.73588 11.3633Z" fill="#925849"/>
<path d="M10.4684 11.7244C10.4684 11.7244 10.3971 12.1994 10.9196 12.2894C11.4421 12.3781 11.8758 12.4969 12.102 12.4494C12.3283 12.4019 12.5532 12.0994 12.577 11.8794C12.6007 11.6594 12.6132 11.3419 12.577 11.2557C12.482 11.027 12.042 10.4007 11.8283 10.3345C11.7083 10.2982 11.5946 10.3182 11.5946 10.3182L10.4684 11.7244Z" fill="#593329"/>
<path d="M10.807 10.892C10.5732 11.1507 10.4532 11.5245 10.4657 11.722C10.4782 11.9082 11.3644 11.827 11.4694 11.7632C11.5744 11.6995 11.8032 11.4145 11.8107 11.3195C11.8182 11.2245 11.7457 10.927 11.7332 10.7433C11.7207 10.5595 11.7157 10.3095 11.6144 10.3158C11.5132 10.322 11.1394 10.5245 10.807 10.892Z" fill="#925849"/>
<path d="M13.8043 7.66724C13.8043 7.66724 14.0105 7.69723 14.2643 7.84973C14.5605 8.02722 14.8705 8.47969 14.8467 8.84842C14.823 9.21715 14.5017 9.3834 14.1105 9.38965C13.7193 9.3959 13.3368 9.37465 13.2656 9.17216C13.1943 8.96967 13.8043 7.66724 13.8043 7.66724Z" fill="#593329"/>
<path d="M13.4919 7.79085C13.4306 7.87584 13.2719 8.13583 13.2181 8.46831C13.1644 8.80079 13.2006 9.18077 13.3194 9.24702C13.4381 9.31202 14.0918 8.7483 14.1268 8.6883C14.1631 8.6283 13.9181 7.7196 13.8406 7.67835C13.7631 7.63711 13.5694 7.6846 13.4919 7.79085Z" fill="#925849"/>
<defs>
<radialGradient id="paint0_radial_718_89" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.02019 8.19924) scale(6.6499 6.6499)">
<stop offset="0.507" stop-color="#F29F58"/>
<stop offset="0.7151" stop-color="#F09D56"/>
<stop offset="0.8262" stop-color="#EB9550"/>
<stop offset="0.9141" stop-color="#E18745"/>
<stop offset="0.9898" stop-color="#D47436"/>
<stop offset="1" stop-color="#D27133"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,14 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.8981 13.9368H10.3171V14.803H15.8981V13.9368Z" fill="#AF0C1A"/>
<path d="M9.64575 14.5493V15.213L16.4791 15.2205C16.4791 15.2205 16.4941 14.5781 16.4716 14.5781C16.4504 14.5793 9.64575 14.5493 9.64575 14.5493Z" fill="#DC0D2A"/>
<path d="M9.64575 13.3882L9.66075 14.0594C9.66075 14.0594 16.4579 14.0956 16.4654 14.0744C16.4729 14.0531 16.4804 13.4394 16.4504 13.4244C16.4216 13.4094 9.64575 13.3882 9.64575 13.3882Z" fill="#DC0D2A"/>
<path d="M2.10986 6.98474C2.10986 6.98474 2.10986 9.24213 2.10986 9.31837C2.10986 9.39462 2.16236 9.46211 2.26111 9.46961C2.35985 9.47711 3.65853 9.45461 3.73353 9.46211C3.80852 9.46961 3.89227 9.40212 3.89227 9.26587C3.89227 9.12963 3.89227 6.99349 3.89227 6.99349C3.89227 6.99349 3.43979 6.65351 3.38605 6.66101C3.3323 6.66851 2.10986 6.98474 2.10986 6.98474Z" fill="#858585"/>
<path d="M2.66233 7.52465C2.55734 7.53715 2.51109 7.59964 2.51109 7.72089C2.51109 7.84213 2.49609 8.65709 2.49609 8.73958C2.49609 8.82208 2.50359 8.90582 2.62484 8.91332C2.74608 8.92082 3.2898 8.92832 3.3573 8.92082C3.42479 8.91332 3.47854 8.83833 3.48604 8.73958C3.49354 8.64084 3.48604 7.75838 3.48604 7.68964C3.48604 7.56839 3.40355 7.5309 3.2973 7.5309C3.19106 7.5309 2.78233 7.50965 2.66233 7.52465Z" fill="#B0B0B0"/>
<path d="M13.7444 7.6571L13.7594 6.87964L11.932 6.88714L11.917 7.6196L12.3695 8.11082H12.6382C12.6282 8.83704 12.5957 11.1157 12.5957 11.1157C12.5957 11.1157 12.4932 11.2619 12.377 11.3719C12.2257 11.5156 11.977 11.7044 11.9995 12.1719C12.0207 12.6018 12.4445 12.9268 12.8607 12.9268C13.3057 12.9268 13.6082 12.6018 13.6232 12.2931C13.6382 11.9831 13.5169 11.9706 13.4494 11.9606C13.3432 11.9456 13.2607 12.0206 13.2382 12.1569C13.2157 12.2931 13.0794 12.4668 12.8457 12.4131C12.612 12.3594 12.4607 12.1031 12.6495 11.8544C12.7844 11.6756 13.0419 11.6206 13.0644 11.4694C13.0844 11.3394 13.0694 8.79954 13.0657 8.10957H13.3444L13.7444 7.6571Z" fill="#858587"/>
<path d="M11.4569 13.2582C11.3131 13.3245 11.3331 13.4382 11.3344 13.5545C11.3394 14.0619 11.3631 15.2144 11.3631 15.2144C11.3631 15.2144 11.3969 15.3806 11.6156 15.3806C11.8769 15.3806 11.8969 15.2069 11.8969 15.2069V13.482L12.8831 12.6645L14.1917 13.6257C14.1917 13.6257 14.228 15.1194 14.228 15.2056C14.228 15.2919 14.3142 15.4081 14.5167 15.4006C14.748 15.3919 14.7767 15.2056 14.7767 15.2056C14.7767 15.2056 14.768 13.6057 14.7605 13.4732C14.748 13.2757 14.4517 13.2795 14.4517 13.2795C14.4517 13.2795 13.008 12.1308 12.8781 12.1245C12.7343 12.1183 11.5569 13.212 11.4569 13.2582Z" fill="#FF510F"/>
<path d="M12.3945 9.602C12.3008 9.617 12.2795 9.68199 12.2795 9.76824C12.2795 9.85448 12.2808 10.7119 12.2808 10.7694C12.2808 10.8269 12.2808 10.8844 12.3608 10.8919C12.4408 10.8994 13.2632 10.8994 13.3057 10.8994C13.3707 10.8994 13.407 10.8194 13.407 10.7194C13.407 10.6182 13.412 9.77449 13.407 9.71699C13.3995 9.62325 13.3132 9.602 13.2407 9.5945C13.1682 9.58575 12.4595 9.592 12.3945 9.602Z" fill="#F1901B"/>
<path d="M1.77368 4.73616L6.02971 2.70752C6.02971 2.70752 7.45838 3.025 10.242 3.70872C13.4831 4.50492 14.1368 4.79491 14.1368 4.79491L13.1606 5.06365L12.3669 4.77116L6.04221 3.25749L2.64739 4.83241L1.90242 4.9424L1.77368 4.73616Z" fill="#858585"/>
<path d="M16.5492 6.64477C16.4817 6.57352 14.5593 4.68862 14.4668 4.68862C14.3743 4.68862 7.51213 4.68862 7.51213 4.68862C7.51213 4.68862 6.39844 3.01871 6.25595 2.81247C6.09596 2.57998 5.95222 2.56749 5.80722 2.80622C5.74848 2.90372 4.67728 4.67612 4.67728 4.67612C4.67728 4.67612 1.85243 4.71237 1.68994 4.69987C1.52745 4.68862 1.4812 4.76987 1.4812 4.83862C1.4812 4.90861 1.4812 7.1185 1.5162 7.141C1.5512 7.16349 4.38605 7.15224 4.38605 7.15224L4.3623 15.5768C4.3623 15.5768 4.10981 15.5818 3.93607 15.5818C3.76233 15.5818 3.66609 15.5843 3.66734 15.7168C3.66734 15.7868 3.66484 16.0868 3.66734 16.1918C3.67234 16.3918 3.65109 16.4943 3.78358 16.493C3.89982 16.4918 7.84587 16.493 8.06585 16.493C8.28584 16.493 8.28584 16.3543 8.28584 16.2843C8.28584 16.2143 8.28584 15.8443 8.28584 15.7293C8.28584 15.6143 8.1821 15.5668 8.10085 15.5668C8.01961 15.5668 7.60338 15.5668 7.60338 15.5668V9.16714C7.60338 9.16714 8.0321 9.19089 8.49458 9.12089C8.95706 9.05089 9.10705 8.53842 9.18829 8.16844C9.26954 7.79846 9.24704 7.1235 9.24704 7.1235C9.24704 7.1235 16.4092 7.14224 16.5604 7.08475C16.7104 7.026 16.7554 6.86601 16.5492 6.64477ZM2.16616 5.27609H3.00362L2.16616 6.21104C2.16616 5.92481 2.16616 5.26859 2.16616 5.27609ZM2.62364 6.34229L3.26861 5.61108L4.04107 6.34229H2.62364ZM4.42355 6.13605L3.55484 5.27609H4.42355V6.13605ZM6.87842 13.0944L5.99846 13.9844L5.11351 13.0944H6.87842ZM5.33225 12.6782L6.02471 11.9632L6.73592 12.6695L5.33225 12.6782ZM5.97221 8.59342L4.97477 7.65472H6.89342L5.97221 8.59342ZM7.10215 8.10844V9.65836L6.30595 8.9084L7.10215 8.10844ZM6.79092 10.0646H5.15726L5.96596 9.25088L6.79092 10.0646ZM7.10215 10.7933V12.3657L6.33095 11.6145L7.10215 10.7933ZM6.74842 10.4733L5.98721 11.3083L5.17601 10.4883L6.74842 10.4733ZM7.1659 6.60477H5.40224L7.16715 5.58233V6.60477H7.1659ZM6.01346 3.46369C6.01346 3.46369 6.80842 4.61738 6.78842 4.65613C6.77842 4.67612 6.37344 4.66113 5.98846 4.65863C5.62598 4.65738 5.281 4.66988 5.27725 4.64613C5.26725 4.59863 6.01346 3.46369 6.01346 3.46369ZM4.96602 5.26734H6.75842L4.95852 6.35354L4.96602 5.26734ZM4.88977 8.18844L5.64098 8.9309L4.88977 9.69586V8.18844ZM4.88977 10.842L5.66223 11.6208L4.89477 12.4307L4.88977 10.842ZM4.88977 13.5457L5.66598 14.3219L4.88977 15.1068V13.5457ZM5.11351 15.5856L6.00346 14.6593L6.91216 15.5681C6.50594 15.5668 5.54974 15.5793 5.11351 15.5856ZM7.10215 15.0956L6.3297 14.3181L7.10215 13.5144V15.0956ZM7.63213 6.60477L7.63963 5.49358L8.69707 6.60727C8.05085 6.60477 7.63213 6.60477 7.63213 6.60477ZM9.25829 6.51478L8.00586 5.2361L10.527 5.2286L9.25829 6.51478ZM9.82701 6.60977L11.0482 5.41484L12.3231 6.61602C11.4832 6.61352 10.5982 6.61102 9.82701 6.60977ZM11.4807 5.2261L14.1893 5.2186L12.8519 6.52728L11.4807 5.2261ZM15.598 6.62352C15.5855 6.62352 14.6768 6.62102 13.4781 6.61852C13.7706 6.32354 14.368 5.71982 14.6143 5.47108C14.9455 5.80732 15.5005 6.37229 15.588 6.45978C15.7142 6.58477 15.6367 6.62352 15.598 6.62352Z" fill="#F3911C"/>
<path d="M8.13965 7.33466L8.14465 8.23837L8.93961 8.83084C8.93961 8.83084 9.1296 8.6096 9.21584 8.11712C9.28834 7.70589 9.24834 7.32341 9.24834 7.32341C9.24834 7.32341 8.15215 7.31966 8.13965 7.33466Z" fill="#A7D0D6"/>
<path d="M7.59208 7.09356C7.59208 7.09356 7.60208 7.92976 7.58208 7.93976C7.56208 7.94976 4.366 7.93226 4.30601 7.91226C4.24601 7.89226 4.24726 7.10356 4.27601 7.09356C4.30476 7.08356 7.59208 7.09356 7.59208 7.09356Z" fill="#D15116"/>
</svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

@@ -0,0 +1,15 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.87954 1.94633C5.58971 1.87134 1.96115 4.59994 2.03115 9.01971C2.0924 12.9058 5.01224 15.8468 8.79579 15.9081C12.5793 15.9693 15.9317 13.4682 15.9517 8.99971C15.9729 4.4962 12.4981 2.02883 8.87954 1.94633Z" fill="#FF2A23"/>
<path d="M8.8895 3.52361C6.27964 3.61236 3.64603 5.55975 3.58478 8.99332C3.52354 12.4269 6.52463 14.3393 8.8895 14.3393C11.8918 14.3393 14.4005 11.9744 14.338 8.82833C14.2767 5.6835 11.9118 3.42112 8.8895 3.52361Z" fill="#FFFDFE"/>
<path d="M8.95078 4.86112C6.5859 4.80738 4.8385 6.89727 4.81725 8.89091C4.79725 10.9271 6.07093 13.0032 8.90953 13.1057C11.7481 13.2082 13.0831 10.9471 13.1243 9.03465C13.1668 7.12225 11.6444 4.92237 8.95078 4.86112Z" fill="#FF2A23"/>
<path d="M6.24718 8.9658C6.22718 10.7545 7.50086 11.7832 9.04328 11.7832C10.4445 11.7832 11.8194 10.8995 11.8194 9.0283C11.8194 7.34214 10.5445 6.33469 9.12578 6.27345C7.7071 6.21095 6.26718 7.23964 6.24718 8.9658Z" fill="white"/>
<path d="M7.51208 8.95208C7.43583 10.0183 8.30328 10.5283 9.08449 10.5283C9.82445 10.5283 10.5544 10.042 10.5757 9.05458C10.5944 8.12713 9.86695 7.56091 9.08449 7.53341C8.27704 7.50591 7.57332 8.08588 7.51208 8.95208Z" fill="#FB2B22"/>
<path d="M9.34201 9.03589L8.92328 9.26838L9.72199 10.4046C9.72199 10.4046 10.3395 10.4521 10.4819 10.2908C10.6244 10.1296 10.4632 9.65461 10.4632 9.65461L9.34201 9.03589Z" fill="#CC1935"/>
<path d="M11.9268 7.80095C11.8143 7.56846 11.6993 7.38222 11.3944 7.43972C11.0906 7.49722 10.5294 7.8197 10.4244 7.97219C10.3282 8.11219 10.2344 8.28593 10.2344 8.28593C10.2344 8.28593 9.15947 8.8734 8.98948 8.96964C8.72325 9.12213 8.90699 9.43212 9.09448 9.39712C9.35071 9.34962 10.4957 8.8834 10.4957 8.8834C10.4957 8.8834 10.7381 8.94089 10.8806 8.94089C11.0231 8.94089 11.8356 8.53966 11.9456 8.46592C12.1268 8.34217 12.0406 8.03844 11.9268 7.80095Z" fill="#ACB1B5"/>
<path d="M10.4594 9.6521C10.4594 9.6521 10.3994 9.86459 10.1807 10.0846C9.95322 10.3121 9.71948 10.4021 9.71948 10.4021L10.4407 11.4595C10.4407 11.4595 10.9769 11.4983 11.3669 11.1183C11.7569 10.7383 11.5869 10.2408 11.5869 10.2408L10.4594 9.6521Z" fill="#C8C8C8"/>
<path d="M11.1744 12.5706L10.4395 11.4582C10.4395 11.4582 10.5619 11.3932 10.7232 11.2832C11.0194 11.0807 11.1844 10.9195 11.3957 10.5895C11.5106 10.412 11.5844 10.2395 11.5844 10.2395L12.7206 10.8007C12.7206 10.8007 12.6956 11.6782 12.3244 12.1057C11.9531 12.5331 11.1744 12.5706 11.1744 12.5706Z" fill="#CC1935"/>
<path d="M11.1744 12.5707L11.8056 13.5169C11.8056 13.5169 12.6293 13.5982 13.3043 12.7895C13.9792 11.9807 13.7992 11.3158 13.7992 11.3158L12.7205 10.7996C12.7205 10.7996 12.5106 11.3045 12.0931 11.7895C11.6756 12.2745 11.1744 12.5707 11.1744 12.5707Z" fill="#C8C8C8"/>
<path d="M12.7405 14.9157L11.8068 13.5182C11.8068 13.5182 12.5105 13.107 13.0105 12.5158C13.5292 11.9021 13.8005 11.3184 13.8005 11.3184L15.3116 12.0696C15.3116 12.0696 14.9717 12.9483 14.2179 13.742C13.4805 14.5157 12.7405 14.9157 12.7405 14.9157Z" fill="#CC1935"/>
<path d="M11.2006 8.17093C11.3419 8.43466 11.5756 8.43716 11.8231 8.33217C12.0706 8.22717 13.2393 7.40097 13.2393 7.40097C13.2393 7.40097 14.8267 8.19968 15.8529 7.7997C16.8791 7.40097 16.8503 6.31728 16.8503 6.31728L16.2429 5.9323C16.2429 5.9323 16.6854 5.70231 16.8278 5.61731C16.8978 5.57481 17.0016 5.41732 16.9441 5.27608C16.8991 5.16609 16.7266 5.12859 16.5841 5.18609C16.4416 5.24358 16.0304 5.37233 16.0304 5.37233C16.0304 5.37233 16.1941 4.69111 16.1654 4.65361C16.1366 4.61612 15.8416 4.46362 15.8416 4.46362C15.8416 4.46362 14.8817 4.46362 14.758 4.51112C14.6342 4.55862 13.6843 5.49982 13.6843 5.49982L13.228 6.43102L13.033 6.846C13.033 6.846 11.7019 7.44222 11.5019 7.52721C11.3031 7.61346 11.0269 7.84969 11.2006 8.17093Z" fill="#006CA9"/>
<path d="M13.3168 6.73108C13.3168 6.73108 13.8768 6.3261 14.2755 6.12611C14.6742 5.92612 15.4142 5.68239 15.6329 5.65614C15.8517 5.62989 15.9417 5.68864 15.9804 5.79113C16.0192 5.89363 15.8904 6.02237 15.7804 6.08112C15.6717 6.13861 15.0917 6.40235 14.4492 6.74358C13.8068 7.08481 13.5018 7.3348 13.2418 7.39355C13.1106 7.42355 12.9418 7.30605 12.9318 7.10981C12.9243 6.95732 12.9543 6.82983 13.0543 6.53734C13.1768 6.17736 13.4455 5.63614 13.6718 5.28366C14.0068 4.76244 14.4892 4.40871 15.023 4.31246C15.5567 4.21622 15.9942 4.35121 16.1042 4.4412C16.2129 4.5312 16.2066 4.62369 16.1679 4.65494C16.1292 4.68619 16.0717 4.71119 15.7817 4.60869C15.5804 4.53745 14.9392 4.53745 14.4892 4.87243C14.0343 5.20991 13.923 5.41865 13.7243 5.78613C13.5255 6.15361 13.3168 6.73108 13.3168 6.73108Z" fill="#1F87FD"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -0,0 +1,21 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.76871 13.4269C1.76871 12.2208 5.00604 11.5608 8.99958 11.5608C12.9931 11.5608 16.2304 12.2208 16.2304 13.4269C16.2304 15.1244 12.9931 16.4993 8.99958 16.4993C5.00604 16.4993 1.76871 15.1244 1.76871 13.4269Z" fill="#94D1E0"/>
<path d="M2.39368 7.33472V13.0282H2.39743C2.48117 14.5093 5.38727 15.6993 8.96208 15.6993C12.5369 15.6993 15.443 14.5093 15.5267 13.0282H15.5305V7.33472H2.39368Z" fill="#E63F33"/>
<path d="M3.92859 10.4544C5.28977 11.0219 7.07718 11.3344 8.96208 11.3344C10.847 11.3344 12.6394 11.0331 13.9956 10.4544C15.2142 9.93445 15.338 9.58947 15.5305 9.39073V8.63452C15.1743 9.107 14.573 9.53072 13.7518 9.87196C12.4656 10.4082 10.7645 10.7032 8.96083 10.7032C7.15717 10.7032 5.45601 10.4082 4.16983 9.87196C3.34987 9.52947 2.74866 9.107 2.39117 8.63452V9.55572L3.02239 10.0007C3.29488 10.1644 3.59611 10.3157 3.92859 10.4544Z" fill="#BDBDBD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.39345 10.3438C6.53472 10.3438 6.64924 10.4583 6.64924 10.5995V15.6605C6.64924 15.8018 6.53472 15.9163 6.39345 15.9163C6.25218 15.9163 6.13766 15.8018 6.13766 15.6605V10.5995C6.13766 10.4583 6.25218 10.3438 6.39345 10.3438Z" fill="#94D1E0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5307 10.3438C11.672 10.3438 11.7865 10.4583 11.7865 10.5995V15.6605C11.7865 15.8018 11.672 15.9163 11.5307 15.9163C11.3894 15.9163 11.2749 15.8018 11.2749 15.6605V10.5995C11.2749 10.4583 11.3894 10.3438 11.5307 10.3438Z" fill="#94D1E0"/>
<path d="M6.25098 14.2556C6.13474 14.2131 6.03099 14.1244 5.971 14.0169C5.86475 13.8256 5.8735 13.5806 5.86975 13.3356C5.86225 12.8019 6.00974 12.6694 6.18348 12.6694H6.60221C6.91595 12.7982 6.91595 13.0569 6.91595 13.2307C6.91595 13.4119 6.91595 13.3469 6.91595 13.5281C6.91595 13.7906 6.92969 14.0531 6.67846 14.2194C6.65471 14.2356 6.45972 14.3318 6.25098 14.2556Z" fill="#94D1E0"/>
<path d="M11.6694 14.2556C11.7819 14.2044 11.8807 14.1181 11.9494 14.0169C12.0981 13.7981 12.0469 13.5806 12.0507 13.3356C12.0582 12.8307 11.9107 12.6694 11.7369 12.6694H11.3182C11.0045 12.7982 11.0045 13.0569 11.0045 13.2307C11.0045 13.4119 11.0045 13.3469 11.0045 13.5281C11.0045 13.7906 11.0095 14.0269 11.2419 14.2194C11.3494 14.3081 11.5557 14.3081 11.6694 14.2556Z" fill="#94D1E0"/>
<path d="M16.0405 11.3856C16.028 10.7969 15.9205 10.6957 15.6405 10.7194C15.6405 9.63947 15.6405 8.66577 15.6405 8.66577L15.228 8.92076L15.258 13.8255C15.258 13.8255 15.6392 13.943 15.6392 13.5318C15.6392 13.4105 15.6392 12.938 15.6392 12.3318C16.0467 12.1893 16.068 11.7556 16.0405 11.3856Z" fill="#94D1E0"/>
<path d="M2.68615 8.85704L2.27367 8.60205C2.27367 8.60205 2.27367 9.6145 2.27367 10.7194C1.99369 10.6957 1.88619 10.7969 1.87369 11.3857C1.87369 11.6306 1.83244 11.8481 1.96119 12.0669C2.03493 12.1894 2.14118 12.2931 2.27367 12.3319C2.27367 12.9143 2.27367 13.3655 2.27367 13.483C2.27367 13.8943 2.6549 13.7768 2.6549 13.7768L2.68615 8.85704Z" fill="#94D1E0"/>
<path d="M15.8942 7.56211C15.6905 5.15599 11.3494 4.47727 8.96207 4.48477C6.59595 4.46477 2.05119 5.19598 2.03244 7.6671C0.728759 9.99448 4.86104 11.5519 8.99957 11.5619C13.1519 11.5719 17.4404 9.91699 15.8942 7.56211Z" fill="#94D1E0"/>
<path d="M8.96208 10.3231C12.5897 10.3231 15.5305 9.09754 15.5305 7.58574C15.5305 6.07394 12.5897 4.84839 8.96208 4.84839C5.33445 4.84839 2.39368 6.07394 2.39368 7.58574C2.39368 9.09754 5.33445 10.3231 8.96208 10.3231Z" fill="#FFCC80"/>
<path d="M8.96205 10.3657C12.3108 10.3657 15.0255 9.23473 15.0255 7.83959C15.0255 6.44446 12.3108 5.31348 8.96205 5.31348C5.61331 5.31348 2.89862 6.44446 2.89862 7.83959C2.89862 9.23473 5.61331 10.3657 8.96205 10.3657Z" fill="#FEF6E0"/>
<path d="M7.73839 9.38452L3.36987 6.48217L4.05984 6.14844L7.94213 9.09828L7.73839 9.38452Z" fill="#FFCC80"/>
<path d="M10.2932 9.38452L14.6605 6.48217L13.9718 6.14844L10.0883 9.09828L10.2932 9.38452Z" fill="#FFCC80"/>
<path d="M8.20462 8.83084C8.02838 8.5921 7.74214 8.53836 7.70589 8.47211L7.5634 8.21712C7.51465 8.12963 7.4784 8.07463 7.43841 8.02213L2.48367 1.64247C2.36617 1.48998 2.14994 1.45498 1.98869 1.56248C1.80995 1.68372 1.77371 1.93246 1.9112 2.0987L6.91343 8.39961C6.97968 8.48211 7.05468 8.55711 7.13842 8.62085L7.34841 8.78209C7.40466 8.82459 7.39466 9.09583 7.5934 9.27832C7.75839 9.42956 8.01588 9.48456 8.17962 9.31207C8.30586 9.18082 8.30836 8.97083 8.20462 8.83084Z" fill="#BA793E"/>
<path d="M9.85326 8.83084C10.0295 8.5921 10.3157 8.53836 10.352 8.47211L10.4945 8.21712C10.5432 8.12963 10.5795 8.07463 10.6195 8.02213L15.5742 1.64247C15.693 1.48998 15.9092 1.45498 16.0692 1.56248C16.2479 1.68372 16.2842 1.93246 16.1467 2.0987L11.1444 8.40086C11.0782 8.48336 11.0032 8.55836 10.9195 8.6221L10.7095 8.78334C10.6532 8.82584 10.6632 9.09708 10.4645 9.27957C10.2995 9.43081 10.042 9.48581 9.87826 9.31332C9.75202 9.18082 9.74952 8.97083 9.85326 8.83084Z" fill="#BA793E"/>
<path d="M3.38108 10.0295C2.65487 9.57824 2.32114 9.12202 2.37989 8.97078C2.41738 8.87453 2.55613 8.87453 2.62237 8.95328C3.27859 9.72574 4.62352 10.2695 5.59472 10.4494C5.69596 10.4682 5.77346 10.6569 5.50097 10.6832C5.26473 10.7044 4.33978 10.6257 3.38108 10.0295Z" fill="white"/>
<path d="M11.4494 14.0019C11.3669 14.0356 11.2744 13.9356 11.2357 13.8044C11.1969 13.6731 11.1744 13.2156 11.1969 13.0819C11.2182 12.9494 11.2457 12.8682 11.3494 12.8682C11.4532 12.8682 11.5369 12.9519 11.5369 13.0557C11.5369 13.0557 11.5244 13.5556 11.5307 13.6769C11.5369 13.8006 11.5319 13.9681 11.4494 14.0019Z" fill="white"/>
<path d="M6.30596 14.0019C6.22347 14.0356 6.13097 13.9356 6.09222 13.8044C6.05348 13.6731 6.03098 13.2156 6.05348 13.0819C6.07597 12.9482 6.10222 12.8682 6.20597 12.8682C6.30971 12.8682 6.39346 12.9519 6.39346 13.0557C6.39346 13.0557 6.38096 13.5556 6.38721 13.6769C6.39346 13.8006 6.38846 13.9681 6.30596 14.0019Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

@@ -0,0 +1,28 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.68228 8.48206L4.3548 7.77085L3.55359 4.65601L3.34985 4.17104L3.64359 3.44858C3.64359 3.44858 4.05982 3.50732 4.3773 3.86606C4.76353 4.30353 5.3935 5.61221 5.3935 5.61221L5.261 4.57977L5.97971 2.71612C5.97971 2.71612 6.2947 2.68237 6.37844 2.76861C6.52719 2.92111 6.78092 3.99105 6.87092 4.32978C6.96091 4.66851 7.08591 5.13099 7.2434 5.33348C7.40089 5.53597 7.59338 5.58222 7.59338 5.58222L8.1571 3.40483L8.29209 1.42993C8.29209 1.42993 8.48958 1.42868 8.76582 1.48618C8.97456 1.52993 9.11455 1.60117 9.18205 1.68492C9.24954 1.76867 9.25204 3.09985 9.25204 3.09985C9.25204 3.09985 10.4432 2.0849 10.482 2.09615C10.5645 2.11865 10.8207 2.17489 10.9557 2.32239C11.0907 2.46863 10.9782 3.0111 10.877 3.37233C10.7757 3.73356 10.4257 4.90725 10.4257 4.90725L10.742 4.78726L10.812 4.85475L11.4007 3.95105C11.4007 3.95105 11.6132 3.9498 11.7857 4.0348C11.9731 4.12729 12.0856 4.35478 12.0856 4.35478L13.5981 2.16615C13.5981 2.16615 13.7131 2.26864 13.8143 2.36988C13.9155 2.47113 13.9868 2.54238 13.9918 2.64487C13.9968 2.74611 13.6318 3.41983 13.4631 3.75856C13.2943 4.09729 13.0118 4.77476 13.0118 4.77476L14.2418 3.83606C14.2418 3.83606 14.6493 3.89355 14.6618 4.02855C14.673 4.16354 14.4568 4.41353 14.2418 4.78601C14.0268 5.15849 13.6668 5.88095 13.5418 6.12844C13.4168 6.37592 13.5868 6.38842 13.5868 6.38842L15.0605 5.45722C15.0605 5.45722 15.378 5.52222 15.408 5.66971C15.4417 5.83845 15.0767 6.17468 14.8392 6.44467C14.6018 6.71591 14.0943 7.36962 13.8355 7.87834C13.5768 8.38707 13.4031 8.70455 13.4031 8.70455L11.1594 10.9307L5.18976 10.5695L4.68228 8.48206Z" fill="url(#paint0_radial_718_88)"/>
<path d="M12.1458 6.02463C11.9958 6.05963 11.5221 6.87958 11.0796 8.12702C10.8134 8.87698 10.6371 9.69693 10.6471 10.0894C10.6571 10.4819 11.2496 10.3619 11.2496 10.3619L11.5008 9.54694C11.5008 9.54694 11.7821 8.59074 12.1146 7.87703C12.5033 7.03957 13.007 6.5071 12.9695 6.37836C12.9195 6.20587 12.357 5.97463 12.1458 6.02463Z" fill="#FBDE93"/>
<path d="M13.6343 3.89228C13.4243 4.01478 12.9506 4.80724 12.8094 5.10972C12.6681 5.4122 12.6631 5.98092 12.8181 6.05467C13.0093 6.14467 13.2218 5.5022 13.5443 4.96848C13.8668 4.43476 14.248 3.83104 14.248 3.83104C14.248 3.83104 13.8755 3.75104 13.6343 3.89228Z" fill="#FBDE93"/>
<path d="M14.4293 5.4622C14.1081 5.59845 13.5094 6.23716 13.3019 6.5584C12.9194 7.15212 12.6381 7.81583 12.5369 8.08707C12.4369 8.3583 12.3557 8.73078 12.6681 8.55954C12.9806 8.3883 13.3819 7.58334 13.8956 6.85963C14.4905 6.01968 15.063 5.45096 15.063 5.45096C15.063 5.45096 14.6905 5.35096 14.4293 5.4622Z" fill="#FBDE93"/>
<path d="M13.0807 1.87995C12.9569 1.86495 12.6182 2.7649 12.427 3.19738C12.2357 3.62985 11.9807 4.20232 11.9807 4.20232C11.9807 4.20232 11.8333 5.34101 12.0445 5.36101C12.2557 5.38101 12.6695 3.99233 12.9782 3.35612C13.2794 2.73615 13.5969 2.16243 13.5969 2.16243C13.5969 2.16243 13.3219 1.90994 13.0807 1.87995Z" fill="#FBDE93"/>
<path d="M10.8558 4.13481C10.7445 4.25231 10.807 4.85728 10.807 4.85728C10.807 4.85728 11.2395 5.17976 11.3208 5.10851C11.402 5.03727 11.4058 3.94482 11.4058 3.94482C11.4058 3.94482 11.037 3.94357 10.8558 4.13481Z" fill="#FBDE93"/>
<path d="M9.96212 4.95848C9.80587 5.08973 9.91212 5.70469 9.87212 6.25592C9.81212 7.08087 9.57089 8.77078 9.49964 9.26451C9.42839 9.75823 9.3584 10.3007 9.3584 10.3007L10.1334 10.1795C10.1334 10.1795 10.4346 7.87583 10.5558 6.98963C10.6771 6.10342 10.8571 4.84724 10.7371 4.77599C10.6158 4.70725 10.1534 4.79724 9.96212 4.95848Z" fill="#FBDE93"/>
<path d="M9.86827 2.01991C9.67078 2.05741 9.2533 3.09985 9.2533 3.09985C9.2533 3.09985 9.2283 3.85231 9.2283 4.21479C9.2283 4.57727 9.15831 5.00975 9.2783 5.00975C9.39829 5.00975 9.63078 4.31604 9.87202 3.71232C10.1133 3.1086 10.4832 2.09615 10.4832 2.09615C10.4832 2.09615 10.1483 1.96741 9.86827 2.01991Z" fill="#FBDE93"/>
<path d="M7.59727 1.63861C7.46103 1.78235 7.67726 2.15233 7.69726 3.16728C7.71726 4.18347 7.59227 5.5809 7.59227 5.5809C7.59227 5.5809 7.69976 5.5734 7.76726 5.78214C7.82476 5.95838 7.9185 7.02957 8.03849 6.98958C8.15974 6.94958 8.30973 6.23462 8.43097 5.02718C8.55222 3.81974 8.50097 2.88479 8.46097 2.50231C8.42097 2.11983 8.29098 1.42737 8.29098 1.42737C8.29098 1.42737 7.79851 1.42737 7.59727 1.63861Z" fill="#FBDE93"/>
<path d="M5.97981 2.71606C5.97981 2.71606 5.39859 2.75731 5.2386 3.0698C5.07735 3.38228 5.1286 3.53352 5.1586 3.83476C5.1886 4.13599 5.28359 4.73721 5.28359 4.73721C5.28359 4.73721 5.81732 5.40092 6.10855 5.23968C6.39978 5.07969 5.97981 2.71606 5.97981 2.71606Z" fill="#FBDE93"/>
<path d="M3.64363 3.44854C3.64363 3.44854 3.32114 3.38855 3.1299 3.42854C2.93866 3.46854 2.94866 3.64978 3.03991 3.77103C3.1299 3.89227 3.54613 4.64473 3.54613 4.64473C3.54613 4.64473 3.66988 4.64723 3.83112 4.70723C3.99236 4.76722 4.26859 4.91722 4.25734 4.67598C4.23735 4.25475 3.64363 3.44854 3.64363 3.44854Z" fill="#FBDE93"/>
<path d="M4.55231 8.19957C4.55231 8.19957 3.94484 6.97088 3.49236 6.29592C3.03989 5.62095 2.56616 5.09848 2.56616 4.98849C2.56616 4.87724 2.87865 4.76725 3.05864 4.71725C3.23863 4.66726 3.55236 4.65601 3.55236 4.65601C3.55236 4.65601 4.29607 5.91344 4.59855 6.5984C4.90104 7.28337 5.18227 8.08083 5.11228 8.17832C5.04853 8.26582 4.5973 8.20707 4.55231 8.19957Z" fill="#FBDE93"/>
<path d="M4.96121 6.00465C4.91372 6.18089 5.14245 6.98085 5.2837 7.55457C5.42494 8.12829 5.59618 8.732 5.59618 8.732L6.07865 9.7182L6.61238 9.52696C6.61238 9.52696 6.33114 8.98324 6.1699 8.42077C6.00866 7.8558 5.79992 6.10339 5.58493 5.87465C5.43494 5.71341 4.99246 5.89465 4.96121 6.00465Z" fill="#FBDE93"/>
<path d="M6.63232 5.80348C6.50607 5.88598 6.70231 7.22216 6.80356 7.77588C6.9048 8.3296 7.11604 8.91332 7.22604 9.18456C7.33728 9.45579 7.65851 10.1408 7.65851 10.1408C7.65851 10.1408 7.991 10.5332 8.001 10.4932C8.01099 10.4532 8.15224 9.95951 8.15224 9.95951C8.15224 9.95951 7.70851 8.65083 7.63726 8.08711C7.56727 7.52339 7.45602 5.81348 7.34603 5.75349C7.23604 5.69349 6.83356 5.67349 6.63232 5.80348Z" fill="#FBDE93"/>
<path d="M8.87582 5.83344C8.72458 5.97343 8.57459 7.82584 8.51334 8.50955C8.4521 9.19326 8.4021 10.1995 8.4021 10.1995L9.07581 10.1495C9.07581 10.1495 9.32705 7.66459 9.36705 7.34211C9.40705 7.01963 9.58829 6.28592 9.49829 5.99343C9.4083 5.70345 9.00582 5.71345 8.87582 5.83344Z" fill="#FBDE93"/>
<path d="M4.35233 8.42834C4.30983 8.52459 5.62351 15.713 5.64476 15.8205C5.66601 15.9279 5.62351 16.0254 5.82725 16.1217C6.03224 16.2179 6.35222 16.2692 6.55721 16.2917C6.7622 16.3129 8.17338 16.3154 8.18463 16.1442C8.19587 15.9717 12.4144 9.82702 12.4144 9.82702C12.4144 9.82702 13.1919 8.52334 13.1381 8.41584C13.0844 8.30835 12.8744 8.35835 12.6456 8.51459C12.4169 8.67208 12.2057 9.06081 11.5632 9.49204C10.867 9.95951 9.94453 10.0583 9.01708 10.047C8.16213 10.0358 7.14593 9.88077 6.41847 9.34204C5.7785 8.86707 5.58976 8.52459 5.01979 8.37335C4.51732 8.2396 4.35233 8.42834 4.35233 8.42834Z" fill="#FD703F"/>
<path d="M5.34619 8.89825L6.53863 16.2941C6.53863 16.2941 7.9598 16.4991 9.06725 16.5203C10.1759 16.5416 11.4971 16.3104 11.4971 16.3104L12.4571 13.0655L13.272 8.7845C13.272 8.7845 13.0708 8.58326 12.8445 8.7495C12.6708 8.877 12.1346 9.70695 11.1871 10.0732C10.1184 10.4857 8.19229 10.5157 6.98985 10.0082C5.88741 9.54446 5.55118 8.94074 5.34619 8.89825Z" fill="#FF2A23"/>
<path d="M12.1883 16.0829C11.9458 16.2279 11.3809 16.3253 11.3809 16.3253C11.3809 16.3253 11.9408 13.603 12.2521 12.1818C12.5633 10.7606 13.012 8.35701 13.012 8.35701C13.012 8.35701 13.192 8.32827 13.377 8.35076C13.587 8.37701 13.712 8.43826 13.7545 8.50451C13.8232 8.612 13.4095 10.6194 13.0045 12.5793C12.6345 14.3792 12.3171 16.0066 12.1883 16.0829Z" fill="#DC0D25"/>
<defs>
<radialGradient id="paint0_radial_718_88" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.0729 0.0159706) scale(10.3905 10.3905)">
<stop offset="0.447" stop-color="#FCC216"/>
<stop offset="0.5897" stop-color="#FABB16"/>
<stop offset="0.7939" stop-color="#F3A814"/>
<stop offset="0.9212" stop-color="#ED9913"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,33 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.73857 12.2331C2.73857 12.2331 2.61858 13.0181 2.89732 13.7105C3.1298 14.2905 3.73227 15.0542 4.88471 15.6367C6.03715 16.2191 7.36083 16.4241 9.31448 16.4641C11.2681 16.5054 13.2755 16.0492 14.2392 15.203C15.2042 14.3555 15.5416 13.643 15.5142 12.8693C15.4867 12.0956 15.0754 11.8019 15.0754 11.8019L2.73857 12.2331Z" fill="#FCB745"/>
<path d="M3.2974 11.4443L2.72743 12.0143C2.72743 12.0143 2.57244 12.7768 3.4324 13.6017C4.08361 14.2254 5.23605 15.3091 9.06335 15.3379C12.6457 15.3654 13.7719 14.5779 14.4631 14.0217C15.1555 13.4655 15.4155 12.4993 15.3468 12.228C15.2793 11.9568 15.0668 11.8006 15.0668 11.8006L3.2974 11.4443Z" fill="#E78B20"/>
<path d="M3.54113 12.2581C3.54113 12.2581 3.37989 12.5319 3.43238 12.7056C3.47363 12.8419 3.55488 13.3981 5.16854 14.103C6.02225 14.4768 7.47467 14.7818 9.36082 14.7268C11.5982 14.6618 13.3906 14.2118 14.2581 13.3968C15.1468 12.5631 14.9768 12.0669 14.9768 12.0669L3.54113 12.2581Z" fill="#FFD290"/>
<path d="M3.07979 10.3582C3.07979 10.3582 2.54482 10.9031 2.45607 11.1456C2.37233 11.3744 2.38358 11.7068 2.53107 11.9756C2.72981 12.3355 2.9773 12.373 3.28353 12.5568C3.61476 12.7543 4.13849 13.3705 5.42717 13.6417C6.71585 13.913 8.41201 14.0355 9.32071 14.0355C10.2294 14.0355 11.4369 14.0217 12.563 13.6417C13.6892 13.2618 14.9779 12.5155 15.2766 11.8506C15.5754 11.1856 15.2904 10.7919 15.2904 10.7919L14.6392 10.3582H3.07979Z" fill="#6D544D"/>
<path d="M2.76732 9.66693C2.76732 9.66693 2.35235 9.64818 2.25235 10.2769C2.17986 10.7344 2.50984 11.0231 2.50984 11.0231C2.50984 11.0231 2.33485 11.2743 2.53734 11.5931C2.72733 11.8918 2.93107 11.9593 3.35104 12.0268C3.65353 12.0756 3.81227 11.9856 4.206 12.203C4.69972 12.4755 4.91096 12.963 5.58968 13.1117C6.26839 13.2605 7.02835 13.1255 7.0546 13.0168C7.08085 12.908 6.10465 12.5018 4.34099 11.5106C3.74602 11.1769 3.20105 10.7231 3.05231 10.4931C2.90357 10.2631 2.76732 9.66693 2.76732 9.66693Z" fill="#9B7169"/>
<path d="M11.302 12.9768C11.302 13.1531 11.8857 13.0718 12.157 12.9231C12.4282 12.7743 12.8619 12.3943 13.2556 12.2306C13.6494 12.0681 13.7981 12.2169 14.3681 11.9456C14.9381 11.6744 15.2505 11.0369 15.2505 11.0369C15.2505 11.0369 15.528 10.6944 15.5505 10.4544C15.5855 10.0882 15.5493 9.91072 15.2918 9.82948C15.0343 9.74823 14.4731 10.5919 14.3918 10.6869C14.3106 10.7819 13.1082 11.6606 12.6332 11.9994C12.1582 12.3381 11.302 12.8818 11.302 12.9768Z" fill="#9B7169"/>
<path d="M3.29733 9.20577C3.29733 9.20577 2.61862 9.28702 2.52362 9.572C2.42863 9.85699 2.72736 10.277 3.51357 10.8069C4.15853 11.2407 6.36842 12.2844 7.24462 12.8281C8.24832 13.4518 9.09827 14.323 9.23952 14.3343C9.41576 14.348 10.6394 13.0481 12.3331 11.9056C13.943 10.8182 15.0467 10.5494 15.4404 9.74824C15.6517 9.31826 15.1417 9.06953 15.1417 9.06953L8.60205 6.58716L3.29733 9.20577Z" fill="#FFE869"/>
<path d="M9.32088 13.6018C9.52337 13.5793 10.5821 12.5568 11.8307 11.7706C13.0794 10.9844 14.9243 10.142 15.1956 9.66823C15.4668 9.1945 14.8156 8.88077 14.8156 8.88077L7.21724 7.60583L2.76747 9.35574C2.76747 9.35574 2.52374 9.43699 2.68623 9.64073C2.84872 9.84447 3.10621 10.1832 3.67618 10.5357C4.36864 10.9644 6.14604 11.6756 7.27099 12.2856C8.39843 12.8956 9.07714 13.628 9.32088 13.6018Z" fill="#FFBE01"/>
<path d="M3.37988 9.55827L3.61612 10.1907C3.61612 10.1907 5.31603 11.9044 8.6971 11.9719C12.0619 12.0394 13.9481 10.4382 13.9481 10.4382L14.3781 9.35828L3.37988 9.55827Z" fill="#F4482B"/>
<path d="M7.81222 10.6031L7.54598 11.0294C7.54598 11.0294 8.1147 11.3706 8.27844 11.4294C8.51343 11.5131 8.74467 11.4831 9.0909 11.3756C9.43713 11.2694 10.8746 10.6969 10.8746 10.6969C10.8746 10.6969 12.0632 10.7144 12.8095 10.7281C13.4707 10.7406 14.1331 10.5032 14.4794 10.1032C14.8256 9.7032 15.3631 8.84574 15.3631 8.84574C15.3631 8.84574 15.6431 8.7795 15.8293 8.56576C16.0155 8.35202 16.0293 8.16578 16.0293 8.16578C16.0293 8.16578 15.9755 7.8058 15.7768 7.7133C15.5768 7.61956 6.66978 7.07458 6.66978 7.07458L3.10122 7.34082L2.5 8.57951L4.48365 10.2707L7.27975 10.4307L7.81222 10.6031Z" fill="#A6B732"/>
<path d="M2.68614 7.48837C2.4849 7.39713 2.27366 7.40838 2.17992 7.67461C2.08617 7.94085 2.11492 8.05834 2.11492 8.05834C2.11492 8.05834 1.84118 8.11584 1.80744 8.45957C1.76744 8.87205 2.21991 9.01829 2.21991 9.01829C2.21991 9.01829 2.24116 9.17828 2.4199 9.33828C2.62364 9.52077 2.82738 9.50202 2.82738 9.50202C2.82738 9.50202 3.36485 10.0307 3.72483 10.2432C4.08482 10.4557 4.60354 10.6032 5.12351 10.5895C5.64348 10.5757 5.81597 10.337 6.2022 10.4695C6.58843 10.602 7.61213 11.1407 8.02461 11.1494C8.67707 11.1632 10.0882 10.1507 10.4082 10.1645C10.7282 10.1782 12.1794 10.4045 12.9518 10.4045C13.7243 10.4045 14.0043 10.1782 14.1768 9.95199C14.3493 9.72576 14.643 9.15329 14.5493 9.13954C14.4555 9.12579 13.843 9.63201 13.4043 9.59201C12.9643 9.55201 12.5794 9.29953 12.0331 9.29953C11.4869 9.29953 8.82456 9.64576 8.65082 9.72576C8.47708 9.80575 7.73212 10.3782 7.58588 10.352C7.43964 10.3257 6.93341 9.8995 6.76092 9.80575C6.58843 9.71201 4.77228 9.65201 4.51104 9.69951C4.32355 9.73326 4.05857 9.7795 4.05857 9.7795C4.05857 9.7795 4.08482 9.36702 3.85858 9.12704C3.63234 8.88705 2.88613 8.47457 2.88613 8.47457C2.88613 8.47457 2.97862 7.62087 2.68614 7.48837Z" fill="#C2DD1F"/>
<path d="M14.4956 8.20707C14.4956 8.20707 15.3081 8.20707 15.5343 8.19332C15.7605 8.17957 16.0268 8.16707 16.0268 8.16707C16.0268 8.16707 15.9493 7.79834 15.8318 7.6021C15.5918 7.19962 15.0143 7.08838 15.0143 7.08838L14.4956 8.20707Z" fill="#C2DD1F"/>
<path d="M2.49988 6.99588L2.41113 7.38836C2.41113 7.38836 3.63982 9.56449 9.16203 9.64699C12.3944 9.69574 14.2518 8.71454 15.0717 7.91208C15.5629 7.43085 15.5792 6.96338 15.5792 6.96338L2.49988 6.99588Z" fill="#DE8010"/>
<path d="M2.22002 5.91342C2.15003 7.00086 2.41501 7.39084 2.41501 7.39084C2.41501 7.39084 4.24242 9.04575 8.90967 9.122C13.6932 9.1995 15.2481 7.61333 15.5206 7.15085C15.6943 6.85712 15.8331 6.2809 15.7231 5.44094C15.5406 4.05352 13.9432 1.42115 8.88717 1.44115C3.83119 1.46115 2.32252 4.33475 2.22002 5.91342Z" fill="url(#paint0_radial_721_184)"/>
<path d="M7.0235 2.79484C7.06225 3.00733 6.10355 3.7148 5.96481 3.03608C5.83482 2.39736 6.98725 2.5986 7.0235 2.79484Z" fill="#FDEAC8"/>
<path d="M7.36586 4.12605C7.41586 4.25729 8.39831 4.44603 8.29331 3.89231C8.18332 3.31609 7.26337 3.85606 7.36586 4.12605Z" fill="#FDEAC8"/>
<path d="M8.22709 2.60604C8.25209 2.79478 9.39703 3.01477 9.37328 2.4823C9.34454 1.82483 8.19835 2.3873 8.22709 2.60604Z" fill="#FDEAC8"/>
<path d="M6.33846 4.56352C6.34471 4.60602 6.06348 4.82725 5.77599 4.9135C5.5235 4.98975 5.26477 4.95725 5.23602 4.64351C5.20852 4.34103 5.42351 4.23104 5.681 4.25728C5.97723 4.28603 6.32596 4.48227 6.33846 4.56352Z" fill="#FDEAC8"/>
<path d="M8.71705 5.44724C8.66955 5.2485 7.59336 4.97226 7.66586 5.60098C7.7396 6.2272 8.7683 5.65848 8.71705 5.44724Z" fill="#FDEAC8"/>
<path d="M9.72448 4.016C9.67198 3.95475 9.42574 3.966 9.12576 4.04475C8.90952 4.10224 8.74953 4.34598 8.89952 4.57722C9.07451 4.84721 9.36824 4.73346 9.55573 4.50472C9.70823 4.31973 9.78322 4.08475 9.72448 4.016Z" fill="#FDEAC8"/>
<path d="M10.8771 2.55482C10.8434 2.51858 10.5971 2.49358 10.3959 2.59107C10.1934 2.68857 10.0184 2.92106 10.2059 3.13854C10.3959 3.35728 10.6784 3.23104 10.8121 2.9998C10.9184 2.81356 10.9284 2.60982 10.8771 2.55482Z" fill="#FDEAC8"/>
<path d="M11.6495 4.45727C11.617 4.25353 10.712 3.89105 10.7183 4.44602C10.7245 5.01474 11.6782 4.64101 11.6495 4.45727Z" fill="#FDEAC8"/>
<path d="M12.9005 3.45228C12.8705 3.29604 11.7193 3.16979 11.9156 3.75101C12.1193 4.35723 12.9368 3.64227 12.9005 3.45228Z" fill="#FDEAC8"/>
<path d="M11.3157 5.49714C11.3232 5.32215 10.447 4.92092 10.382 5.44589C10.317 5.97087 11.3057 5.72463 11.3157 5.49714Z" fill="#FDEAC8"/>
<defs>
<radialGradient id="paint0_radial_721_184" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.9328 2.94018) rotate(89.3583) scale(5.50481 9.59198)">
<stop offset="0.517" stop-color="#DF8016"/>
<stop offset="0.6426" stop-color="#E58C21"/>
<stop offset="1" stop-color="#F5AC3C"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

View File

@@ -0,0 +1,27 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.53148 15.0048C4.03297 14.9075 4.32384 14.2326 4.18115 13.4973C4.03847 12.762 3.51626 12.2448 3.01478 12.3421C2.51329 12.4394 2.22242 13.1144 2.3651 13.8497C2.50779 14.585 3.02999 15.1021 3.53148 15.0048Z" fill="#616161"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.80501 13.7743C2.59761 12.0082 2.47024 10.1592 2.55166 8.48633L3.30073 8.52278C3.22216 10.1373 3.34478 11.9405 3.54985 13.6868L2.80501 13.7743Z" fill="#616161"/>
<path d="M15.6327 13.8482C15.7754 13.1129 15.4845 12.4379 14.983 12.3406C14.4815 12.2433 13.9593 12.7605 13.8166 13.4958C13.674 14.2311 13.9648 14.906 14.4663 15.0034C14.9678 15.1007 15.49 14.5835 15.6327 13.8482Z" fill="#616161"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4494 13.6868C14.6545 11.9405 14.7771 10.1373 14.6985 8.52278L15.4476 8.48633C15.529 10.1592 15.4016 12.0082 15.1942 13.7743L14.4494 13.6868Z" fill="#616161"/>
<path d="M15.2405 9.32571C14.5943 9.29572 14.0581 8.81074 13.9743 8.16953C13.8806 7.44331 13.6394 6.60461 13.0344 5.79965C12.0832 4.53597 10.6908 3.86725 9.00586 3.86725C7.82467 3.86725 6.16476 4.20224 4.96357 5.79965C4.3511 6.61461 4.11237 7.47081 4.02112 8.20827C3.94488 8.81949 3.4499 9.29322 2.83368 9.32197L2.75869 9.32571C2.32621 9.34571 1.96873 8.98823 1.99373 8.557C2.05373 7.50331 2.31871 5.63216 3.37491 4.22723C4.69734 2.46733 6.69723 1.49988 9.00586 1.49988C11.3057 1.49988 13.3006 2.46733 14.6231 4.22473C15.6805 5.63091 15.9442 7.50331 16.0055 8.55576C16.0305 8.98823 15.673 9.34571 15.2405 9.32571Z" fill="#616161"/>
<path d="M5.84104 16.4103C4.6261 16.5791 3.23243 15.6829 2.95119 13.6092C2.67121 11.5443 3.65241 10.2894 4.64735 10.0969L5.84104 16.4103Z" fill="#424242"/>
<path d="M5.91105 9.66946L4.8536 9.8507C4.42613 9.93319 4.09114 10.2644 4.00365 10.6919C3.88616 11.2681 3.81116 12.1893 4.05365 13.4455C4.29738 14.7004 4.71111 15.5266 5.03734 16.0179C5.27733 16.3804 5.71231 16.5628 6.13979 16.4791L7.19723 16.2979C7.70845 16.1991 7.8347 14.6342 7.47847 12.8043C7.12348 10.9744 6.42227 9.57071 5.91105 9.66946Z" fill="#757575"/>
<path opacity="0.5" d="M7.30094 16.0162C7.72721 15.9335 7.80678 14.4958 7.47867 12.8049C7.15057 11.1141 6.53903 9.81048 6.11276 9.8932C5.6865 9.97591 5.60693 11.4137 5.93503 13.1045C6.26314 14.7953 6.87468 16.0989 7.30094 16.0162Z" fill="#424242"/>
<path d="M7.23096 12.853C7.45345 13.9979 7.5022 14.9516 7.34095 14.9829C7.17971 15.0141 6.86723 14.1117 6.64599 12.9667C6.4235 11.8218 6.37476 10.8681 6.536 10.8368C6.69724 10.8056 7.00847 11.7081 7.23096 12.853Z" fill="url(#paint0_linear_715_302)"/>
<path d="M12.1582 16.4103C13.3731 16.5791 14.7668 15.6829 15.0481 13.6092C15.328 11.5443 14.3468 10.2894 13.3519 10.0969L12.1582 16.4103Z" fill="#424242"/>
<path d="M12.0882 9.66946L13.1456 9.8507C13.5731 9.93319 13.9081 10.2644 13.9956 10.6919C14.1131 11.2681 14.1881 12.1893 13.9456 13.4455C13.7018 14.7004 13.2881 15.5266 12.9619 16.0179C12.7219 16.3804 12.2869 16.5628 11.8594 16.4791L10.802 16.2979C10.2908 16.1991 10.1645 14.6342 10.5208 12.8043C10.8757 10.9744 11.577 9.57071 12.0882 9.66946Z" fill="#757575"/>
<path opacity="0.5" d="M12.0637 13.1035C12.3918 11.4127 12.3122 9.97494 11.886 9.89222C11.4597 9.8095 10.8482 11.1131 10.5201 12.804C10.1919 14.4948 10.2715 15.9325 10.6978 16.0153C11.1241 16.098 11.7356 14.7943 12.0637 13.1035Z" fill="#424242"/>
<path d="M10.7683 12.853C10.5458 13.9979 10.4971 14.9516 10.6583 14.9829C10.8196 15.0141 11.132 14.1117 11.3533 12.9667C11.5758 11.8218 11.6245 10.8681 11.4633 10.8368C11.302 10.8056 10.9908 11.7081 10.7683 12.853Z" fill="url(#paint1_linear_715_302)"/>
<path d="M15.8855 8.76198C15.5318 9.00197 15.0955 8.73198 14.9618 8.367C14.8068 7.45705 14.5006 6.40086 13.9006 5.52965C12.7769 3.80599 10.8882 2.94229 8.9996 2.94104C7.11095 2.94229 5.2223 3.80599 4.09861 5.52965C3.49864 6.40086 3.19365 7.4583 3.03741 8.367C2.90367 8.73198 2.46744 9.00197 2.11371 8.76198C2.08496 8.74198 2.04121 8.69199 1.99622 8.66449C2.03121 9.04572 2.3637 9.34195 2.75868 9.32445L2.86742 9.31945C3.63988 9.3257 3.94611 8.83448 4.01736 8.24076C4.1061 7.49705 4.34234 6.62585 4.96481 5.79964C6.1635 4.20597 7.81841 3.86849 8.9996 3.86724C10.1808 3.86849 11.8357 4.20597 13.0344 5.79964C13.6469 6.6146 13.8856 7.4708 13.9768 8.20826C14.0543 8.82698 14.3543 9.3257 15.198 9.3232L15.2393 9.3257C15.6342 9.34445 15.9667 9.04697 16.0017 8.66574C15.958 8.69199 15.9142 8.74198 15.8855 8.76198Z" fill="#424242"/>
<path d="M2.83121 6.20091C2.71497 6.11841 2.62372 6.02717 2.78996 5.3997C2.9912 4.64349 3.9249 3.17982 5.80605 2.20987C6.15478 2.02988 7.00224 1.76114 7.26098 2.22737C7.50596 2.66984 6.32228 2.99233 5.9048 3.21607C4.81236 3.80353 4.02365 4.63224 3.59992 5.28721C3.46493 5.49595 3.1087 6.3959 2.83121 6.20091Z" fill="#757575"/>
<defs>
<linearGradient id="paint0_linear_715_302" x1="6.67969" y1="12.9608" x2="7.03923" y2="12.891" gradientUnits="userSpaceOnUse">
<stop offset="0.2016" stop-color="#212121"/>
<stop offset="1" stop-color="#212121" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint1_linear_715_302" x1="11.3196" y1="12.9609" x2="10.96" y2="12.8911" gradientUnits="userSpaceOnUse">
<stop offset="0.2016" stop-color="#212121"/>
<stop offset="1" stop-color="#212121" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -0,0 +1,25 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.9153 14.4092L15.1966 14.0943C15.1916 14.0555 15.1854 14.0168 15.1754 13.9793C15.1216 13.7705 14.9241 13.5168 14.5979 13.443L14.5316 12.5068L13.8317 12.5468L13.9192 13.543C13.6592 13.6855 13.5392 13.9368 13.5479 14.2255L12.813 14.8217L13.2505 15.3704L13.8417 14.873C13.9892 14.9867 14.1829 15.0817 14.4229 15.068C14.7116 15.0517 14.9091 14.9017 15.0316 14.7492L15.6678 15.0905L15.9153 14.4092Z" fill="#878787"/>
<path d="M6.0135 14.1379C5.88226 12.4643 4.50233 11.9118 3.49989 11.9743C2.35495 12.0468 1.29375 12.8843 1.3125 14.3129C1.32875 15.5691 2.11621 16.5553 3.72737 16.5553C5.01731 16.5566 6.1085 15.3554 6.0135 14.1379ZM3.67113 15.7279C2.69993 15.7454 2.13996 15.1791 2.13121 14.3292C2.12371 13.533 2.77492 12.8418 3.59488 12.8655C4.38234 12.888 4.96231 13.3342 5.05855 14.1617C5.1548 14.9891 4.52983 15.7129 3.67113 15.7279Z" fill="#4D453C"/>
<path d="M3.58723 13.1605C2.95101 13.1268 2.39354 13.5655 2.38604 14.3692C2.37854 15.2204 3.14975 15.5141 3.59473 15.5229C4.0397 15.5316 4.74092 15.2054 4.76342 14.4179C4.78841 13.5817 4.33469 13.1993 3.58723 13.1605Z" fill="#858585"/>
<path d="M14.1731 11.6731C12.9419 11.7556 11.867 12.8155 11.882 14.1467C11.8982 15.5466 12.9719 16.5403 14.3319 16.5403C15.6605 16.5403 16.7642 15.5616 16.7417 14.0742C16.718 12.4606 15.4693 11.5856 14.1731 11.6731ZM14.3644 15.5779C13.6094 15.5704 13.0357 14.9729 12.9644 14.3217C12.8694 13.4505 13.4157 12.8168 14.1894 12.7543C14.9931 12.6905 15.6605 13.168 15.748 14.0267C15.8355 14.8854 15.1593 15.5866 14.3644 15.5779Z" fill="#4D453C"/>
<path d="M1.89367 12.2531C1.93992 12.3343 2.45864 12.2931 2.85612 12.4681C3.11486 12.5818 3.39734 12.8818 3.35735 13.2955C3.31735 13.7092 3.2386 14.0905 3.2386 14.0905L3.96981 14.3292C3.96981 14.3292 4.25605 13.6855 4.41479 13.518C4.57353 13.3505 4.93226 13.0168 5.45723 13.088C5.86096 13.143 6.31594 13.5093 6.31594 13.5093C6.31594 13.5093 6.0297 11.7756 5.99721 11.7594C5.96596 11.7431 4.39104 11.8231 4.39104 11.8231C4.39104 11.8231 3.67483 11.5294 2.94362 11.7119C2.21116 11.8956 1.86117 12.1981 1.89367 12.2531Z" fill="#DB0D2A"/>
<path d="M4.2399 11.498L3.48494 13.7892C3.48494 13.7892 3.1962 13.7904 3.08746 14.1867C2.99996 14.5054 3.21495 14.7116 3.41369 14.7666C3.64868 14.8329 3.95241 14.7279 4.02616 14.4566C4.12115 14.1067 3.93116 13.9479 3.93116 13.9154C3.93116 13.8829 4.77362 11.5618 4.77362 11.5618L4.2399 11.498Z" fill="#E1D9DC"/>
<path d="M6.84961 13.3155L8.20829 14.8479L10.8519 14.8354L12.4706 14.3529L12.2493 13.488L10.9394 12.8705C10.9394 12.8705 11.5944 11.0169 11.4581 10.9556C11.3219 10.8944 9.7907 9.86816 9.7907 9.86816L6.88711 10.8444L6.84961 13.3155Z" fill="#2F2F2F"/>
<path d="M12.3868 11.2294C12.3868 11.2294 11.9393 11.6369 11.6793 11.9981C11.4969 12.2531 11.4456 12.5481 11.4456 12.5481L11.8281 12.7206C11.8281 12.7206 12.0593 12.1906 12.6005 11.8756C13.2218 11.5131 13.6942 11.5594 13.768 11.7194C13.8417 11.8794 13.5367 11.9331 13.1005 12.3868C12.7793 12.7206 12.6068 13.0418 12.6068 13.0418C12.6068 13.0418 14.053 13.6343 14.053 13.7093C14.053 13.783 13.793 14.5242 13.793 14.5242L12.1493 14.1292C12.1493 14.1292 11.0869 13.808 10.9756 14.1292C10.8644 14.4505 11.3581 14.6355 11.3581 14.6355L10.9006 14.9317L9.47946 14.3142C9.47946 14.3142 9.39571 13.9218 9.83819 13.0543C10.5069 11.7406 11.5056 11.0769 11.5056 11.0769L12.3868 11.2294Z" fill="#5E6268"/>
<path d="M10.9369 14.7805C10.9406 14.833 10.9806 14.8955 11.1481 14.8867C11.3156 14.878 12.993 14.9042 13.1955 14.8605C13.398 14.8167 16.1091 13.6405 16.1628 13.5343C16.2016 13.4568 16.2078 13.1955 16.0816 12.9268C15.9404 12.6268 15.7991 12.4768 15.6141 12.5118C15.4291 12.5468 11.6518 14.348 11.3693 14.4617C11.0869 14.5767 10.9281 14.6567 10.9369 14.7805Z" fill="#E0E0E0"/>
<path d="M12.9218 14.2767C12.9518 14.348 13.2455 14.338 13.5868 14.1817C13.928 14.0255 16.0991 12.963 16.0991 12.963C16.0991 12.963 16.0629 12.8543 15.9991 12.753C15.9491 12.6743 15.8654 12.5918 15.8654 12.5918C15.8654 12.5918 13.3968 13.8492 13.258 13.918C13.078 14.008 12.8656 14.143 12.9218 14.2767Z" fill="#FEFEFE"/>
<path d="M15.063 9.95188L15.9642 10.7481C15.9642 10.7481 16.1654 10.4256 16.1654 10.3944C16.1654 10.3619 15.5617 9.59814 15.5617 9.59814L15.063 9.95188Z" fill="#484D51"/>
<path d="M12.5669 11.2956C12.5669 11.2956 13.793 10.7694 14.308 10.5394C15.1142 10.1807 15.9392 9.71821 16.0867 9.63072C16.3529 9.47198 16.3504 9.32323 16.2767 9.26199C16.2029 9.20074 16.0054 9.28698 15.9054 9.29948C15.8054 9.31198 15.5105 9.32448 15.5105 9.32448L13.2993 9.92945C13.2993 9.92945 11.8294 10.8557 11.8656 10.8557C11.9019 10.8557 12.5669 11.2956 12.5669 11.2956Z" fill="#DC0D2A"/>
<path d="M9.77075 10.1719C9.77075 10.1719 10.737 11.1119 10.8757 11.1819C11.0157 11.2519 11.2819 11.2831 11.5044 11.2394C11.7269 11.1956 12.9531 10.2669 13.4806 10.1019C14.008 9.93693 14.9167 9.91193 15.2155 9.74569C15.5142 9.57945 15.5104 9.32446 15.5104 9.32446L12.9156 9.50445C12.9156 9.50445 12.3656 10.3882 11.3332 10.3307C10.5395 10.2857 9.94199 9.61945 9.94199 9.61945L9.77075 10.1719Z" fill="#464C4F"/>
<path d="M12.7831 11.9943C13.0119 12.1156 13.1206 11.8518 13.0631 11.7343C12.9419 11.4869 12.4919 10.7831 12.3769 10.7369C12.2819 10.6994 11.767 11.0731 11.7157 11.1119C11.6645 11.1494 11.6832 11.2306 11.792 11.3094C11.9319 11.4106 12.6057 11.9006 12.7831 11.9943Z" fill="#C8C8C8"/>
<path d="M5.00966 11.0169L3.4585 10.7057L4.22596 9.22699L5.04966 8.40329L5.53714 8.22705C5.53714 8.22705 5.12591 8.77327 4.90467 9.117C4.76718 9.33074 4.62218 9.68197 4.62218 9.68197C4.62218 9.68197 4.79343 9.64447 4.92967 9.62822C5.06591 9.61197 5.21715 9.60072 5.21715 9.60072C5.21715 9.60072 5.17216 9.30824 5.2359 9.157C5.29965 9.0045 5.43214 8.90576 5.52839 8.89701C5.62463 8.88951 5.68838 8.93701 5.68838 8.93701C5.68838 8.93701 5.67463 8.70577 5.69588 8.64077C5.73588 8.52078 6.07211 8.37954 6.48834 8.22705C6.90706 8.0733 7.37704 7.85956 7.50328 7.88956C7.58203 7.90831 7.76702 8.26579 7.67077 8.36954C7.57453 8.47328 6.08836 9.1695 6.08836 9.1695L6.33584 9.84946L5.00966 11.0169Z" fill="#464C4F"/>
<path d="M7.70974 10.7943L9.73088 10.7206L10.1246 11.1318L8.80218 11.953L7.521 11.6405L7.40601 10.8443L7.70974 10.7943Z" fill="#464C4F"/>
<path d="M6.84709 12.0594L5.78339 13.2205C5.78339 13.2205 6.17337 13.913 6.28087 14.2105C6.39336 14.5205 6.5521 15.0805 6.5521 15.0805C6.5521 15.0805 10.5844 15.1954 10.7731 15.1704C10.9619 15.1454 11.1756 14.9242 11.0769 14.8342C10.9781 14.7442 9.44195 14.1118 9.44195 14.1118C9.44195 14.1118 8.23451 14.1942 8.03702 14.1205C7.83953 14.0468 7.19582 13.1593 7.23082 13.0918C7.31581 12.9243 8.88323 11.9031 8.88323 11.9031L8.75199 11.7631L7.54455 11.4919L7.6183 10.9994C7.6183 10.9994 8.16077 10.5644 8.22576 10.5644C8.29076 10.5644 8.94073 10.4582 9.30196 10.3757C9.66319 10.2932 10.1069 10.1945 10.2132 10.137C10.3194 10.0795 10.3981 9.98072 10.3694 9.82448C10.3469 9.70698 10.0407 9.60324 10.0407 9.60324L6.87084 9.422L6.21962 9.49074C6.21962 9.49074 6.13213 9.78323 5.6634 10.0707C5.19467 10.3582 4.39722 10.5444 4.39722 10.5444L6.84709 12.0594Z" fill="#DC0D2A"/>
<path d="M3.62231 9.04443C3.62231 9.04443 4.55851 8.03324 5.05474 7.58951C5.42222 7.26078 5.7047 7.04829 5.8097 7.00829C5.86595 6.98704 6.40967 6.96829 6.46466 6.98454C6.52091 7.00079 6.57716 7.08829 6.48841 7.17703C6.40092 7.26453 6.12343 7.58201 5.87345 7.8445C5.75845 7.96449 5.53721 8.22323 5.53721 8.22323L5.12973 8.37447L4.10604 8.88569L3.62231 9.04443Z" fill="#AEE3FD"/>
<path d="M5.52591 8.22571C5.52591 8.22571 4.46222 8.40445 3.73976 8.94692C3.0173 9.48939 2.73731 10.5818 2.72106 10.6881C2.70481 10.7943 2.56732 11.1481 2.83856 11.318C2.9698 11.4005 3.23104 11.3618 3.50977 11.428C3.74101 11.4818 4.26473 11.658 4.66721 12.0193C5.06969 12.3805 5.78465 13.2267 5.78465 13.2267L8.75199 11.7643C8.75199 11.7643 7.89579 10.7631 5.91589 10.6318C3.936 10.5006 3.73101 10.5493 3.73101 10.5493C3.73101 10.5493 3.97975 9.83437 4.33098 9.3669C4.7997 8.74193 5.52591 8.22571 5.52591 8.22571Z" fill="#FE2A22"/>
<path d="M8.37714 13.2118C8.37714 13.2118 8.1859 12.6768 8.63587 12.3681C8.97585 12.1343 9.40708 12.1656 9.69957 12.5543C9.99205 12.943 9.73081 13.3993 9.57582 13.528C9.0696 13.9505 8.60837 13.5505 8.60837 13.5505C8.60837 13.5505 8.63837 13.7705 8.43963 13.9217C8.29839 14.0292 7.94466 14.0605 7.79842 13.7917C7.64093 13.5018 7.80217 13.2868 7.96716 13.1955C8.17965 13.0768 8.37714 13.2118 8.37714 13.2118Z" fill="#464C4F"/>
<path d="M3.04728 9.81194C3.04728 9.81194 3.721 9.80819 3.726 9.83569C3.731 9.86319 3.47851 10.5019 3.47851 10.5019C3.47851 10.5019 2.7073 10.9019 2.68355 10.8969C2.65981 10.8919 2.67855 10.5532 2.77855 10.2782C2.87854 10.0032 3.04728 9.81194 3.04728 9.81194Z" fill="#D9E3DF"/>
<path d="M6.21851 9.49316C6.21851 9.49316 6.80597 9.99064 7.86592 10.0806C8.92586 10.1706 10.0421 9.60441 10.0421 9.60441C10.0421 9.60441 8.25465 8.77695 8.03841 8.79195C7.80592 8.80695 6.22726 9.44316 6.21851 9.49316Z" fill="#FE2A22"/>
<path d="M3.32099 8.642C3.35223 8.75449 3.81971 9.10697 4.12344 9.07947C4.40218 9.05448 4.35468 8.53575 4.35218 8.40826C4.34843 8.15077 4.32593 7.93328 4.31093 7.79829C4.29468 7.6508 4.17219 7.63455 4.08595 7.70455C3.9997 7.77329 3.28724 8.52325 3.32099 8.642Z" fill="#464C4F"/>
</svg>

After

Width:  |  Height:  |  Size: 9.0 KiB

View File

@@ -0,0 +1,100 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.10094 15.5491L5.56352 16.2291L2.34369 8.94571L3.88111 8.26575L7.10094 15.5491Z" fill="url(#paint0_linear_746_959)"/>
<path d="M2.87744 9.64192C2.93369 9.52192 3.00868 9.43943 3.08743 9.36943C3.16743 9.30068 3.25367 9.24694 3.34492 9.20569C3.43741 9.16444 3.53491 9.13694 3.6399 9.12444C3.7449 9.11319 3.85614 9.11444 3.98238 9.15319C3.92614 9.27444 3.85239 9.35568 3.77239 9.42693C3.6924 9.49567 3.60615 9.54942 3.51366 9.59067C3.42116 9.63067 3.32367 9.65817 3.21867 9.67067C3.11618 9.68191 3.00493 9.68191 2.87744 9.64192Z" fill="#FFC107"/>
<path d="M3.73865 11.5881C3.79489 11.4681 3.86989 11.3856 3.94864 11.3156C4.02863 11.2469 4.11488 11.1931 4.20612 11.1519C4.29862 11.1106 4.39611 11.0831 4.50111 11.0706C4.6061 11.0594 4.71735 11.0606 4.84359 11.0994C4.78734 11.2206 4.7136 11.3018 4.6336 11.3731C4.5536 11.4418 4.46736 11.4956 4.37486 11.5368C4.28237 11.5768 4.18487 11.6043 4.07988 11.6168C3.97613 11.6281 3.86614 11.6268 3.73865 11.5881Z" fill="#FFC107"/>
<path d="M3.16492 10.2906C3.22116 10.1706 3.29616 10.0881 3.37491 10.0181C3.4549 9.94937 3.54115 9.89562 3.63239 9.85437C3.72489 9.81312 3.82238 9.78563 3.92738 9.77313C4.03237 9.76188 4.14362 9.76313 4.26986 9.80187C4.21361 9.92312 4.13987 10.0044 4.05987 10.0756C3.97987 10.1444 3.89363 10.1981 3.80113 10.2394C3.70864 10.2793 3.61114 10.3068 3.50615 10.3193C3.4024 10.3306 3.29241 10.3293 3.16492 10.2906Z" fill="#FFC107"/>
<path d="M4.31238 12.8854C4.36862 12.7655 4.44362 12.683 4.52237 12.613C4.60236 12.5442 4.68861 12.4905 4.77985 12.4492C4.87235 12.408 4.96984 12.3805 5.07484 12.368C5.17983 12.3567 5.28983 12.358 5.41732 12.3967C5.36107 12.518 5.28733 12.5992 5.20733 12.6705C5.12733 12.7392 5.04109 12.793 4.94859 12.8342C4.8561 12.8742 4.7586 12.9017 4.65361 12.9142C4.54987 12.9254 4.43987 12.9242 4.31238 12.8854Z" fill="#FFC107"/>
<path d="M4.0249 12.2368C4.08115 12.1168 4.15615 12.0343 4.23489 11.9643C4.31489 11.8955 4.40113 11.8418 4.49238 11.8005C4.58487 11.7593 4.68237 11.7318 4.78736 11.7193C4.89236 11.708 5.00235 11.7093 5.12984 11.748C5.0736 11.8693 4.99985 11.9505 4.91986 12.0218C4.83986 12.0905 4.75361 12.1443 4.66112 12.1855C4.56862 12.2255 4.47113 12.253 4.36613 12.2655C4.26239 12.2768 4.1524 12.2755 4.0249 12.2368Z" fill="#FFC107"/>
<path d="M5.1723 14.8316C5.2298 14.7116 5.30355 14.6291 5.38229 14.5591C5.46229 14.4904 5.54853 14.4366 5.63978 14.3954C5.73227 14.3541 5.82977 14.3266 5.93476 14.3141C6.03976 14.3029 6.14975 14.3041 6.27724 14.3429C6.221 14.4641 6.14725 14.5454 6.06726 14.6166C5.98726 14.6854 5.90101 14.7391 5.80977 14.7804C5.71727 14.8204 5.61978 14.8479 5.51478 14.8604C5.40979 14.8716 5.2998 14.8704 5.1723 14.8316Z" fill="#FFC107"/>
<path d="M3.45117 10.9394C3.50742 10.8194 3.58241 10.7369 3.66116 10.6669C3.74116 10.5982 3.8274 10.5444 3.91865 10.5032C4.01114 10.4619 4.10864 10.4344 4.21363 10.4219C4.31863 10.4107 4.42987 10.4119 4.55611 10.4507C4.49987 10.5719 4.42612 10.6532 4.34612 10.7244C4.26613 10.7932 4.17988 10.8469 4.08739 10.8882C3.99489 10.9282 3.8974 10.9557 3.7924 10.9682C3.68866 10.9794 3.57867 10.9781 3.45117 10.9394Z" fill="#FFC107"/>
<path d="M4.88611 14.1829C4.94236 14.0629 5.01735 13.9804 5.0961 13.9104C5.17609 13.8417 5.26234 13.788 5.35358 13.7467C5.44608 13.7055 5.54357 13.678 5.64857 13.6655C5.75356 13.6542 5.86356 13.6555 5.99105 13.6942C5.9348 13.8155 5.86106 13.8967 5.78106 13.9679C5.70107 14.0367 5.61482 14.0904 5.52357 14.1317C5.43108 14.1717 5.33358 14.1992 5.22859 14.2117C5.1236 14.2229 5.01235 14.2217 4.88611 14.1829Z" fill="#FFC107"/>
<path d="M4.59863 13.5343C4.65488 13.4143 4.72988 13.3318 4.80862 13.2618C4.88862 13.193 4.97486 13.1393 5.06611 13.098C5.1586 13.0568 5.2561 13.0293 5.36109 13.0168C5.46609 13.0055 5.57608 13.0068 5.70357 13.0455C5.64733 13.1668 5.57358 13.248 5.49359 13.3193C5.41359 13.388 5.32734 13.4418 5.2361 13.483C5.1436 13.523 5.04611 13.5505 4.94111 13.563C4.83612 13.5742 4.72613 13.573 4.59863 13.5343Z" fill="#FFC107"/>
<path d="M3.91738 8.48077L2.48995 9.11199C2.31746 9.18824 2.11747 9.11074 2.04123 8.93825C1.96498 8.76576 2.04248 8.56577 2.21497 8.48952L3.64239 7.85831C3.81488 7.78206 4.01487 7.85956 4.09112 8.03205C4.16737 8.20329 4.08987 8.40453 3.91738 8.48077Z" fill="#FFA000"/>
<g opacity="0.81">
<path opacity="0.81" d="M4.57732 13.9392L3.70612 11.993L5.09104 11.0693L6.16349 13.2905L4.57732 13.9392Z" fill="url(#paint1_linear_746_959)"/>
</g>
<path d="M2.11749 9.04568C2.23124 9.17068 2.42748 9.13568 2.42748 9.13568L2.48247 9.25942L4.02239 8.58571L3.96865 8.45821C3.96865 8.45821 4.13114 8.34697 4.11739 8.12573L2.11749 9.04568Z" fill="url(#paint2_linear_746_959)"/>
<path d="M2.81244 16.1078V16.4803H15.9867V16.1078C15.9867 13.9817 12.6482 12.9292 9.39959 12.933C6.17101 12.9367 2.81244 13.8554 2.81244 16.1078Z" fill="url(#paint3_linear_746_959)"/>
<path d="M3.73236 13.6642C3.73236 14.8329 5.57851 16.4803 5.57851 16.4803H13.0106C13.0106 16.4803 14.7205 14.7879 14.7205 13.408C14.7205 13.408 12.8244 12.8643 9.39956 12.933C5.57851 13.0093 3.73236 13.6642 3.73236 13.6642Z" fill="#212121"/>
<path d="M9.99328 13.3067C12.2157 13.3067 13.7056 13.5692 14.3106 13.7005C14.1418 14.5792 13.2969 15.6266 12.8494 16.1003H5.72476C5.14104 15.5554 4.33983 14.6229 4.15234 13.9355C4.8023 13.7592 6.53846 13.3705 9.40706 13.313C9.6033 13.3092 9.79954 13.3067 9.99328 13.3067ZM9.99328 12.9268C9.80079 12.9268 9.6033 12.928 9.39956 12.933C5.57976 13.0093 3.73236 13.6642 3.73236 13.6642C3.73236 14.8329 5.57851 16.4803 5.57851 16.4803H13.0106C13.0106 16.4803 14.7205 14.7879 14.7205 13.408C14.7205 13.408 13.0419 12.9268 9.99328 12.9268Z" fill="url(#paint4_linear_746_959)"/>
<path d="M7.55591 13.1867C7.55591 12.8142 7.58091 12.1593 8.02713 11.9505C8.33587 11.8068 10.4095 11.8805 10.7295 12.0455C11.1345 12.2543 11.2432 12.8342 11.2432 13.1855C11.2432 13.8154 10.4183 15.7028 9.40081 15.7028C8.38336 15.7028 7.55591 13.8167 7.55591 13.1867Z" fill="url(#paint5_linear_746_959)"/>
<path opacity="0.61" d="M7.67841 12.2467L7.59216 12.6592C7.59216 12.6592 8.23838 13.4404 9.39957 13.4404C10.7407 13.4404 11.1945 12.7604 11.1945 12.7604L11.0782 12.3742L7.67841 12.2467Z" fill="black"/>
<path d="M10.6057 16.4803C11.5294 15.4716 12.3882 14.2704 12.6444 13.0917L11.6744 12.8355C11.5519 12.803 11.427 12.873 11.3895 12.993C11.0282 14.1579 9.80079 15.4753 8.68335 16.4803H10.6057Z" fill="black"/>
<path d="M10.3483 16.4828C11.3132 15.4504 12.2307 14.2004 12.4969 12.9755L11.527 12.7193C11.4045 12.6868 11.2795 12.7568 11.242 12.8767C10.8657 14.0879 9.55332 15.4654 8.40088 16.4828H10.3483Z" fill="url(#paint6_linear_746_959)"/>
<g opacity="0.31">
<path opacity="0.31" d="M7.57971 12.9655C7.57971 12.9655 9.06213 15.1716 10.857 16.4278C10.857 16.4278 9.84834 16.544 9.81959 16.4803C9.79085 16.4165 7.65846 14.0841 7.65846 14.0841L7.57971 12.9655Z" fill="black"/>
</g>
<path d="M10.3333 16.4802C9.22584 15.4891 7.98215 14.1554 7.57968 12.9654C7.52718 12.8092 7.36219 12.7229 7.20595 12.7742L6.23975 13.1004C6.50473 14.2941 7.38344 15.4891 8.31964 16.4802H10.3333Z" fill="url(#paint7_linear_746_959)"/>
<path d="M9.39959 2.17358C7.19471 2.17358 5.15356 4.53096 5.15356 7.92453C5.15356 11.2994 7.2572 12.828 9.39959 12.828C11.542 12.828 13.6456 11.2994 13.6456 7.92453C13.6456 4.53096 11.6045 2.17358 9.39959 2.17358Z" fill="#F9DDBD"/>
<path d="M7.3172 8.86824C7.66167 8.86824 7.94092 8.57892 7.94092 8.22202C7.94092 7.86513 7.66167 7.57581 7.3172 7.57581C6.97273 7.57581 6.69348 7.86513 6.69348 8.22202C6.69348 8.57892 6.97273 8.86824 7.3172 8.86824Z" fill="#312D2D"/>
<path d="M11.482 8.86824C11.8265 8.86824 12.1057 8.57892 12.1057 8.22202C12.1057 7.86513 11.8265 7.57581 11.482 7.57581C11.1375 7.57581 10.8583 7.86513 10.8583 8.22202C10.8583 8.57892 11.1375 8.86824 11.482 8.86824Z" fill="#312D2D"/>
<path d="M8.25714 7.08204C8.13965 6.92704 7.86841 6.70081 7.34094 6.70081C6.81347 6.70081 6.54223 6.92704 6.42474 7.08204C6.37224 7.15078 6.38599 7.23078 6.42224 7.27828C6.45599 7.32327 6.55473 7.36452 6.66347 7.32702C6.77222 7.28952 6.98471 7.17953 7.34219 7.17703C7.69842 7.17953 7.91091 7.28952 8.0209 7.32702C8.12965 7.36452 8.22839 7.32327 8.26214 7.27828C8.29589 7.23078 8.30964 7.15078 8.25714 7.08204Z" fill="#454140"/>
<path d="M12.3744 7.08204C12.2569 6.92704 11.9857 6.70081 11.4582 6.70081C10.9307 6.70081 10.6595 6.92704 10.542 7.08204C10.4895 7.15078 10.5032 7.23078 10.5395 7.27828C10.5732 7.32327 10.672 7.36452 10.7807 7.32702C10.8895 7.28952 11.102 7.17953 11.4594 7.17703C11.8157 7.17953 12.0282 7.28952 12.1382 7.32702C12.2469 7.36452 12.3456 7.32327 12.3794 7.27828C12.4131 7.23078 12.4256 7.15078 12.3744 7.08204Z" fill="#454140"/>
<g opacity="0.17">
<path opacity="0.17" d="M9.39959 2.0061C7.05846 2.0061 5.15356 3.57852 5.15356 7.15208C5.15356 10.7456 6.99097 12.7105 9.39959 12.7105C11.8082 12.7105 13.8494 10.6994 13.8494 7.10458C13.8481 3.53102 11.7407 2.0061 9.39959 2.0061ZM9.39959 12.4106C7.82217 12.4106 5.73603 11.5981 5.73603 8.657C5.73603 5.73216 7.48969 5.81215 9.39959 5.81215C11.3095 5.81215 13.0631 5.73216 13.0631 8.657C13.0631 11.5981 10.977 12.4106 9.39959 12.4106Z" fill="black"/>
</g>
<path d="M9.44585 8.60449C10.8395 8.60449 13.3319 9.94067 13.3319 9.94067C13.2557 11.2181 11.592 12.688 9.5346 12.658C6.08228 12.608 5.46606 10.0432 5.46606 10.0432C5.46606 10.0432 8.05218 8.60449 9.44585 8.60449Z" fill="url(#paint8_linear_746_959)"/>
<path opacity="0.4" d="M13.3281 9.99189C13.3281 9.98814 13.3294 9.98439 13.3294 9.98189C13.1969 9.90314 13.0632 9.82065 12.9282 9.73565C12.1645 9.36067 10.4945 8.60571 9.4471 8.60571C8.48215 8.60571 6.85473 9.31943 5.85479 9.84315C5.6548 9.97689 5.76979 9.98939 5.57605 10.1119C6.23102 9.8819 8.11842 9.02944 9.41835 9.02944C10.6945 9.02819 12.6294 9.7669 13.3281 9.99189Z" fill="#BDBDBD"/>
<path d="M9.39959 1.49988C6.95596 1.49988 4.55609 3.26728 4.55609 6.8421C4.55609 10.4357 6.88472 12.933 9.39834 12.933C11.912 12.933 14.2406 10.4357 14.2406 6.8421C14.2418 3.26728 11.8432 1.49988 9.39959 1.49988ZM9.39959 12.6555C7.75217 12.6555 5.57604 11.0919 5.57604 8.15078C5.57604 5.22593 7.40594 5.30593 9.39959 5.30593C11.3932 5.30593 13.2231 5.22593 13.2231 8.15078C13.2231 11.0906 11.0457 12.6555 9.39959 12.6555Z" fill="url(#paint9_linear_746_959)"/>
<path d="M9.53456 2.71479C9.53456 2.71479 7.72966 2.15357 6.08225 3.92723C4.50233 5.62839 4.9948 8.87947 4.9948 8.87947C4.9948 8.87947 4.82731 6.04587 6.69221 4.27221C8.32338 2.71979 9.53456 2.71479 9.53456 2.71479Z" fill="url(#paint10_linear_746_959)"/>
<path d="M12.5669 5.14215C13.6194 6.1646 13.6344 8.88946 13.6344 8.88946C13.6344 8.88946 14.4231 6.14585 12.9781 4.66718C11.7832 3.44349 10.1283 3.93847 10.1283 3.93847C10.1283 3.93847 11.4582 4.06471 12.5669 5.14215Z" fill="url(#paint11_linear_746_959)"/>
<g opacity="0.38">
<path opacity="0.38" d="M9.39959 1.74986C10.612 1.74986 11.7357 2.19734 12.5619 3.00855C13.4981 3.92725 13.9918 5.25218 13.9918 6.8421C13.9918 8.38826 13.5394 9.81819 12.7144 10.8969C13.1744 10.1707 13.4731 9.25197 13.4731 8.15203C13.4731 6.8021 13.0756 5.92589 12.2569 5.47092C11.5682 5.08844 10.6895 5.05719 9.69582 5.05719H9.54833H9.40084H9.25334H9.10585C7.42219 5.05719 5.3273 5.05719 5.3273 8.15203C5.3273 9.25322 5.62478 10.1707 6.08601 10.8969C5.26105 9.81819 4.80858 8.38826 4.80858 6.8421C4.80858 5.25343 5.30355 3.92725 6.2385 3.00855C7.06346 2.19734 8.18715 1.74986 9.39959 1.74986ZM9.39959 1.49988C6.95596 1.49988 4.55609 3.26728 4.55609 6.8421C4.55609 10.4357 6.88472 12.933 9.39834 12.933C11.912 12.933 14.2406 10.4357 14.2406 6.8421C14.2418 3.26728 11.8432 1.49988 9.39959 1.49988ZM9.39959 5.30593C9.49833 5.30593 9.59707 5.30593 9.69457 5.30593C11.4982 5.30593 13.2231 5.37717 13.2231 8.15078C13.2231 11.0906 10.9733 12.6555 9.39959 12.6555C7.82592 12.6555 5.57604 11.0906 5.57604 8.15078C5.57604 5.37717 7.30095 5.30593 9.1046 5.30593C9.2021 5.30593 9.30084 5.30593 9.39959 5.30593Z" fill="url(#paint12_linear_746_959)"/>
</g>
<defs>
<linearGradient id="paint0_linear_746_959" x1="3.42906" y1="9.47632" x2="5.21624" y2="13.306" gradientUnits="userSpaceOnUse">
<stop stop-color="#646464"/>
<stop offset="1" stop-color="#444444"/>
</linearGradient>
<linearGradient id="paint1_linear_746_959" x1="5.47577" y1="13.9528" x2="4.50005" y2="11.5885" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint2_linear_746_959" x1="3.28571" y1="8.99385" x2="3.11769" y2="8.58672" gradientUnits="userSpaceOnUse">
<stop/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear_746_959" x1="9.39947" y1="16.6913" x2="9.39947" y2="15.0654" gradientUnits="userSpaceOnUse">
<stop offset="0.1696" stop-color="#646464"/>
<stop offset="1" stop-color="#757575"/>
</linearGradient>
<linearGradient id="paint4_linear_746_959" x1="9.22613" y1="12.927" x2="9.22613" y2="16.4801" gradientUnits="userSpaceOnUse">
<stop stop-color="#646464"/>
<stop offset="0.2649" stop-color="#575757"/>
<stop offset="0.7538" stop-color="#353535"/>
<stop offset="1" stop-color="#212121"/>
</linearGradient>
<linearGradient id="paint5_linear_746_959" x1="9.39944" y1="12.6796" x2="9.39944" y2="15.4456" gradientUnits="userSpaceOnUse">
<stop offset="0.0744" stop-color="#444444"/>
<stop offset="0.653" stop-color="#212121"/>
</linearGradient>
<linearGradient id="paint6_linear_746_959" x1="11.6532" y1="13.1431" x2="10.2698" y2="15.2902" gradientUnits="userSpaceOnUse">
<stop stop-color="#646464"/>
<stop offset="1" stop-color="#444444"/>
</linearGradient>
<linearGradient id="paint7_linear_746_959" x1="8.38795" y1="15.2569" x2="6.35194" y2="12.3334" gradientUnits="userSpaceOnUse">
<stop stop-color="#646464"/>
<stop offset="1" stop-color="#9E9E9E"/>
</linearGradient>
<linearGradient id="paint8_linear_746_959" x1="9.39942" y1="9.89264" x2="9.39942" y2="12.9208" gradientUnits="userSpaceOnUse">
<stop offset="0.0744" stop-color="#444444"/>
<stop offset="0.653" stop-color="#212121"/>
</linearGradient>
<linearGradient id="paint9_linear_746_959" x1="9.39934" y1="1.56391" x2="9.39934" y2="12.7995" gradientUnits="userSpaceOnUse">
<stop offset="0.0744" stop-color="#444444"/>
<stop offset="0.653" stop-color="#212121"/>
</linearGradient>
<linearGradient id="paint10_linear_746_959" x1="5.59864" y1="6.32337" x2="8.30122" y2="2.98737" gradientUnits="userSpaceOnUse">
<stop stop-color="#0D0C0C"/>
<stop offset="1" stop-color="#0D0C0C" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint11_linear_746_959" x1="12.9059" y1="6.30033" x2="11.0478" y2="4.10448" gradientUnits="userSpaceOnUse">
<stop stop-color="#0D0C0C"/>
<stop offset="1" stop-color="#0D0C0C" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint12_linear_746_959" x1="9.39934" y1="1.49988" x2="9.39934" y2="12.933" gradientUnits="userSpaceOnUse">
<stop stop-color="#BDBDBD"/>
<stop offset="1" stop-color="#646464"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,14 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.7946 16.0455C8.7946 16.0455 13.3519 15.573 14.2118 14.8205C14.6418 14.4443 15.1305 13.6406 15.4155 13.1656C15.7005 12.6894 15.888 12.3482 16.0605 11.8757C16.4042 10.9295 16.5767 8.09214 16.0605 6.67347C15.5442 5.25479 14.878 3.98611 13.9531 3.23365C13.0444 2.49119 11.4382 1.48999 8.98709 1.48999C7.8934 1.48999 7.06469 1.71998 6.36473 1.96247C5.31728 2.3262 4.72231 2.72368 4.32234 3.05866C3.84236 3.46239 2.89241 4.29609 2.23745 5.78976C1.98121 6.37223 1.69872 7.18844 1.61373 7.9184C1.31249 10.5195 1.78497 12.7132 3.28989 14.2606C4.79481 15.808 5.35353 15.593 5.35353 15.593L8.7946 16.0455Z" fill="#F47A00"/>
<path d="M4.90973 14.103C4.90973 14.103 4.6735 14.103 4.39351 13.9743C4.11353 13.8456 3.08233 12.4044 2.88859 11.8457C2.69485 11.2869 2.25363 9.31955 2.45861 8.3846C2.57611 7.85088 2.7161 7.4384 2.7161 7.4384C2.7161 7.4384 3.23607 6.03597 3.30107 5.58475C3.36607 5.13352 3.94229 4.6223 4.22102 4.29981C4.49976 3.97733 4.6935 3.65485 5.20972 3.35361C5.72594 3.05238 6.26341 3.05238 6.60715 2.88114C6.95088 2.70865 7.46085 2.39741 7.96207 2.25742C8.91327 1.99119 9.48824 2.27867 10.0045 2.25742C10.5207 2.23617 11.0094 2.39866 11.6431 2.73865C12.0944 2.98113 12.4431 3.25987 12.8506 3.58235C13.258 3.90483 13.8243 4.09857 14.2392 4.62105C14.6367 5.12102 14.7117 5.69599 14.9492 6.34096C15.1854 6.98592 15.4454 7.3259 15.4892 7.75588C15.5317 8.18586 15.4942 8.52334 15.5154 9.01707C15.5367 9.51079 15.6467 9.88077 15.6217 10.1683C15.5692 10.7882 15.3317 10.9245 15.2679 11.3544C15.2029 11.7844 14.928 12.2869 14.7892 12.5606C14.4142 13.3018 13.7255 13.7031 13.4355 13.8956C12.9318 14.2343 4.90973 14.103 4.90973 14.103Z" fill="#FFA726"/>
<path d="M3.13976 8.74452C2.69603 8.68577 2.29105 9.13949 2.3198 9.67322C2.3498 10.2069 2.70478 10.3844 3.08976 10.2857C3.57724 10.1607 3.75098 9.71321 3.74098 9.38698C3.73098 9.06075 3.58224 8.80326 3.13976 8.74452Z" fill="#F27B00"/>
<path d="M5.95848 5.41845C5.56975 5.10347 5.71225 4.67474 5.92224 4.40225C6.13222 4.12977 6.71094 4.00353 6.95218 4.4235C7.19342 4.84348 7.01343 5.13597 6.82219 5.33596C6.6347 5.53344 6.21722 5.62844 5.95848 5.41845Z" fill="#F27B00"/>
<path d="M7.60341 4.27854C7.7709 4.61477 8.24462 4.67352 8.56086 4.49603C8.87709 4.31854 8.98583 3.97231 8.79834 3.65732C8.61335 3.34609 8.13713 3.2036 7.7909 3.47983C7.44592 3.75482 7.50466 4.08105 7.60341 4.27854Z" fill="#F27B00"/>
<path d="M9.54186 5.50722C9.69561 5.8622 10.1331 5.9197 10.4718 5.75096C10.8105 5.58222 11.0493 5.13099 10.8468 4.78226C10.6431 4.43478 10.1868 4.40353 9.8831 4.61352C9.57936 4.82351 9.37062 5.11224 9.54186 5.50722Z" fill="#F27B00"/>
<path d="M11.397 4.59352C11.6807 4.82101 12.1757 4.68977 12.3569 4.35478C12.4956 4.09855 12.5719 3.80356 12.2857 3.51733C11.9994 3.23109 11.5319 3.35859 11.317 3.66107C11.1482 3.90106 11.1007 4.35728 11.397 4.59352Z" fill="#F27B00"/>
<path d="M4.47722 6.2734C4.26473 6.2884 4.29598 6.72463 4.28598 7.3471C4.27598 7.94207 4.15849 9.19075 4.22474 10.5307C4.25223 11.0944 4.19349 12.6843 4.24723 13.483C4.30223 14.283 4.26973 15.1767 4.26973 15.1767C4.26973 15.1767 5.55092 16.5041 8.99448 16.5541C12.6218 16.6066 14.1017 14.8717 14.1017 14.8717C14.1017 14.8717 14.0617 12.6831 14.0655 11.8219C14.0705 10.5482 13.9917 8.12581 13.9917 7.73458C13.9917 7.34335 14.0242 6.4209 13.883 6.2959C13.7417 6.17091 12.738 6.22716 11.8231 6.24966C11.0044 6.26965 8.14828 6.27715 7.09833 6.27715C6.04839 6.27715 4.91595 6.24216 4.47722 6.2734Z" fill="#3C3C3C"/>
<path d="M4.96974 7.12343C4.83599 7.20467 4.81349 8.28212 4.84474 9.28581C4.87599 10.2895 4.87599 11.2445 5.20472 11.307C5.53346 11.3695 5.86219 10.6332 6.20717 10.3508C6.55215 10.0683 6.95963 9.84953 7.22587 9.4583C7.4921 9.06707 7.78959 8.43961 7.93083 8.12712C8.07207 7.81339 8.5108 7.39091 8.4633 7.13968C8.4158 6.88844 6.88088 6.98343 6.41091 6.98343C5.94093 6.98343 5.25222 6.95219 4.96974 7.12343Z" fill="#858585"/>
<path d="M9.34083 6.96717C9.14209 7.10841 8.93335 7.65588 8.79211 7.90712C8.65087 8.15836 8.25964 8.70583 8.55712 8.89457C8.85461 9.08206 9.44957 8.64333 9.63831 8.51834C9.8258 8.39335 10.077 8.33085 10.1708 8.2521C10.2645 8.17336 10.7345 7.48464 10.8445 7.37465C10.9545 7.26466 11.1895 7.13966 11.1895 7.01467C11.1895 6.88967 10.6095 6.88968 10.0933 6.90467C9.57707 6.91967 9.45082 6.88843 9.34083 6.96717Z" fill="#858585"/>
<path d="M7.53947 10.147C7.39448 10.3445 7.14824 10.7733 7.42948 10.977C7.71196 11.1807 8.02445 10.9607 8.24444 10.7258C8.46442 10.4908 8.57317 10.177 8.30693 9.8958C8.0407 9.61456 7.71071 9.91205 7.53947 10.147Z" fill="#858585"/>
<path d="M6.5997 11.2757C6.44346 11.2632 6.11347 11.762 5.81599 11.887C5.51851 12.012 5.06353 11.9345 4.98603 12.2157C4.90729 12.4982 4.7348 13.6881 4.98603 13.8294C5.23727 13.9706 5.92598 13.7356 6.08223 13.6419C6.23847 13.5481 6.22347 13.3281 6.53595 13.1244C6.84969 12.9207 6.89593 12.3882 6.91218 12.012C6.92843 11.6357 6.80219 11.2907 6.5997 11.2757Z" fill="#858585"/>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@@ -0,0 +1,21 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.2395 13.9305C13.2395 13.9305 13.2283 15.2642 13.2283 15.5292C13.2283 15.7941 13.3783 15.9316 13.5508 15.9204C13.7232 15.9091 14.552 15.9204 14.7357 15.9204C14.9194 15.9204 15.1269 15.7479 15.1269 15.4954C15.1269 15.2429 15.1619 12.988 15.1619 12.988L13.2395 13.9305Z" fill="#4E433D"/>
<path d="M4.42997 13.1492L2.60132 12.8967C2.60132 12.8967 2.59007 15.2316 2.57882 15.4503C2.56757 15.6691 2.69381 15.8278 2.87756 15.8416C3.20004 15.8641 3.80876 15.8641 4.02749 15.8528C4.24623 15.8416 4.39497 15.6803 4.40747 15.4728C4.41997 15.2654 4.40747 14.0242 4.40747 14.0242L4.42997 13.1492Z" fill="#4E433D"/>
<path d="M14.0606 8.08963L15.2093 8.03838C15.2093 8.03838 15.638 8.71959 15.5843 11.0245C15.533 13.2131 14.9681 14.0681 14.4681 14.1893C13.9681 14.3105 12.0807 14.4968 9.09088 14.5143C6.10103 14.5318 3.8799 14.3605 3.45242 14.2581C3.02495 14.1556 2.56247 13.5231 2.51247 13.0619C2.43623 12.3544 2.43123 11.4032 2.46497 10.0533C2.49872 8.70334 2.79246 8.02588 2.79246 8.02588L14.0606 8.08963Z" fill="#474C4F"/>
<path d="M4.35115 11.8981C3.70369 11.7906 2.76374 11.3831 2.69249 11.6831C2.62125 11.9831 2.90748 12.3406 3.5937 12.5693C4.27991 12.7981 5.85233 12.9693 9.15465 12.8981C12.457 12.8268 13.5719 12.7406 14.2294 12.5556C14.8868 12.3693 15.6443 11.8694 15.4018 11.5831C15.1593 11.2969 14.5156 11.7119 13.9294 11.8256C13.3432 11.9394 11.1408 12.0006 9.11215 12.0406C6.91102 12.0831 5.03737 12.0118 4.35115 11.8981Z" fill="#858585"/>
<path d="M5.57114 10.0206C5.3574 9.96435 2.70504 9.9031 2.58879 10.0281C2.53755 10.0831 2.55005 10.8793 2.56004 11.0243C2.57004 11.1693 2.68129 11.2193 2.87378 11.2293C3.06752 11.2393 5.54239 11.258 5.70613 11.2393C5.86987 11.2205 5.90862 11.0555 5.87987 10.9105C5.85112 10.7643 5.75488 10.0693 5.57114 10.0206Z" fill="#FEA826"/>
<path d="M15.3544 9.89699C15.2331 9.82574 12.542 9.93324 12.4558 10.0107C12.3683 10.0882 11.902 11.0507 12.137 11.1419C12.3595 11.2294 15.2681 11.1832 15.3644 11.1344C15.5143 11.0594 15.4694 9.96449 15.3544 9.89699Z" fill="#FEA826"/>
<path d="M6.95345 11.4419C6.69221 11.4544 6.32473 11.3156 6.16974 10.9107C6.01475 10.5044 5.996 10.2632 6.0435 10.0507C6.09224 9.83822 6.23724 9.76072 6.57472 9.73198C6.91345 9.70323 7.57092 9.65448 8.98209 9.64448C10.3945 9.63448 11.3707 9.66323 11.622 9.69323C11.8732 9.72198 12.0182 9.80947 11.9794 10.0707C11.9407 10.3319 11.8057 10.8544 11.6994 11.1057C11.622 11.2894 11.3807 11.4244 11.0607 11.4344C10.7432 11.4419 7.15719 11.4319 6.95345 11.4419Z" fill="#2F2F2F"/>
<path d="M14.668 7.41965L15.0442 7.42965C15.0442 7.42965 15.0529 6.97717 15.1042 6.83718C15.1554 6.69719 15.2592 6.62219 15.4942 6.62219C15.7292 6.62219 16.1904 6.63969 16.3454 6.64719C16.5004 6.65469 16.8016 6.91968 16.8166 7.31716C16.8316 7.71464 16.5591 7.96462 16.2866 7.99462C16.0141 8.02462 14.9917 7.99462 14.9917 7.99462L14.668 7.41965Z" fill="#474C4F"/>
<path d="M3.45106 7.35714L3.03358 7.38339C3.03358 7.38339 3.04608 6.97466 3.01733 6.82717C2.98858 6.67968 2.81109 6.59219 2.6786 6.58469C2.54611 6.57719 1.92739 6.57719 1.75115 6.59968C1.57491 6.62218 1.27242 6.79842 1.27992 7.24715C1.28742 7.69588 1.57491 7.94586 1.81739 7.93961C2.05988 7.93336 2.64735 7.95211 2.82609 7.94711C3.05483 7.93961 3.5548 7.96211 3.5548 7.96211L3.45106 7.35714Z" fill="#474C4F"/>
<path d="M7.95972 3.09839L7.97847 3.68711H10.3571L10.3758 3.11714L7.95972 3.09839Z" fill="#474C4F"/>
<path d="M11.6233 3.07858C11.6233 3.22357 11.6233 3.84229 11.6233 3.84229L12.407 3.86104L12.3583 3.04858L11.6233 3.07858Z" fill="#474C4F"/>
<path d="M5.73486 3.83223C5.75361 3.71598 5.76361 2.89478 5.76361 2.89478L6.60482 3.01102L6.57607 3.76473L5.73486 3.83223Z" fill="#474C4F"/>
<path d="M2.7888 8.02582C2.81005 7.98582 3.06754 7.89458 3.22253 7.62209C3.33128 7.43085 3.64751 6.40341 4.06374 5.5822C4.47996 4.76099 4.65371 4.40226 4.76995 4.21852C4.88619 4.03478 5.05993 3.88979 5.35992 3.80229C5.6599 3.7148 6.70485 3.47856 9.21846 3.52731C11.7321 3.57606 12.3695 3.65105 12.6145 3.7298C12.812 3.79229 13.182 3.98603 13.4232 4.43101C13.6645 4.87599 14.6882 7.15962 14.7857 7.36211C14.8819 7.5646 15.2106 8.03582 15.2106 8.03582C15.2106 8.03582 14.3419 8.93952 12.3121 9.17451C10.4871 9.38575 7.83729 9.57449 5.10118 9.10202C2.9538 8.73453 2.76756 8.06582 2.7888 8.02582Z" fill="#E3E4DE"/>
<path d="M5.01991 4.55725C4.79743 4.77974 4.2962 5.87968 4.04747 6.51215C3.79873 7.14461 3.65249 7.66458 3.99122 7.67583C4.32995 7.68708 7.55103 7.64209 8.97596 7.63084C10.4009 7.61959 13.802 7.69833 14.0507 7.69833C14.2994 7.69833 14.4457 7.48334 14.3332 7.21211C14.2207 6.94087 13.3832 5.08722 13.192 4.78224C12.9995 4.47725 12.7283 4.37476 12.2533 4.35226C11.7783 4.32976 10.0034 4.22852 8.78347 4.22852C7.56353 4.22852 5.22365 4.35476 5.01991 4.55725Z" fill="#2D2E35"/>
<path d="M4.26346 7.15069C4.31221 7.22444 7.3708 7.0907 9.2132 7.12444C11.0556 7.15819 13.5192 7.21444 13.6442 7.20319C13.768 7.19194 13.7805 7.0107 13.768 6.95445C13.7567 6.89821 13.5417 6.45698 13.5417 6.45698C13.5417 6.45698 12.423 5.1908 12.3318 5.1908C12.2418 5.1908 9.07696 5.39454 9.07696 5.39454L5.51715 5.29329L4.44345 6.61822C4.44345 6.61822 4.24096 7.11694 4.26346 7.15069Z" fill="#96C8ED"/>
<path d="M4.4436 6.61844C4.4436 6.61844 4.94233 5.42475 5.14357 5.12227C5.22231 5.00478 5.27981 4.89603 5.55105 4.86228C5.82228 4.82853 7.22346 4.6248 9.09961 4.65854C10.9758 4.69229 12.0607 4.76729 12.2869 4.78229C12.5544 4.79979 12.7394 4.89478 12.8407 5.06477C12.9419 5.23476 13.6544 6.69219 13.6544 6.69219L12.5044 6.58719C12.5044 6.58719 12.4969 6.05722 12.4794 5.87473C12.4644 5.71724 12.3907 5.58724 12.1494 5.5735C11.9345 5.56225 11.4595 5.551 11.302 5.56225C11.1432 5.5735 11.0358 5.70474 11.0045 5.92973C10.9733 6.15596 10.9408 6.55969 10.9408 6.55969L7.3797 6.58094C7.3797 6.58094 7.3797 6.02347 7.3472 5.82223C7.32345 5.67724 7.23221 5.58224 7.04597 5.58349C6.82973 5.58474 6.41975 5.56225 6.12601 5.59599C5.98977 5.61224 5.88853 5.77723 5.87728 6.01472C5.86853 6.18596 5.85478 6.56844 5.85478 6.56844L4.4436 6.61844Z" fill="#AFE3FB"/>
<path d="M8.05607 1.98621H10.3285L10.7434 2.73117L10.3085 3.27239H8.05607L7.62109 2.59492L8.05607 1.98621Z" fill="#CECECF"/>
<path d="M10.3283 1.98621C10.3283 1.98621 12.2807 2.01495 12.4169 2.03495C12.5532 2.05495 12.6882 2.21869 12.7069 2.41243C12.7257 2.60617 12.6882 3.0799 12.6882 3.16614C12.6882 3.25239 12.5769 3.30489 12.3944 3.30489C12.2107 3.30489 10.3108 3.27239 10.3108 3.27239L10.3283 1.98621Z" fill="#DD0C26"/>
<path d="M5.42603 3.02115C5.42603 3.1474 5.43603 3.24364 5.56102 3.25364C5.68726 3.26364 8.05589 3.27239 8.05589 3.27239V1.98621C8.05589 1.98621 5.89975 1.98621 5.74476 1.98621C5.58977 1.98621 5.42603 2.16995 5.42603 2.31494C5.42603 2.51743 5.42603 3.02115 5.42603 3.02115Z" fill="#0F64C7"/>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@@ -0,0 +1,15 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M15.7991 15.0355C15.7317 14.6755 15.4717 12.6843 15.4717 12.6843H2.52735C2.52735 12.6843 2.26736 14.6755 2.19987 15.0355C2.13237 15.3954 2.90358 16.4991 8.99951 16.4991C15.0954 16.4991 15.8666 15.3954 15.7991 15.0355Z" fill="#82AEC0"/>
<path d="M2.52368 12.7445C2.52368 12.0107 5.42353 11.4158 8.99959 11.4158C12.5757 11.4158 15.4755 12.0107 15.4755 12.7445C15.4755 13.4782 12.5757 14.1944 8.99959 14.1944C5.42353 14.1944 2.52368 13.4782 2.52368 12.7445Z" fill="#E0E0E0"/>
<path d="M13.2217 3.03992C12.8667 1.90873 11.6843 1.5 8.99816 1.5C6.31205 1.5 5.13087 1.90873 4.77589 3.03992C4.06092 5.31855 3.41846 12.5032 3.41846 12.5032C3.41846 12.5032 3.97718 13.4394 8.99941 13.4394C14.0216 13.4394 14.5816 12.5044 14.5816 12.5044C14.5816 12.5044 13.9367 5.31855 13.2217 3.03992Z" fill="#F44336"/>
<path opacity="0.9" d="M3.41724 12.517C3.41724 11.9421 5.91585 11.4758 8.99944 11.4758C12.083 11.4758 14.5816 11.9421 14.5816 12.517C14.5816 13.092 12.083 13.6532 8.99944 13.6532C5.91585 13.6532 3.41724 13.092 3.41724 12.517Z" fill="#C62828"/>
<path d="M10.0731 5.46596L12.8017 2.64986C12.8542 2.60361 12.9317 2.66236 12.9017 2.72486L10.8718 5.98218C10.7206 6.29717 10.8543 6.50091 11.2043 6.49216L13.7854 6.46591C13.8554 6.46466 13.8767 6.55965 13.8129 6.5884L11.4581 7.49585C11.1393 7.6396 11.0718 7.98083 11.3443 8.19832L14.2804 10.3157C14.3354 10.3595 14.2904 10.4457 14.2229 10.427L10.8818 9.04327C10.5456 8.94828 10.3919 9.13452 10.4606 9.477L11.5381 13.0881C11.5518 13.1568 11.4618 13.1943 11.4231 13.1368L9.58441 9.88073C9.38817 9.59199 9.15068 9.537 8.98444 9.84323L7.22079 13.1781C7.18704 13.2393 7.09454 13.2106 7.10079 13.1406L8.01199 9.542C8.04699 9.19452 7.887 9.12202 7.56202 9.24951L3.78097 11.1882C3.71597 11.2132 3.66347 11.1319 3.71347 11.0832L6.8858 8.44955C7.13579 8.20582 7.12454 7.93958 6.79331 7.82834L4.14595 6.92588C4.0797 6.90339 4.0922 6.80714 4.16095 6.80214L6.76581 6.85214C7.11454 6.82589 7.29078 6.47841 7.10829 6.18092L5.01465 3.16233C4.9784 3.10234 5.04965 3.03609 5.1059 3.07734L7.89825 5.50221C8.18199 5.70595 8.43072 5.6872 8.48197 5.34222L8.9357 2.09239C8.9457 2.02364 9.04319 2.01864 9.06069 2.08614L9.45567 5.25847C9.54316 5.5972 9.81065 5.6972 10.0731 5.46596Z" fill="url(#paint0_radial_716_535)"/>
<path d="M9.23063 5.25724L10.4006 3.1461C10.4356 3.08611 10.5281 3.11735 10.5193 3.1861L10.1543 5.49223C10.1118 5.83846 10.3043 5.9897 10.6318 5.86971L12.9229 5.09975C12.9879 5.076 13.0392 5.1585 12.9879 5.20599L11.193 6.73841C10.9368 6.9759 10.9818 7.32088 11.3105 7.43963L13.6004 8.21584C13.6654 8.23958 13.6516 8.33583 13.5816 8.33958L11.1418 8.38833C10.793 8.40583 10.7068 8.63206 10.8818 8.93455L12.1142 11.0394C12.1492 11.0994 12.0755 11.1644 12.0205 11.1219L10.1793 9.59701C9.90059 9.38702 9.65936 9.40952 9.59936 9.75325L9.16188 12.2456C9.14938 12.3144 9.05189 12.3169 9.03689 12.2494L8.58191 9.77825C8.50442 9.43827 8.32943 9.42077 8.06194 9.64451L6.12454 11.2732C6.0708 11.3182 5.99455 11.2569 6.02705 11.1957L7.16574 9.10329C7.32448 8.79206 7.22948 8.54457 6.8795 8.54457L4.42213 8.57582C4.35214 8.57582 4.33339 8.48082 4.39713 8.45332L6.54202 7.62712C6.86325 7.49087 6.9195 7.10589 6.65201 6.88091L4.85211 5.41473C4.79836 5.36974 4.84586 5.28474 4.91211 5.30474L7.18324 5.9847C7.51697 6.0872 7.74696 5.9897 7.68571 5.64597L7.21448 3.26985C7.20199 3.2011 7.29323 3.1661 7.33073 3.2236L8.58191 5.25599C8.77065 5.54973 9.05689 5.55973 9.23063 5.25724Z" fill="#FFD5CA"/>
<defs>
<radialGradient id="paint0_radial_716_535" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.99962 7.62467) scale(5.44854)">
<stop offset="0.5718" stop-color="#FF6E40"/>
<stop offset="0.7682" stop-color="#FF7046" stop-opacity="0.5414"/>
<stop offset="1" stop-color="#FF7555" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -713,6 +713,19 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
}
};
// Row buttons select without letting the wrapping checkbox also toggle:
// stop propagation, run the selection, then drop the active/chip focus.
const selectFromButton = (
e: React.MouseEvent,
source: 'option' | 'checkbox',
): void => {
e.stopPropagation();
e.preventDefault();
handleItemSelection(source);
setActiveChipIndex(-1);
setActiveIndex(-1);
};
return (
<div
key={option.value || `option-${index}`}
@@ -726,13 +739,6 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
selected: isSelected,
active: isActive,
})}
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
handleItemSelection('option');
setActiveChipIndex(-1);
setActiveIndex(-1);
}}
onKeyDown={(e): void => {
if ((e.key === 'Enter' || e.key === SPACEKEY) && isActive) {
e.stopPropagation();
@@ -752,13 +758,7 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
<Checkbox
value={isSelected}
className="option-checkbox"
onClick={(e): void => {
e.stopPropagation();
e.preventDefault();
handleItemSelection('checkbox');
setActiveChipIndex(-1);
setActiveIndex(-1);
}}
onClick={(e): void => selectFromButton(e, 'checkbox')}
>
<div className="option-content">
<Typography.Text truncate={1} className="option-label-text">
@@ -768,11 +768,19 @@ const CustomMultiSelect: React.FC<CustomMultiSelectProps> = ({
<div className="option-badge">{capitalize(option.type)}</div>
)}
{option.value && ensureValidOption(option.value) && (
<Button type="text" className="only-btn">
<Button
type="text"
className="only-btn"
onClick={(e): void => selectFromButton(e, 'option')}
>
{currentToggleTagValue({ option: option.value })}
</Button>
)}
<Button type="text" className="toggle-btn">
<Button
type="text"
className="toggle-btn"
onClick={(e): void => selectFromButton(e, 'checkbox')}
>
Toggle
</Button>
</div>

View File

@@ -656,6 +656,28 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
});
});
it('UI-03b: clicking "Only" selects just that value', async () => {
renderWithVirtuoso(
<CustomMultiSelect
options={mockOptions}
onChange={mockOnChange}
value={['frontend']}
/>,
);
const combobox = screen.getByRole('combobox');
await user.click(combobox);
// "Only" renders for each non-selected row (hidden until hover via CSS,
// which jsdom doesn't apply, so it's clickable here).
const onlyButtons = await screen.findAllByText('Only');
mockOnChange.mockClear();
await user.click(onlyButtons[0]);
expect(mockOnChange).toHaveBeenCalledTimes(1);
expect(mockOnChange.mock.calls[0][0]).toStrictEqual(['backend']);
});
it('UI-04: Should display values with loading info at bottom', async () => {
renderWithVirtuoso(
<CustomMultiSelect options={mockOptions} onChange={mockOnChange} loading />,
@@ -1449,7 +1471,7 @@ describe('CustomMultiSelect - Comprehensive Tests', () => {
expect(mockOnChange).toHaveBeenCalledWith(
['custom-value'],
[{ label: 'custom-value', value: 'custom-value' }],
[{ label: 'custom-value', value: 'custom-value', type: 'custom' }],
);
});
});

View File

@@ -0,0 +1,21 @@
import { prioritizeOrAddOptionForMultiSelect } from '../utils';
describe('prioritizeOrAddOptionForMultiSelect ordering', () => {
it('hoists selected then preserves the given (sorted) order in each group', () => {
const sorted = ['apple', 'banana', 'cherry', 'date', 'elderberry'].map(
(v) => ({ label: v, value: v }),
);
// selection given in a non-sorted order on purpose
const result = prioritizeOrAddOptionForMultiSelect(sorted, [
'date',
'banana',
]);
expect(result.map((o) => o.value)).toStrictEqual([
'banana',
'date',
'apple',
'cherry',
'elderberry',
]);
});
});

View File

@@ -458,7 +458,9 @@ $custom-border-color: #2c3044;
.option-item {
padding: 8px 12px;
cursor: pointer;
// Not the whole row — only the checkbox and the action buttons get the
// pointer (set below), so inert areas don't look clickable.
cursor: default;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -494,6 +496,13 @@ $custom-border-color: #2c3044;
.option-checkbox {
width: 100%;
cursor: default;
// The checkbox button is the only pointer target on the left; the label
// still toggles on click but keeps a default cursor.
> button {
cursor: pointer;
}
// @signozhq/ui Checkbox renders children inside a <label> that is
// content-sized by default. Make it fill the row (min-width: 0 lets it
@@ -535,9 +544,9 @@ $custom-border-color: #2c3044;
}
}
// Size the buttons to the row's resting content height (20px) and fully
// override antd's default 32px Button box, so revealing them on hover
// never changes the row height.
// "Only"/"All" is the primary action — a filled pill that reads as a
// button; "Toggle" is a secondary hint in plain text. Sized to the row's
// resting height so revealing them on hover never shifts it.
.only-btn,
.toggle-btn {
display: none;
@@ -545,14 +554,39 @@ $custom-border-color: #2c3044;
justify-content: center;
height: 18px;
min-height: 0;
padding: 0 6px;
font-size: 12px;
line-height: 1;
border: none;
box-shadow: none;
}
&:hover {
background-color: unset;
.only-btn {
padding: 4px 8px;
// Black interior + a visible border so the pill stands out clearly
// against the near-black row when revealed on hover.
border: 1px solid var(--l3-border);
border-radius: 3px;
background-color: var(--bg-ink-500, #0b0c0e);
color: var(--l1-foreground);
cursor: pointer;
}
.toggle-btn {
padding: 0 6px;
border: none;
background-color: transparent;
color: var(--l2-foreground);
cursor: pointer;
}
// Toggle appears over the checkbox area; "Only/All" takes over the row
// content and hides Toggle there (higher specificity wins).
&:hover {
.toggle-btn {
display: flex;
}
.option-badge {
display: none;
}
}
@@ -560,6 +594,7 @@ $custom-border-color: #2c3044;
.only-btn {
display: flex;
}
.toggle-btn {
display: none;
}
@@ -569,15 +604,6 @@ $custom-border-color: #2c3044;
}
}
}
.option-checkbox:hover {
.toggle-btn {
display: flex;
}
.option-badge {
display: none;
}
}
}
.loading-container {

View File

@@ -52,6 +52,8 @@ import type { SignalType } from 'types/api/v5/queryRange';
import { queryExamples, SUGGESTIONS_SECTION } from './constants';
import {
combineInitialAndUserExpression,
dedupeOptionsByLabel,
getFieldContextPrefix,
getRecentOptions,
renderRecentDeleteButton,
} from './utils';
@@ -222,6 +224,12 @@ function QuerySearch({
QueryKeyDataSuggestionsProps[] | null
>(null);
// dedupe keySuggestions by label/name
const dedupedKeySuggestions = useMemo(
() => dedupeOptionsByLabel(keySuggestions || []),
[keySuggestions],
);
const [showExamples] = useState(false);
const [cursorPos, setCursorPos] = useState({ line: 0, ch: 0 });
@@ -246,9 +254,11 @@ function QuerySearch({
[key: string]: QueryKeyDataSuggestionsProps[];
}): any[] =>
Object.values(keys).flatMap((items: QueryKeyDataSuggestionsProps[]) =>
items.map(({ name, fieldDataType }) => ({
items.map(({ name, fieldDataType, fieldContext }) => ({
label: name,
type: fieldDataType === 'string' ? 'keyword' : fieldDataType,
fieldContext,
fieldDataType,
info: '',
details: '',
})),
@@ -307,13 +317,17 @@ function QuerySearch({
if (response.data.data) {
const { keys } = response.data.data;
const options = generateOptions(keys);
// Use a Map to deduplicate by label and preserve order: new options take precedence
// Deduplicate by full variant identity (name + context + data type), NOT by
// label. deduping by label removes varient which is not expected. If we need
// to dedupe by label use dedupedKeySuggestions not dedupe the source itself
const variantId = (opt: QueryKeyDataSuggestionsProps): string =>
`${opt.label}|${opt.fieldContext ?? ''}|${opt.fieldDataType ?? ''}`;
const merged = new Map<string, QueryKeyDataSuggestionsProps>();
options.forEach((opt) => merged.set(opt.label, opt));
options.forEach((opt) => merged.set(variantId(opt), opt));
if (searchText && lastKeyRef.current !== searchText) {
(keySuggestions || []).forEach((opt) => {
if (!merged.has(opt.label)) {
merged.set(opt.label, opt);
if (!merged.has(variantId(opt))) {
merged.set(variantId(opt), opt);
}
});
}
@@ -919,8 +933,55 @@ function QuerySearch({
if (queryContext.isInKey) {
const searchText = word?.text.toLowerCase().trim() ?? '';
const fieldContextMatch = getFieldContextPrefix(searchText);
options = (keySuggestions || []).filter((option) =>
if (fieldContextMatch) {
const { context: fieldContext, remainder } = fieldContextMatch;
// Fetch the context's page when the prefix is typed exactly eg.("attribute.")
if (remainder === '' && lastFetchedKeyRef.current !== searchText) {
debouncedFetchKeySuggestions(searchText);
}
//suggestions that actually do start with <fieldContext>.
const nameMatches = (keySuggestions || [])
.filter((option) => option.label.toLowerCase().includes(searchText))
.map((option) => ({ ...option, boost: 100 }));
//suggestions which do not start with the prefix but qualifies for suggestion
const contextQualified = (keySuggestions || [])
.filter(
(option) =>
option.fieldContext === fieldContext &&
option.label.toLowerCase().includes(remainder),
)
.map((option) => ({
...option,
label: `${fieldContext}.${option.label}`,
boost: 0,
}));
const contextOptions = dedupeOptionsByLabel([
...nameMatches,
...contextQualified,
]);
// If contextOptions is empty fetch again.
if (
contextOptions.length === 0 &&
lastFetchedKeyRef.current !== searchText
) {
debouncedFetchKeySuggestions(searchText);
}
return {
from: word?.from ?? 0,
to: word?.to ?? cursorPos.ch,
options: addSpaceToOptions(contextOptions),
};
}
options = dedupedKeySuggestions.filter((option) =>
option.label.toLowerCase().includes(searchText),
);
@@ -969,9 +1030,26 @@ function QuerySearch({
// If we have a key context, add that info to the operator suggestions
if (keyName) {
// Find the key details from suggestions
const keyDetails = (keySuggestions || []).find((k) => k.label === keyName);
const keyType = keyDetails?.type || '';
const keyContextMatch = getFieldContextPrefix(keyName);
// key-suggestion can contain multiple variants of a single key
// In variants we capture ones that match the label to typed keyName exactly or,
// if it has a prefix fieldContext remove it and then match.
const variants = (keySuggestions || []).filter(
(k) =>
k.label === keyName ||
(keyContextMatch !== null &&
k.fieldContext === keyContextMatch.context &&
k.label === keyContextMatch.remainder),
);
const variantTypes = new Set(
variants
.map((k) =>
k.type === 'keyword' ? QUERY_BUILDER_KEY_TYPES.STRING : k.type,
)
.filter(Boolean),
);
//if there are multi-variant, show all suggestions else just the one
const keyType = variantTypes.size === 1 ? [...variantTypes][0] : '';
// Filter operators based on key type
if (keyType) {
@@ -1214,7 +1292,7 @@ function QuerySearch({
if (curChar === '(') {
// In expression context, suggest keys, functions, or nested parentheses
options = [
...(keySuggestions || []),
...dedupedKeySuggestions,
{ label: '(', type: 'parenthesis', info: 'Open nested group' },
{ label: 'NOT', type: 'operator', info: 'Negate expression' },
...options.filter((opt) => opt.type === 'function'),

View File

@@ -0,0 +1,120 @@
import {
combineInitialAndUserExpression,
dedupeOptionsByLabel,
getFieldContextPrefix,
getUserExpressionFromCombined,
} from '../utils';
describe('entityLogsExpression', () => {
describe('combineInitialAndUserExpression', () => {
it('returns user when initial is empty', () => {
expect(combineInitialAndUserExpression('', 'body contains error')).toBe(
'body contains error',
);
});
it('returns initial when user is empty', () => {
expect(combineInitialAndUserExpression('k8s.pod.name = "x"', '')).toBe(
'k8s.pod.name = "x"',
);
});
it('wraps user in parentheses with AND', () => {
expect(
combineInitialAndUserExpression('k8s.pod.name = "x"', 'body = "a"'),
).toBe('k8s.pod.name = "x" AND (body = "a")');
});
});
describe('getUserExpressionFromCombined', () => {
it('returns empty when combined equals initial', () => {
expect(
getUserExpressionFromCombined('k8s.pod.name = "x"', 'k8s.pod.name = "x"'),
).toBe('');
});
it('extracts user from wrapped form', () => {
expect(
getUserExpressionFromCombined(
'k8s.pod.name = "x"',
'k8s.pod.name = "x" AND (body = "a")',
),
).toBe('body = "a"');
});
it('extracts user from legacy AND without parens', () => {
expect(
getUserExpressionFromCombined(
'k8s.pod.name = "x"',
'k8s.pod.name = "x" AND body = "a"',
),
).toBe('body = "a"');
});
it('returns full combined when initial is empty', () => {
expect(getUserExpressionFromCombined('', 'service.name = "a"')).toBe(
'service.name = "a"',
);
});
});
});
describe('getFieldContextPrefix', () => {
it('matches a complete context prefix with a remainder', () => {
expect(getFieldContextPrefix('attribute.status')).toStrictEqual({
context: 'attribute',
remainder: 'status',
});
});
it('matches a bare context prefix with empty remainder', () => {
expect(getFieldContextPrefix('resource.')).toStrictEqual({
context: 'resource',
remainder: '',
});
});
it('matches every backend field context', () => {
['attribute', 'resource', 'span', 'body', 'log', 'metric'].forEach((ctx) => {
expect(getFieldContextPrefix(`${ctx}.x`)).toStrictEqual({
context: ctx,
remainder: 'x',
});
});
});
it('does not match a partial context name', () => {
expect(getFieldContextPrefix('attr')).toBeNull();
expect(getFieldContextPrefix('attribute')).toBeNull();
});
it('does not match a non-context key with dots', () => {
expect(getFieldContextPrefix('status.code')).toBeNull();
});
it('matches context case-insensitively but keeps remainder casing', () => {
expect(getFieldContextPrefix('Attribute.Status')).toStrictEqual({
context: 'attribute',
remainder: 'Status',
});
});
});
describe('dedupeOptionsByLabel', () => {
it('keeps the first occurrence per label, preserving order', () => {
expect(
dedupeOptionsByLabel([
{ label: 'status.code', type: 'keyword' },
{ label: 'status.code', type: 'number' },
{ label: 'duration', type: 'number' },
]),
).toStrictEqual([
{ label: 'status.code', type: 'keyword' },
{ label: 'duration', type: 'number' },
]);
});
it('returns an empty array for empty input', () => {
expect(dedupeOptionsByLabel([])).toStrictEqual([]);
});
});

View File

@@ -1,58 +0,0 @@
import {
combineInitialAndUserExpression,
getUserExpressionFromCombined,
} from '../utils';
describe('entityLogsExpression', () => {
describe('combineInitialAndUserExpression', () => {
it('returns user when initial is empty', () => {
expect(combineInitialAndUserExpression('', 'body contains error')).toBe(
'body contains error',
);
});
it('returns initial when user is empty', () => {
expect(combineInitialAndUserExpression('k8s.pod.name = "x"', '')).toBe(
'k8s.pod.name = "x"',
);
});
it('wraps user in parentheses with AND', () => {
expect(
combineInitialAndUserExpression('k8s.pod.name = "x"', 'body = "a"'),
).toBe('k8s.pod.name = "x" AND (body = "a")');
});
});
describe('getUserExpressionFromCombined', () => {
it('returns empty when combined equals initial', () => {
expect(
getUserExpressionFromCombined('k8s.pod.name = "x"', 'k8s.pod.name = "x"'),
).toBe('');
});
it('extracts user from wrapped form', () => {
expect(
getUserExpressionFromCombined(
'k8s.pod.name = "x"',
'k8s.pod.name = "x" AND (body = "a")',
),
).toBe('body = "a"');
});
it('extracts user from legacy AND without parens', () => {
expect(
getUserExpressionFromCombined(
'k8s.pod.name = "x"',
'k8s.pod.name = "x" AND body = "a"',
),
).toBe('body = "a"');
});
it('returns full combined when initial is empty', () => {
expect(getUserExpressionFromCombined('', 'service.name = "a"')).toBe(
'service.name = "a"',
);
});
});
});

View File

@@ -1,6 +1,16 @@
export const RECENTS_SECTION = { name: 'Recent searches', rank: 1 } as const;
export const SUGGESTIONS_SECTION = { name: 'Suggestions', rank: 2 } as const;
// TODO: move to using TelemetrytypesFieldContextDTO when we migrate getKeySuggestions API (Part of https://github.com/SigNoz/engineering-pod/issues/5289)
export const FIELD_CONTEXTS = [
'attribute',
'resource',
'span',
'body',
'log',
'metric',
] as const;
// Custom CodeMirror Completion.type for recent-query entries. Used to discriminate
// recents from regular autocomplete completions in renderers and event handlers.
export const RECENT_COMPLETION_TYPE = 'recent';

View File

@@ -9,11 +9,45 @@ import type { SignalType } from 'types/api/v5/queryRange';
import 'utils/timeUtils';
import {
FIELD_CONTEXTS,
RECENT_COMPLETION_TYPE,
RECENTS_DISPLAY_CAP,
RECENTS_SECTION,
} from './constants';
export interface FieldContextPrefixMatch {
context: string;
remainder: string;
}
// This function checks if the text(query key) starts with a fieldContext
// This util strictly checks that and returns context and remainder back.
// This helps differentiate if the typed key was prefixed with a context or
// was it an actual queryKey like (attribute.abc)
export function getFieldContextPrefix(
text: string,
): FieldContextPrefixMatch | null {
const lower = text.toLowerCase();
const context = FIELD_CONTEXTS.find((ctx) => lower.startsWith(`${ctx}.`));
return context ? { context, remainder: text.slice(context.length + 1) } : null;
}
// Keeps the first occurrence per label, preserving order. Key suggestions hold
// one entry per (name, fieldContext, fieldDataType) variant; This means query builder
// could show multiple labels and this avoids that.
export function dedupeOptionsByLabel<T extends { label: string }>(
options: T[],
): T[] {
const seen = new Set<string>();
return options.filter((option) => {
if (seen.has(option.label)) {
return false;
}
seen.add(option.label);
return true;
});
}
export function combineInitialAndUserExpression(
initial: string,
user: string,

View File

@@ -0,0 +1,217 @@
import { initialQueriesMap } from 'constants/queryBuilder';
import { rest, server } from 'mocks-server/server';
import { render, userEvent, waitFor } from 'tests/test-utils';
import { DataSource } from 'types/common/queryBuilder';
import QuerySearch from '../QuerySearch/QuerySearch';
import { mockCodeMirrorDomApis } from './codemirrorDomMocks';
const CM_EDITOR_SELECTOR = '.cm-editor .cm-content';
const CM_OPTION_SELECTOR = '.cm-tooltip-autocomplete .cm-completionLabel';
beforeAll(() => {
mockCodeMirrorDomApis();
});
jest.mock('hooks/useDarkMode', () => ({
useIsDarkMode: (): boolean => false,
}));
jest.mock('providers/Dashboard/store/useDashboardStore', () => ({
useDashboardStore: (): { dashboardData: undefined } => ({
dashboardData: undefined,
}),
}));
jest.mock('hooks/queryBuilder/useQueryBuilder', () => {
const handleRunQuery = jest.fn();
return {
__esModule: true,
useQueryBuilder: (): { handleRunQuery: () => void } => ({ handleRunQuery }),
handleRunQuery,
};
});
// Keys fixture: status.code exists as 3 variants (attribute string/number +
// resource string); attribute.a.b.c is a key literally named with the prefix.
const KEYS_FIXTURE = {
'status.code': [
{
name: 'status.code',
fieldContext: 'attribute',
fieldDataType: 'string',
signal: 'logs',
},
{
name: 'status.code',
fieldContext: 'attribute',
fieldDataType: 'number',
signal: 'logs',
},
{
name: 'status.code',
fieldContext: 'resource',
fieldDataType: 'string',
signal: 'logs',
},
],
'attribute.a.b.c': [
{
name: 'attribute.a.b.c',
fieldContext: 'attribute',
fieldDataType: 'string',
signal: 'logs',
},
],
'duration.nano': [
{
name: 'duration.nano',
fieldContext: 'span',
fieldDataType: 'number',
signal: 'logs',
},
],
};
const fetchedSearchTexts: string[] = [];
beforeEach(() => {
fetchedSearchTexts.length = 0;
server.use(
rest.get('http://localhost/api/v1/fields/keys', (req, res, ctx) => {
fetchedSearchTexts.push(req.url.searchParams.get('searchText') ?? '');
return res(
ctx.status(200),
ctx.json({
status: 'success',
data: { complete: true, keys: KEYS_FIXTURE },
}),
);
}),
rest.get('http://localhost/api/v1/fields/values', (_req, res, ctx) =>
res(
ctx.status(200),
ctx.json({
status: 'success',
data: { values: { stringValues: [], numberValues: [] } },
}),
),
),
);
});
async function renderAndType(text: string): Promise<HTMLElement> {
render(
<QuerySearch
onChange={jest.fn() as jest.MockedFunction<(v: string) => void>}
queryData={initialQueriesMap.logs.builder.queryData[0]}
dataSource={DataSource.LOGS}
/>,
);
await waitFor(() => {
expect(document.querySelector(CM_EDITOR_SELECTOR)).toBeInTheDocument();
});
const editor = document.querySelector(CM_EDITOR_SELECTOR) as HTMLElement;
await userEvent.click(editor);
await userEvent.type(editor, text);
return editor;
}
// Types a key, waits for its suggestion to render (proving the debounced key
// fetch resolved into state), then types a space to enter operator context.
async function typeKeyThenEnterOperatorContext(
key: string,
expectedKeyLabel: string,
): Promise<void> {
const editor = await renderAndType(key);
await waitFor(() => {
expect(visibleOptionLabels()).toContain(expectedKeyLabel);
});
// skipClick: a click would reset the caret to position 0 under the mocked
// DOM rects; we need the space appended at the end of the typed key.
await userEvent.type(editor, ' ', { skipClick: true });
await waitFor(() => {
expect(visibleOptionLabels()).toContain('=');
});
}
function visibleOptionLabels(): string[] {
return Array.from(document.querySelectorAll(CM_OPTION_SELECTOR)).map(
(el) => el.textContent ?? '',
);
}
describe('QuerySearch context-prefixed key suggestions', () => {
it('shows one deduped suggestion per key name in normal mode', async () => {
await renderAndType('statu');
await waitFor(() => {
expect(visibleOptionLabels()).toContain('status.code');
});
const statusOptions = visibleOptionLabels().filter(
(label) => label === 'status.code',
);
expect(statusOptions).toHaveLength(1);
});
it('shows context-scoped suggestions for a complete context prefix', async () => {
await renderAndType('attribute.');
await waitFor(() => {
expect(visibleOptionLabels()).toContain('attribute.status.code');
});
const labels = visibleOptionLabels();
// Literal key name match ranks first
expect(labels[0]).toBe('attribute.a.b.c');
// Context-qualified form of the literal key is also present
expect(labels).toContain('attribute.attribute.a.b.c');
// span-only key is not suggested under attribute.
expect(labels).not.toContain('attribute.duration.nano');
});
it('fetches the context page when the prefix is typed exactly', async () => {
await renderAndType('attribute.');
await waitFor(() => {
expect(fetchedSearchTexts).toContain('attribute.');
});
});
});
describe('QuerySearch operator suggestions by key type', () => {
it('shows all operators for a key with ambiguous types', async () => {
// status.code is string + number across variants → no type filtering
await typeKeyThenEnterOperatorContext('status.code', 'status.code');
const labels = visibleOptionLabels();
expect(labels).toContain('>');
expect(labels).toContain('LIKE');
});
it('shows numeric operators for a single-type number key', async () => {
await typeKeyThenEnterOperatorContext('duration.nano', 'duration.nano');
const labels = visibleOptionLabels();
expect(labels).toContain('>');
expect(labels).not.toContain('LIKE');
});
it('narrows operators when a context prefix disambiguates the type', async () => {
// status.code is ambiguous globally, but resource.status.code is string-only
await typeKeyThenEnterOperatorContext(
'resource.status.code',
'resource.status.code',
);
const labels = visibleOptionLabels();
expect(labels).toContain('LIKE');
expect(labels).not.toContain('>');
});
});

View File

@@ -8,78 +8,13 @@ import type { QueryKeyDataSuggestionsProps } from 'types/api/querySuggestions/ty
import { DataSource } from 'types/common/queryBuilder';
import QuerySearch from '../QuerySearch/QuerySearch';
import { mockCodeMirrorDomApis } from './codemirrorDomMocks';
const CM_EDITOR_SELECTOR = '.cm-editor .cm-content';
// Mock DOM APIs that CodeMirror needs
beforeAll(() => {
// Mock getClientRects and getBoundingClientRect for Range objects
const mockRect: DOMRect = {
width: 100,
height: 20,
top: 0,
left: 0,
right: 100,
bottom: 20,
x: 0,
y: 0,
toJSON: (): DOMRect => mockRect,
} as DOMRect;
// Create a minimal Range mock with only what CodeMirror actually uses
const createMockRange = (): Range => {
let startContainer: Node = document.createTextNode('');
let endContainer: Node = document.createTextNode('');
let startOffset = 0;
let endOffset = 0;
const mockRange = {
// CodeMirror uses these for text measurement
getClientRects: (): DOMRectList =>
({
length: 1,
item: (index: number): DOMRect | null => (index === 0 ? mockRect : null),
0: mockRect,
*[Symbol.iterator](): Generator<DOMRect> {
yield mockRect;
},
}) as unknown as DOMRectList,
getBoundingClientRect: (): DOMRect => mockRect,
// CodeMirror calls these to set up text ranges
setStart: (node: Node, offset: number): void => {
startContainer = node;
startOffset = offset;
},
setEnd: (node: Node, offset: number): void => {
endContainer = node;
endOffset = offset;
},
// Minimal Range properties (TypeScript requires these)
get startContainer(): Node {
return startContainer;
},
get endContainer(): Node {
return endContainer;
},
get startOffset(): number {
return startOffset;
},
get endOffset(): number {
return endOffset;
},
get collapsed(): boolean {
return startContainer === endContainer && startOffset === endOffset;
},
commonAncestorContainer: document.body,
};
return mockRange as unknown as Range;
};
// Mock document.createRange to return a new Range instance each time
document.createRange = (): Range => createMockRange();
// Mock getBoundingClientRect for elements
Element.prototype.getBoundingClientRect = (): DOMRect => mockRect;
mockCodeMirrorDomApis();
});
jest.mock('hooks/useDarkMode', () => ({

View File

@@ -0,0 +1,71 @@
// Mocks the DOM measurement APIs CodeMirror needs to render in jsdom
// (Range client rects + element bounding rects). Call from a beforeAll in
// specs that render the real CodeMirror editor.
export function mockCodeMirrorDomApis(): void {
const mockRect: DOMRect = {
width: 100,
height: 20,
top: 0,
left: 0,
right: 100,
bottom: 20,
x: 0,
y: 0,
toJSON: (): DOMRect => mockRect,
} as DOMRect;
// Create a minimal Range mock with only what CodeMirror actually uses
const createMockRange = (): Range => {
let startContainer: Node = document.createTextNode('');
let endContainer: Node = document.createTextNode('');
let startOffset = 0;
let endOffset = 0;
const mockRange = {
// CodeMirror uses these for text measurement
getClientRects: (): DOMRectList =>
({
length: 1,
item: (index: number): DOMRect | null => (index === 0 ? mockRect : null),
0: mockRect,
*[Symbol.iterator](): Generator<DOMRect> {
yield mockRect;
},
}) as unknown as DOMRectList,
getBoundingClientRect: (): DOMRect => mockRect,
// CodeMirror calls these to set up text ranges
setStart: (node: Node, offset: number): void => {
startContainer = node;
startOffset = offset;
},
setEnd: (node: Node, offset: number): void => {
endContainer = node;
endOffset = offset;
},
// Minimal Range properties (TypeScript requires these)
get startContainer(): Node {
return startContainer;
},
get endContainer(): Node {
return endContainer;
},
get startOffset(): number {
return startOffset;
},
get endOffset(): number {
return endOffset;
},
get collapsed(): boolean {
return startContainer === endContainer && startOffset === endOffset;
},
commonAncestorContainer: document.body,
};
return mockRange as unknown as Range;
};
// Mock document.createRange to return a new Range instance each time
document.createRange = (): Range => createMockRange();
// Mock getBoundingClientRect for elements
Element.prototype.getBoundingClientRect = (): DOMRect => mockRect;
}

View File

@@ -9,6 +9,7 @@ import { extractQueryPairs } from 'utils/queryContextUtils';
import {
convertAggregationToExpression,
convertExpressionToFilters,
convertFiltersToExpression,
convertFiltersToExpressionWithExistingQuery,
formatValueForExpression,
@@ -1598,4 +1599,36 @@ describe('formatValueForExpression', () => {
expect(formatValueForExpression([123] as any)).toBe('[123]');
});
});
// Regression: an escaped single quote must not gain a backslash on each
// expression <-> filters round-trip (broke Create Alert from a panel).
describe('escaped quote round-trip', () => {
const EXPR = "name = 'it\\'s a ribbon'"; // name = 'it\'s a ribbon' (single backslash)
it('stores the unescaped value in the filter item', () => {
const items = convertExpressionToFilters(EXPR);
expect(items).toHaveLength(1);
expect(items[0].value).toBe("it's a ribbon");
});
it('is lossless: expression -> filters -> expression', () => {
const items = convertExpressionToFilters(EXPR);
expect(convertFiltersToExpression({ items, op: 'AND' }).expression).toBe(
EXPR,
);
});
it('is idempotent across repeated existing-query conversions', () => {
const pass1 = convertFiltersToExpressionWithExistingQuery(
{ items: [], op: 'AND' },
EXPR,
);
expect(pass1.filter.expression).toBe(EXPR);
const pass2 = convertFiltersToExpressionWithExistingQuery(
pass1.filters,
pass1.filter.expression,
);
expect(pass2.filter.expression).toBe(EXPR);
});
});
});

View File

@@ -176,7 +176,9 @@ function formatSingleValueForFilter(
}
if (isQuoted(value)) {
return unquote(value);
// Unescape `\'` → `'` (inverse of formatSingleValue) so the round-trip doesn't
// double the backslash each pass.
return unquote(value).replace(/\\'/g, "'");
}
}
@@ -768,6 +770,34 @@ export const removeVariableFromExpression = (
return removeKeysFromExpression(expression, keysToRemove, `$${variableName}`);
};
// Appends `clause` as a top-level AND term, parenthesising the base only when it
// has a top-level OR (AND binds tighter, so `a OR b AND c` would misbind).
export const appendAndClause = (
expression: string | undefined,
clause: string,
): string => {
const base = expression?.trim();
if (!base) {
return clause;
}
const chars = CharStreams.fromString(base);
const lexer = new FilterQueryLexer(chars);
lexer.removeErrorListeners();
const tokenStream = new CommonTokenStream(lexer);
const parser = new FilterQueryParser(tokenStream);
parser.removeErrorListeners();
const tree = parser.query();
if (parser.syntaxErrorsCount > 0) {
return `(${base}) AND ${clause}`;
}
const hasTopLevelOr =
tree.expression().orExpression().andExpression_list().length > 1;
return hasTopLevelOr ? `(${base}) AND ${clause}` : `${base} AND ${clause}`;
};
/**
* Convert old having format to new having format
* @param having - Array of old having objects with columnName, op, and value

View File

@@ -1,8 +1,6 @@
.quick-filters-container {
display: flex;
flex-direction: row;
height: 100%;
min-height: 0;
position: relative;
.quick-filters-settings-container {

View File

@@ -0,0 +1,11 @@
.static {
// Tag chips are not interactive, but the @signozhq Badge darkens outline
// variants on hover — which reads as clickable. Pin the hover background to the
// resting background so hovering a plain tag produces no change.
--badge-outline-hover-background-color: var(
--badge-outline-background-color,
color-mix(in oklab, var(--badge-background) 10%, transparent)
);
cursor: default;
}

View File

@@ -1,5 +1,8 @@
import { type MouseEvent, type ReactNode } from 'react';
import { Badge } from '@signozhq/ui/badge';
import cx from 'classnames';
import styles from './TagBadge.module.scss';
interface TagBadgeProps {
children: ReactNode;
@@ -22,7 +25,7 @@ function TagBadge({
<Badge
color="sienna"
variant="outline"
className={className}
className={cx(styles.static, className)}
closable={closable}
onClose={onClose}
>

View File

@@ -0,0 +1,112 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TagKeyValueInput from './TagKeyValueInput';
const TID = 'tag-key-value-input';
type User = ReturnType<typeof userEvent.setup>;
const startEditingFirstChip = async (user: User): Promise<HTMLElement> => {
await user.dblClick(screen.getAllByTestId(`${TID}-chip`)[0]);
return screen.getByTestId(`${TID}-edit`);
};
describe('TagKeyValueInput — inline chip edit', () => {
it('shows an error and stays in edit mode when Enter commits an invalid value', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={['env:prod']} onTagsChange={onTagsChange} />);
const input = await startEditingFirstChip(user);
await user.clear(input);
await user.type(input, 'novalue{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(
'key:value format',
);
// Still editing (input present), and no change committed.
expect(screen.getByTestId(`${TID}-edit`)).toBeInTheDocument();
expect(onTagsChange).not.toHaveBeenCalled();
});
it('shows a duplicate error when Enter commits an existing tag', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(
<TagKeyValueInput
tags={['env:prod', 'team:pulse']}
onTagsChange={onTagsChange}
/>,
);
const input = await startEditingFirstChip(user);
await user.clear(input);
await user.type(input, 'team:pulse{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(
'already exists',
);
expect(onTagsChange).not.toHaveBeenCalled();
});
it('commits a valid edit on Enter', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={['env:prod']} onTagsChange={onTagsChange} />);
const input = await startEditingFirstChip(user);
await user.clear(input);
await user.type(input, 'env:staging{Enter}');
expect(onTagsChange).toHaveBeenCalledWith(['env:staging']);
expect(screen.queryByTestId(`${TID}-error`)).not.toBeInTheDocument();
});
it('reverts silently (no error) when blurring an invalid edit', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={['env:prod']} onTagsChange={onTagsChange} />);
const input = await startEditingFirstChip(user);
await user.clear(input);
await user.type(input, 'novalue');
await user.tab(); // blur off the edit input
expect(screen.queryByTestId(`${TID}-error`)).not.toBeInTheDocument();
expect(screen.queryByTestId(`${TID}-edit`)).not.toBeInTheDocument();
expect(onTagsChange).not.toHaveBeenCalled();
});
});
describe('TagKeyValueInput — backend-rule validation', () => {
it('rejects a key containing a space on inline edit', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={['env:prod']} onTagsChange={onTagsChange} />);
const input = await startEditingFirstChip(user);
await user.clear(input);
await user.type(input, 'my key:prod{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(
'spaces or special characters',
);
expect(screen.getByTestId(`${TID}-edit`)).toBeInTheDocument();
expect(onTagsChange).not.toHaveBeenCalled();
});
it('rejects a value containing a space in the new-tag input', async () => {
const user = userEvent.setup();
const onTagsChange = jest.fn();
render(<TagKeyValueInput tags={[]} onTagsChange={onTagsChange} />);
const input = screen.getByTestId(TID);
await user.type(input, 'env:pro d{Enter}');
expect(screen.getByTestId(`${TID}-error`)).toHaveTextContent(
'spaces or special characters',
);
expect(onTagsChange).not.toHaveBeenCalled();
});
});

View File

@@ -5,7 +5,7 @@ import { Typography } from '@signozhq/ui/typography';
import cx from 'classnames';
import TagBadge from '../TagBadge/TagBadge';
import { parseKeyValueTag } from './utils';
import { validateTag } from './utils';
import styles from './TagKeyValueInput.module.scss';
@@ -43,16 +43,12 @@ function TagKeyValueInput({
if (!raw) {
return;
}
const normalized = parseKeyValueTag(raw);
if (!normalized) {
setError('Tags must be in key:value format (both sides required).');
const result = validateTag(raw, tags);
if ('error' in result) {
setError(result.error);
return;
}
if (tags.includes(normalized)) {
setError('This tag already exists.');
return;
}
onTagsChange([...tags, normalized]);
onTagsChange([...tags, result.tag]);
setInputValue('');
setError('');
};
@@ -82,15 +78,20 @@ function TagKeyValueInput({
const cancelEdit = (): void => {
setEditIndex(-1);
setEditValue('');
setError('');
};
const commitEdit = (): void => {
const normalized = parseKeyValueTag(editValue);
// Drop into a no-op (revert) on invalid or duplicate edits rather than
// stranding the user in an un-exitable edit box.
if (normalized && !tags.some((t, i) => t === normalized && i !== editIndex)) {
onTagsChange(tags.map((t, i) => (i === editIndex ? normalized : t)));
const commitEdit = (revertOnInvalid = false): void => {
const result = validateTag(editValue, tags, editIndex);
if ('error' in result) {
if (revertOnInvalid) {
cancelEdit();
} else {
setError(result.error);
}
return;
}
onTagsChange(tags.map((t, i) => (i === editIndex ? result.tag : t)));
cancelEdit();
};
@@ -121,11 +122,14 @@ function TagKeyValueInput({
value={editValue}
autoFocus
testId={`${testId}-edit`}
onChange={(e: ChangeEvent<HTMLInputElement>): void =>
setEditValue(e.target.value)
}
onChange={(e: ChangeEvent<HTMLInputElement>): void => {
setEditValue(e.target.value);
if (error) {
setError('');
}
}}
onKeyDown={handleEditKeyDown}
onBlur={commitEdit}
onBlur={(): void => commitEdit(true)}
/>
) : (
<TagBadge

View File

@@ -15,3 +15,42 @@ export function parseKeyValueTag(raw: string): string | null {
}
return `${key}:${value}`;
}
const TAG_KEY_REGEX = new RegExp('^[a-zA-Z$_@{#][a-zA-Z0-9$_@#{}:/-]*$');
const TAG_VALUE_REGEX = new RegExp('^[a-zA-Z0-9$_@#{}:.+=/-]*$');
const MAX_TAG_LEN = 32;
export type TagValidation = { tag: string } | { error: string };
export function validateTag(
raw: string,
existingTags: string[],
excludeIndex = -1,
): TagValidation {
const normalized = parseKeyValueTag(raw);
if (!normalized) {
return { error: 'Tags must be in key:value format (both sides required).' };
}
const separator = normalized.indexOf(':');
const key = normalized.slice(0, separator);
const value = normalized.slice(separator + 1);
if (!TAG_KEY_REGEX.test(key)) {
return { error: 'Tag keys cannot contain spaces or special characters.' };
}
if (!TAG_VALUE_REGEX.test(value)) {
return { error: 'Tag values cannot contain spaces or special characters.' };
}
if (key.length > MAX_TAG_LEN || value.length > MAX_TAG_LEN) {
return {
error: `Tag key and value must each be ${MAX_TAG_LEN} characters or fewer.`,
};
}
if (
existingTags.some(
(tag, index) => tag === normalized && index !== excludeIndex,
)
) {
return { error: 'This tag already exists.' };
}
return { tag: normalized };
}

View File

@@ -1,7 +1,11 @@
import { ComponentProps, memo } from 'react';
import { ComponentProps, memo, useCallback, useLayoutEffect } from 'react';
import { TableComponents } from 'react-virtuoso';
import cx from 'classnames';
import {
chromePerformanceTanstackTableBeginHover,
chromePerformanceMeasureTanstackTable,
} from './perfDevtools';
import TanStackRowCells from './TanStackRow';
import {
useClearRowHovered,
@@ -10,6 +14,7 @@ import {
import { FlatItem, TableRowContext } from './types';
import tableStyles from './TanStackTable.module.scss';
import { chromePerformanceNow } from 'lib/chromePerformanceDevTools';
type VirtuosoTableRowProps<TData, TItemKey = string> = ComponentProps<
NonNullable<
@@ -22,6 +27,7 @@ function TanStackCustomTableRow<TData, TItemKey = string>({
context,
...props
}: VirtuosoTableRowProps<TData, TItemKey>): JSX.Element {
const renderStart = chromePerformanceNow();
const rowId = item.row.id;
const rowData = item.row.original;
@@ -29,6 +35,23 @@ function TanStackCustomTableRow<TData, TItemKey = string>({
const setHovered = useSetRowHovered(rowId);
const clearHovered = useClearRowHovered(rowId);
const handleMouseEnter = useCallback((): void => {
chromePerformanceTanstackTableBeginHover(rowId);
setHovered();
}, [rowId, setHovered]);
useLayoutEffect(() => {
chromePerformanceMeasureTanstackTable('Row render', renderStart, {
track: 'Row render',
color: 'secondary',
tooltipText: 'Row render + commit',
properties: [
['rowId', rowId],
['kind', item.kind],
],
});
});
if (item.kind === 'expansion') {
return (
<tr {...props} className={tableStyles.tableRowExpansion}>
@@ -65,7 +88,7 @@ function TanStackCustomTableRow<TData, TItemKey = string>({
{...props}
className={rowClassName}
style={rowStyle}
onMouseEnter={setHovered}
onMouseEnter={handleMouseEnter}
onMouseLeave={clearHovered}
>
<TanStackRowCells

View File

@@ -0,0 +1,30 @@
import { useLayoutEffect, type ReactNode } from 'react';
import { chromePerformanceTanstackTableEndHover } from './perfDevtools';
import { useIsRowHovered } from './TanStackTableStateContext';
import { TooltipSimple, TooltipSimpleProps } from '@signozhq/ui/tooltip';
export type HoverTooltipProps = Omit<TooltipSimpleProps, 'open'> & {
rowId: string;
children: ReactNode;
};
export function TanStackHoverTooltip({
rowId,
children,
...tooltipProps
}: HoverTooltipProps): JSX.Element {
const isHovered = useIsRowHovered(rowId);
useLayoutEffect(() => {
if (isHovered) {
chromePerformanceTanstackTableEndHover(rowId);
}
}, [isHovered, rowId]);
if (!isHovered) {
return <>{children}</>;
}
return <TooltipSimple {...tooltipProps}>{children}</TooltipSimple>;
}

View File

@@ -5,6 +5,7 @@ import {
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useMemo,
useRef,
} from 'react';
@@ -44,6 +45,7 @@ import {
TanStackTableHandle,
TanStackTableProps,
} from './types';
import { chromePerformanceMeasureTanstackTable } from './perfDevtools';
import { useColumnDnd } from './useColumnDnd';
import { useColumnHandlers } from './useColumnHandlers';
import { useColumnState } from './useColumnState';
@@ -56,6 +58,7 @@ import { VirtuosoTableColGroup } from './VirtuosoTableColGroup';
import tableStyles from './TanStackTable.module.scss';
import viewStyles from './TanStackTableView.module.scss';
import { chromePerformanceNow } from 'lib/chromePerformanceDevTools';
const COLUMN_DND_AUTO_SCROLL = {
layoutShiftCompensation: false as const,
@@ -116,6 +119,8 @@ function TanStackTableInner<TData, TItemKey = string>(
);
}
const renderStart = chromePerformanceNow();
const virtuosoRef = useRef<TableVirtuosoHandle | null>(null);
const isDarkMode = useIsDarkMode();
@@ -344,6 +349,19 @@ function TanStackTableInner<TData, TItemKey = string>(
const visibleColumnsCount = table.getVisibleFlatColumns().length;
useLayoutEffect(() => {
chromePerformanceMeasureTanstackTable('Table render', renderStart, {
track: 'Table render',
color: 'primary',
tooltipText: 'TanStackTable render + commit',
properties: [
['rows', String(flatItems.length)],
['columns', String(visibleColumnsCount)],
['loading', String(isLoading)],
],
});
});
const columnOrderKey = useMemo(() => columnIds.join(','), [columnIds]);
const columnVisibilityKey = useMemo(
() =>

View File

@@ -1,6 +1,8 @@
import { TanStackHoverTooltip } from './TanStackHoverTooltip';
import { TanStackTableBase } from './TanStackTable';
import TanStackTableText from './TanStackTableText';
export * from './TanStackHoverTooltip';
export * from './TanStackTableStateContext';
export * from './types';
export * from './useCalculatedPageSize';
@@ -274,6 +276,7 @@ export * from './useTableParams';
*/
const TanStackTable = Object.assign(TanStackTableBase, {
Text: TanStackTableText,
HoverTooltip: TanStackHoverTooltip,
});
export default TanStackTable;

View File

@@ -0,0 +1,35 @@
import {
createChromePerformanceInteractionTracker,
chromePerformanceMeasure,
ChromePerformanceTrackEntryOptions,
} from 'lib/chromePerformanceDevTools';
const TABLE_TRACK_GROUP = 'SigNoz Table';
export function chromePerformanceMeasureTanstackTable(
name: string,
start: number,
{
track,
color,
tooltipText,
properties,
}: Omit<ChromePerformanceTrackEntryOptions, 'trackGroup'>,
): void {
chromePerformanceMeasure(name, start, {
trackGroup: TABLE_TRACK_GROUP,
track,
color,
tooltipText,
properties,
});
}
const hoverTracker = createChromePerformanceInteractionTracker(
TABLE_TRACK_GROUP,
'Hover',
'Row hover',
);
export const chromePerformanceTanstackTableBeginHover = hoverTracker.begin;
export const chromePerformanceTanstackTableEndHover = hoverTracker.end;

View File

@@ -21,6 +21,7 @@ export type TableCellContext<TData, TValue> = {
value: TValue;
isActive: boolean;
rowIndex: number;
rowId: string;
isExpanded: boolean;
canExpand: boolean;
toggleExpanded: () => void;

View File

@@ -135,6 +135,7 @@ export function buildTanstackColumnDef<TData, TItemKey = string>(
value: getValue() as TData[any],
isActive: isRowActive?.(rowData) ?? false,
rowIndex: row.index,
rowId: row.id,
isExpanded: row.getIsExpanded(),
canExpand: row.getCanExpand(),
toggleExpanded: (): void => {

View File

@@ -1,9 +1,11 @@
import { QueryClient, QueryClientProvider } from 'react-query';
import { MemoryRouter } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { MemoryRouter, useHistory } from 'react-router-dom';
import { fireEvent, render, screen } from '@testing-library/react';
import { AlertTypes } from 'types/api/alerts/alertTypes';
import { INITIAL_CREATE_ALERT_STATE } from '../constants';
import { CreateAlertProvider, useCreateAlertState } from '../index';
import { AlertThresholdMatchType } from '../types';
// The provider only needs a query with a unit + empty builder for these assertions.
jest.mock('hooks/queryBuilder/useQueryBuilder', () => ({
@@ -108,3 +110,67 @@ describe('CreateAlertProvider — URL-declared prefill (issue #5291)', () => {
expect(screen.getByTestId('window-type')).toHaveTextContent('cumulative');
});
});
// Reproduces the #12137 regression: on the alert overview page the query builder
// rewrites location.search after the alert loads, which used to re-run the prefill
// effect and RESET the loaded threshold back to 0.
function SearchMutator(): JSX.Element {
const routerHistory = useHistory();
return (
<button
type="button"
data-testid="mutate-search"
onClick={(): void =>
routerHistory.replace(
'/alerts/overview?compositeQuery=normalized&ruleId=r1',
)
}
>
change search
</button>
);
}
describe('CreateAlertProvider — edit mode ignores URL prefill', () => {
it('keeps loaded thresholds when the query builder rewrites location.search', () => {
const initialAlertState = {
...INITIAL_CREATE_ALERT_STATE,
threshold: {
...INITIAL_CREATE_ALERT_STATE.threshold,
matchType: AlertThresholdMatchType.AT_LEAST_ONCE,
thresholds: [
{
...INITIAL_CREATE_ALERT_STATE.threshold.thresholds[0],
thresholdValue: 245,
},
],
},
};
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
render(
<MemoryRouter initialEntries={['/alerts/overview?ruleId=r1']}>
<QueryClientProvider client={queryClient}>
<CreateAlertProvider
initialAlertType={AlertTypes.METRICS_BASED_ALERT}
isEditMode
ruleId="r1"
initialAlertState={initialAlertState}
>
<Probe />
<SearchMutator />
</CreateAlertProvider>
</QueryClientProvider>
</MemoryRouter>,
);
expect(screen.getByTestId('threshold-value')).toHaveTextContent('245');
// Simulate the query builder normalizing the query into the URL post-load.
fireEvent.click(screen.getByTestId('mutate-search'));
expect(screen.getByTestId('threshold-value')).toHaveTextContent('245');
});
});

View File

@@ -168,6 +168,14 @@ export function CreateAlertProvider(
const yAxisUnitAppliedRef = useRef(false);
useEffect(() => {
// URL-declared prefill is a create-flow concern. In edit mode the alert
// state is owned by SET_INITIAL_STATE; running the RESET below would wipe
// the loaded thresholds each time the query builder rewrites location.search
// (which yields a fresh urlPrefill object and re-triggers this effect).
if (isEditMode) {
return;
}
setCreateAlertState({
slice: CreateAlertSlice.THRESHOLD,
action: {
@@ -235,7 +243,7 @@ export function CreateAlertProvider(
},
});
}
}, [alertType, urlPrefill, ruleNameFromURL, yAxisUnitFromURL]);
}, [alertType, urlPrefill, ruleNameFromURL, yAxisUnitFromURL, isEditMode]);
useEffect(() => {
if (isEditMode && initialAlertState) {

View File

@@ -65,4 +65,5 @@
min-width: 0;
padding-left: 12px;
padding-bottom: 12px;
padding-top: 8px;
}

View File

@@ -25,20 +25,52 @@ describe('calculateChartDimensions', () => {
});
});
it('RIGHT: reserves a side column capped at 30% of the width and keeps full height', () => {
it('RIGHT: reserves a side column capped at 320px / 40% of the width and keeps full height', () => {
const dims = calculateChartDimensions({
containerWidth: 1000,
containerHeight: 400,
legendConfig: { position: LegendPosition.RIGHT },
seriesLabels: labels(10, 40),
});
// 40-char labels approximate to 336px, capped at min(240, 30% of 1000).
expect(dims.legendWidth).toBe(240);
expect(dims.width).toBe(760);
expect(dims.legendWidth).toBe(320);
expect(dims.width).toBe(680);
expect(dims.height).toBe(400);
expect(dims.legendHeight).toBe(400);
});
it('RIGHT: sizes the column to the longest label when it fits under the cap', () => {
const dims = calculateChartDimensions({
containerWidth: 1000,
containerHeight: 400,
legendConfig: { position: LegendPosition.RIGHT },
seriesLabels: labels(5, 20),
});
expect(dims.legendWidth).toBe(216);
expect(dims.width).toBe(784);
});
it('RIGHT: never shrinks the column below the 150px floor', () => {
const dims = calculateChartDimensions({
containerWidth: 1000,
containerHeight: 400,
legendConfig: { position: LegendPosition.RIGHT },
seriesLabels: labels(3, 3),
});
expect(dims.legendWidth).toBe(150);
expect(dims.width).toBe(850);
});
it('RIGHT: on a narrow container the legend never takes more than 40% of the width', () => {
const dims = calculateChartDimensions({
containerWidth: 300,
containerHeight: 400,
legendConfig: { position: LegendPosition.RIGHT },
seriesLabels: labels(10, 40),
});
expect(dims.legendWidth).toBe(120);
expect(dims.width).toBe(180);
});
it('BOTTOM: a single row of items reserves one legend row', () => {
const dims = calculateChartDimensions({
containerWidth: 1000,

View File

@@ -15,6 +15,12 @@ const BASE_LEGEND_WIDTH = 16;
const LEGEND_PADDING = 12;
const LEGEND_LINE_HEIGHT = 28;
// RIGHT legend is a vertical column with its own width budget (cap protects the donut).
const MAX_RIGHT_LEGEND_WIDTH = 320;
const RIGHT_LEGEND_WIDTH_RATIO = 0.4;
// Column padding + copy button, not covered by the text-length estimate.
const RIGHT_LEGEND_RESERVED_WIDTH = 40;
/**
* Calculates the average width of the legend items based on the labels of the series.
* @param legends - The labels of the series.
@@ -42,7 +48,7 @@ export function calculateAverageLegendWidth(legends: string[]): number {
* Implementation details (high level):
* - Approximates legend item width from label text length, using a fixed average char width.
* - RIGHT legend:
* - `legendWidth` is clamped between 150px and min(MAX_LEGEND_WIDTH, 30% of container width).
* - `legendWidth` fits the longest label, clamped to [150px, min(MAX_RIGHT_LEGEND_WIDTH, 40% width)].
* - Chart width is `containerWidth - legendWidth`.
* - BOTTOM legend:
* - Computes how many items fit per row, then uses at most 2 rows.
@@ -80,9 +86,22 @@ export function calculateChartDimensions({
const legendItemCount = seriesLabels.length;
if (legendConfig.position === LegendPosition.RIGHT) {
const maxRightLegendWidth = Math.min(MAX_LEGEND_WIDTH, containerWidth * 0.3);
// Size the column to the longest name (up to the cap) so it doesn't ellipsize.
const longestLabelLength = seriesLabels.reduce(
(max, label) => Math.max(max, label.length),
0,
);
const desiredLegendWidth =
BASE_LEGEND_WIDTH +
longestLabelLength * AVG_CHAR_WIDTH +
RIGHT_LEGEND_RESERVED_WIDTH;
const maxRightLegendWidth = Math.min(
MAX_RIGHT_LEGEND_WIDTH,
containerWidth * RIGHT_LEGEND_WIDTH_RATIO,
);
const rightLegendWidth = Math.min(
Math.max(150, approxLegendItemWidth),
Math.max(150, desiredLegendWidth),
maxRightLegendWidth,
);

View File

@@ -15,6 +15,10 @@
&--legend-right {
flex-direction: row;
.chart-layout__legend-wrapper {
padding-top: 8px;
}
}
&__legend-wrapper {

View File

@@ -15,7 +15,6 @@ import APIError from 'types/api/error';
import QuickFilters from 'components/QuickFilters/QuickFilters';
import { QuickFiltersSource } from 'components/QuickFilters/types';
import { InfraMonitoringEvents } from 'constants/events';
import { FeatureKeys } from 'constants/features';
import { initialQueriesMap } from 'constants/queryBuilder';
import K8sBaseDetails, {
K8sDetailsFilters,
@@ -29,7 +28,6 @@ import {
} from 'container/InfraMonitoringK8sV2/constants';
import { useGetCompositeQueryParam } from 'hooks/queryBuilder/useGetCompositeQueryParam';
import { useQueryBuilder } from 'hooks/queryBuilder/useQueryBuilder';
import { useAppContext } from 'providers/App/App';
import { DataSource } from 'types/common/queryBuilder';
import {
@@ -51,6 +49,7 @@ import { getHostsQuickFiltersConfig } from './utils';
import styles from './InfraMonitoringHosts.module.scss';
import { ArrowUpToLine, Filter } from '@signozhq/icons';
import { NANO_SECOND_MULTIPLIER, useGlobalTimeStore } from 'store/globalTime';
import OverlayScrollbar from 'components/OverlayScrollbar/OverlayScrollbar';
function Hosts(): JSX.Element {
const [showFilters, setShowFilters] = useState(true);
@@ -92,11 +91,6 @@ function Hosts(): JSX.Element {
}
}, [compositeQuery, redirectWithQueryBuilderData]);
const { featureFlags } = useAppContext();
const dotMetricsEnabled =
featureFlags?.find((flag) => flag.name === FeatureKeys.DOT_METRICS_ENABLED)
?.active || false;
const handleFilterVisibilityChange = (): void => {
setShowFilters(!showFilters);
};
@@ -189,8 +183,8 @@ function Hosts(): JSX.Element {
const getInitialLogTracesExpression = useCallback(
(host: InframonitoringtypesHostRecordDTO) =>
hostInitialLogTracesExpression(host, dotMetricsEnabled),
[dotMetricsEnabled],
hostInitialLogTracesExpression(host),
[],
);
const controlListPrefix = !showFilters ? (
<div className={styles.quickFiltersToggleContainer}>
@@ -211,27 +205,31 @@ function Hosts(): JSX.Element {
<div className={styles.infraContentRow}>
{showFilters && (
<div className={styles.quickFiltersContainer}>
<div className={styles.quickFiltersContainerHeader}>
<Typography.Text>Filters</Typography.Text>
<Tooltip title="Collapse Filters">
<ArrowUpToLine
style={{ rotate: '270deg', cursor: 'pointer' }}
onClick={handleFilterVisibilityChange}
size="md"
<OverlayScrollbar>
<>
<div className={styles.quickFiltersContainerHeader}>
<Typography.Text>Filters</Typography.Text>
<Tooltip title="Collapse Filters">
<ArrowUpToLine
style={{ rotate: '270deg', cursor: 'pointer' }}
onClick={handleFilterVisibilityChange}
size="md"
/>
</Tooltip>
</div>
<QuickFilters
source={QuickFiltersSource.INFRA_MONITORING}
config={getHostsQuickFiltersConfig()}
handleFilterVisibilityChange={handleFilterVisibilityChange}
useFieldApis={{
metricNamespace:
METRIC_NAMESPACE_BY_ENTITY[InfraMonitoringEntity.HOSTS],
startUnixMilli,
endUnixMilli,
}}
/>
</Tooltip>
</div>
<QuickFilters
source={QuickFiltersSource.INFRA_MONITORING}
config={getHostsQuickFiltersConfig(dotMetricsEnabled)}
handleFilterVisibilityChange={handleFilterVisibilityChange}
useFieldApis={{
metricNamespace:
METRIC_NAMESPACE_BY_ENTITY[InfraMonitoringEntity.HOSTS],
startUnixMilli,
endUnixMilli,
}}
/>
</>
</OverlayScrollbar>
</div>
)}
<div
@@ -248,6 +246,7 @@ function Hosts(): JSX.Element {
getRowKey={getHostRowKey}
getItemKey={getHostItemKey}
eventCategory={InfraMonitoringEvents.HostEntity}
detailsQueryKeyPrefix="hosts"
/>
</div>
</div>

View File

@@ -48,24 +48,6 @@
width: 280px;
min-width: 280px;
border-right: 1px solid var(--l1-border);
overflow-y: auto;
&::-webkit-scrollbar {
width: 0.1rem;
height: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--accent-primary);
}
&::-webkit-scrollbar-thumb:hover {
background: colox-mix(var(--accent-primary), var(--bg-vanilla-100), 20%);
}
:global(.ant-collapse-header) {
border-bottom: 1px solid var(--l1-border);
@@ -80,28 +62,6 @@
padding-left: 18px;
}
}
:global(.quick-filters) {
overflow-y: auto;
overflow-x: hidden;
&::-webkit-scrollbar {
width: 0.1rem;
height: 0.1rem;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--accent-primary);
}
&::-webkit-scrollbar-thumb:hover {
background: var(--accent-primary);
}
}
}
.quickFiltersContainerHeader {

View File

@@ -8,8 +8,8 @@ import {
InframonitoringtypesHostStatusDTO,
} from 'api/generated/services/sigNoz.schemas';
import { K8sDetailsMetadataConfig } from 'container/InfraMonitoringK8sV2/Base/K8sBaseDetails';
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
import { formatValueForExpression } from 'components/QueryBuilderV2/utils';
import { SelectedItemParams } from 'container/InfraMonitoringK8sV2/hooks';
import {
getHostQueryPayload,
@@ -63,7 +63,7 @@ export const hostDetailsMetadataConfig: HostDetailMetadataConfigType[] = [
},
{
label: 'OPERATING SYSTEM',
getValue: (h): string => h.meta?.['os.type'] || '-',
getValue: (h): string => h.meta?.[INFRA_MONITORING_ATTR_KEYS.OS_TYPE] || '-',
render: (value): React.ReactNode =>
value !== '-' ? (
<Badge variant="outline" className={infraHostsStyles.infraMonitoringTags}>
@@ -101,9 +101,8 @@ export function getHostMetricsQueryPayload(
host: InframonitoringtypesHostRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): ReturnType<typeof getHostQueryPayload> {
return getHostQueryPayload(host.hostName, start, end, dotMetricsEnabled);
return getHostQueryPayload(host.hostName, start, end, true);
}
export { hostWidgetInfo };
@@ -111,17 +110,13 @@ export { hostWidgetInfo };
export const hostGetSelectedItemExpression = (
params: SelectedItemParams,
): string =>
`host.name = ${formatValueForExpression(params.selectedItem ?? '')}`;
`${INFRA_MONITORING_ATTR_KEYS.HOST_NAME} = ${formatValueForExpression(params.selectedItem ?? '')}`;
export function hostInitialLogTracesExpression(
host: InframonitoringtypesHostRecordDTO,
dotMetricsEnabled: boolean,
): string {
const hostKey = dotMetricsEnabled
? INFRA_MONITORING_ATTR_KEYS.HOST_NAME
: 'host_name';
const hostName = formatValueForExpression(host.hostName || '');
return `${hostKey} = ${hostName}`;
return `${INFRA_MONITORING_ATTR_KEYS.HOST_NAME} = ${hostName}`;
}
export function hostInitialEventsExpression(

View File

@@ -130,12 +130,13 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
accessorFn: (row): string => row.status,
width: { min: 140 },
enableSort: false,
cell: ({ value, groupMeta, row }): React.ReactNode => {
cell: ({ value, groupMeta, row, rowId }): React.ReactNode => {
const status = value as InframonitoringtypesHostStatusDTO;
if (groupMeta) {
return (
<GroupedStatusCounts
rowId={rowId}
items={[
{
value: row.activeHostCount,
@@ -195,13 +196,13 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
<ColumnHeader
tooltip="Excluding cache memory."
docPath="/infrastructure-monitoring/host-monitoring#memory-usage"
className={styles.memoryUsageHeader}
>
Memory Usage (WSS)
Memory Usage
<br /> (WSS)
</ColumnHeader>
),
accessorFn: (row): number => row.memory,
width: { min: 240 },
width: { min: 200 },
enableSort: true,
cell: ({ value }): React.ReactNode => {
const memory = value as number;
@@ -248,11 +249,12 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
id: 'load15',
header: (): React.ReactNode => (
<ColumnHeader docPath="/infrastructure-monitoring/host-monitoring#load-avg">
Load Avg (15min)
Load Avg
<br /> (15min)
</ColumnHeader>
),
accessorFn: (row): number => row.load15,
width: { min: 200 },
width: { min: 160 },
enableSort: true,
cell: ({ value }): React.ReactNode => {
const load15 = Number(value);
@@ -276,7 +278,7 @@ export const hostColumnsConfig: HostColumnConfigType[] = [
</ColumnHeader>
),
accessorFn: (row): number => row.diskUsage,
width: { min: 160 },
width: { min: 200 },
enableSort: true,
cell: ({ value }): React.ReactNode => {
const diskUsage = value as number;

View File

@@ -13,14 +13,6 @@
text-align: right;
}
.memoryUsageHeader {
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: var(--spacing-2);
width: 100%;
}
.statusTag {
width: fit-content;
font-family: Inter, sans-serif;

View File

@@ -7,8 +7,8 @@ import {
IQuickFiltersConfig,
} from 'components/QuickFilters/types';
import { TriangleAlert } from '@signozhq/icons';
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
import { CellValueTooltip } from 'container/InfraMonitoringK8sV2/components';
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
import { DataTypes } from 'types/api/queryBuilder/queryAutocompleteResponse';
import { DataSource } from 'types/common/queryBuilder';
const HOSTNAME_DOCS_URL =
@@ -62,32 +62,18 @@ export function HostnameCell({
);
}
export function getHostsQuickFiltersConfig(
dotMetricsEnabled: boolean,
): IQuickFiltersConfig[] {
const hostNameKey = dotMetricsEnabled
? INFRA_MONITORING_ATTR_KEYS.HOST_NAME
: 'host_name';
const osTypeKey = dotMetricsEnabled ? 'os.type' : 'os_type';
const metricName = dotMetricsEnabled
? 'system.cpu.load_average.15m'
: 'system_cpu_load_average_15m';
const environmentKey = dotMetricsEnabled
? 'deployment.environment'
: 'deployment_environment';
export function getHostsQuickFiltersConfig(): IQuickFiltersConfig[] {
return [
{
type: FiltersType.CHECKBOX,
title: 'Host Name',
attributeKey: {
key: hostNameKey,
key: INFRA_MONITORING_ATTR_KEYS.HOST_NAME,
dataType: DataTypes.String,
type: 'resource',
},
aggregateOperator: 'noop',
aggregateAttribute: metricName,
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.SYSTEM_CPU_LOAD_AVERAGE_15M,
dataSource: DataSource.METRICS,
defaultOpen: true,
},
@@ -95,12 +81,12 @@ export function getHostsQuickFiltersConfig(
type: FiltersType.CHECKBOX,
title: 'OS Type',
attributeKey: {
key: osTypeKey,
key: INFRA_MONITORING_ATTR_KEYS.OS_TYPE,
dataType: DataTypes.String,
type: 'resource',
},
aggregateOperator: 'noop',
aggregateAttribute: metricName,
aggregateAttribute: INFRA_MONITORING_ATTR_KEYS.SYSTEM_CPU_LOAD_AVERAGE_15M,
dataSource: DataSource.METRICS,
defaultOpen: true,
},
@@ -108,7 +94,7 @@ export function getHostsQuickFiltersConfig(
type: FiltersType.CHECKBOX,
title: 'Environment',
attributeKey: {
key: environmentKey,
key: INFRA_MONITORING_ATTR_KEYS.DEPLOYMENT_ENVIRONMENT,
dataType: DataTypes.String,
type: 'resource',
},

View File

@@ -1,148 +1,39 @@
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useQuery } from 'react-query';
import { Color, Spacing } from '@signozhq/design-tokens';
import { Button, Drawer, Tooltip } from 'antd';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { X } from '@signozhq/icons';
import { Divider } from '@signozhq/ui/divider';
import { Typography } from '@signozhq/ui/typography';
import { Drawer } from 'antd';
import logEvent from 'api/common/logEvent';
import ErrorContent from 'components/ErrorModal/components/ErrorContent';
import APIError from 'types/api/error';
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
import { InfraMonitoringEvents } from 'constants/events';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
initialQueryState,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { DEFAULT_TIME_RANGE } from 'container/TopNav/DateTimeSelectionV2/constants';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { useIsDarkMode } from 'hooks/useDarkMode';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import GetMinMax from 'lib/getMinMax';
import {
BarChart,
ChevronsLeftRight,
Compass,
DraftingCompass,
ScrollText,
X,
} from '@signozhq/icons';
import { isCustomTimeRange, useGlobalTimeStore } from 'store/globalTime';
import { NANO_SECOND_MULTIPLIER } from 'store/globalTime/utils';
import {
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
GlobalTimeProvider,
NANO_SECOND_MULTIPLIER,
useGlobalTimeStore,
} from 'store/globalTime';
import { InfraMonitoringEntity, VIEW_TYPES } from '../constants';
import EntityEvents from '../EntityDetailsUtils/EntityEvents';
import EntityLogs from '../EntityDetailsUtils/EntityLogs';
import { K8S_ENTITY_LOGS_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityLogs/hooks';
import EntityMetrics from '../EntityDetailsUtils/EntityMetrics';
import EntityTraces from '../EntityDetailsUtils/EntityTraces';
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityTraces/hooks';
import {
SelectedItemParams,
useInfraMonitoringEventsFilters,
useInfraMonitoringLogFilters,
useInfraMonitoringSelectedItemParams,
useInfraMonitoringTracesFilters,
useInfraMonitoringView,
} from '../hooks';
import { INFRA_MONITORING_K8S_PARAMS_KEYS } from '../constants';
import { useInfraMonitoringSelectedItemParams } from '../hooks';
import LoadingContainer from '../LoadingContainer';
import K8sBaseDetailsContent from './K8sBaseDetailsContent';
import { K8sBaseDetailsProps } from './types';
import '../EntityDetailsUtils/entityDetails.styles.scss';
import { parseAsString, useQueryState } from 'nuqs';
import {
EntityCountConfig,
EntityCountsSection,
} from './components/EntityCountsSection/EntityCountsSection';
const TimeRangeOffset = 1000000000;
export type {
CustomTab,
CustomTabRenderProps,
K8sBaseDetailsProps,
K8sDetailsCountConfig,
K8sDetailsFilters,
K8sDetailsMetadataConfig,
} from './types';
export interface K8sDetailsMetadataConfig<T> {
label: string;
getValue: (entity: T) => string | number;
render?: (value: string | number, entity: T) => React.ReactNode;
}
export type K8sDetailsCountConfig<T> = EntityCountConfig<T>;
export interface K8sDetailsFilters {
filter: { expression: string };
start: number;
end: number;
}
export interface CustomTabRenderProps<T> {
entity: T;
timeRange: { startTime: number; endTime: number };
selectedInterval: Time;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
}
export interface CustomTab<T> {
key: string;
label: string;
icon: React.ReactNode;
render: (props: CustomTabRenderProps<T>) => React.ReactNode;
}
export interface K8sBaseDetailsProps<T> {
category: InfraMonitoringEntity;
eventCategory: string;
// Data fetching configuration
getSelectedItemExpression: (params: SelectedItemParams) => string;
fetchEntityData: (
filters: K8sDetailsFilters,
signal?: AbortSignal,
) => Promise<{ data: T | null; error?: APIError | null }>;
// Entity configuration
getEntityName: (entity: T) => string;
getInitialLogTracesExpression: (entity: T) => string;
getInitialEventsExpression: (entity: T) => string;
metadataConfig: K8sDetailsMetadataConfig<T>[];
countsConfig?: K8sDetailsCountConfig<T>[];
getCountsFilterExpression?: (entity: T) => string;
entityWidgetInfo: {
title: string;
yAxisUnit: string;
docPath?: string;
}[];
getEntityQueryPayload: (
entity: T,
start: number,
end: number,
dotMetricsEnabled: boolean,
) => GetQueryResultsProps[];
queryKeyPrefix: string;
/** When true, only metrics are shown and the Metrics/Logs/Traces/Events tab bar is hidden. */
hideDetailViewTabs?: boolean;
tabsConfig?: {
showMetrics?: boolean;
showLogs?: boolean;
showTraces?: boolean;
showEvents?: boolean;
};
customTabs?: Array<CustomTab<T>>;
}
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function K8sBaseDetails<T>({
category,
eventCategory,
@@ -163,7 +54,6 @@ export default function K8sBaseDetails<T>({
}: K8sBaseDetailsProps<T>): JSX.Element {
const selectedTime = useGlobalTimeStore((s) => s.selectedTime);
const getMinMaxTime = useGlobalTimeStore((s) => s.getMinMaxTime);
const lastComputedMinMax = useGlobalTimeStore((s) => s.lastComputedMinMax);
const getAutoRefreshQueryKey = useGlobalTimeStore(
(s) => s.getAutoRefreshQueryKey,
);
@@ -237,86 +127,9 @@ export default function K8sBaseDetails<T>({
const entityName = entity ? getEntityName(entity) : '';
// Content state (previously in K8sBaseDetailsContent)
const tabVisibility = useMemo(
() => ({
showMetrics: true,
showLogs: true,
showTraces: true,
showEvents: true,
...tabsConfig,
}),
[tabsConfig],
);
const { startMs, endMs } = useMemo(
() => ({
startMs: Math.floor(lastComputedMinMax.minTime / NANO_SECOND_MULTIPLIER),
endMs: Math.floor(lastComputedMinMax.maxTime / NANO_SECOND_MULTIPLIER),
}),
[lastComputedMinMax],
);
const [modalTimeRange, setModalTimeRange] = useState(() => ({
startTime: startMs,
endTime: endMs,
}));
// TODO(h4ad): Remove this and use context/zustand
const lastSelectedInterval = useRef<Time | null>(null);
const [selectedInterval, setSelectedInterval] = useState<Time>(
lastSelectedInterval.current
? lastSelectedInterval.current
: isCustomTimeRange(selectedTime)
? DEFAULT_TIME_RANGE
: selectedTime,
);
const [selectedView, setSelectedView] = useInfraMonitoringView();
const effectiveView = hideDetailViewTabs ? VIEW_TYPES.METRICS : selectedView;
const validTabs = useMemo(() => {
const tabs: string[] = [];
if (tabVisibility.showMetrics) {
tabs.push(VIEW_TYPES.METRICS);
}
if (tabVisibility.showLogs) {
tabs.push(VIEW_TYPES.LOGS);
}
if (tabVisibility.showTraces) {
tabs.push(VIEW_TYPES.TRACES);
}
if (tabVisibility.showEvents) {
tabs.push(VIEW_TYPES.EVENTS);
}
if (customTabs) {
tabs.push(...customTabs.map((t) => t.key));
}
return tabs;
}, [tabVisibility, customTabs]);
useEffect(() => {
if (!hideDetailViewTabs && !validTabs.includes(selectedView)) {
const firstValid = validTabs[0] || VIEW_TYPES.METRICS;
void setSelectedView(firstValid);
}
}, [hideDetailViewTabs, selectedView, validTabs, setSelectedView]);
const [, setLogFiltersParam] = useInfraMonitoringLogFilters();
const [, setTracesFiltersParam] = useInfraMonitoringTracesFilters();
const [, setEventsFiltersParam] = useInfraMonitoringEventsFilters();
const [userLogsExpression] = useQueryState(
K8S_ENTITY_LOGS_EXPRESSION_KEY,
parseAsString,
);
const [userTracesExpression] = useQueryState(
K8S_ENTITY_TRACES_EXPRESSION_KEY,
parseAsString,
);
useEffect(() => {
if (entity) {
logEvent(InfraMonitoringEvents.PageVisited, {
void logEvent(InfraMonitoringEvents.PageVisited, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
@@ -324,136 +137,6 @@ export default function K8sBaseDetails<T>({
}
}, [entity, eventCategory]);
useEffect(() => {
const currentSelectedInterval = lastSelectedInterval.current || selectedTime;
if (!isCustomTimeRange(currentSelectedInterval)) {
setSelectedInterval(currentSelectedInterval);
const { minTime, maxTime } = getMinMaxTime();
setModalTimeRange({
startTime: Math.floor(minTime / TimeRangeOffset),
endTime: Math.floor(maxTime / TimeRangeOffset),
});
}
}, [getMinMaxTime, selectedTime]);
const handleTabChange = (value: string | null): void => {
if (!value) {
return;
}
setSelectedView(value);
setLogFiltersParam(null);
setTracesFiltersParam(null);
setEventsFiltersParam(null);
logEvent(InfraMonitoringEvents.TabChanged, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
view: value,
});
};
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
lastSelectedInterval.current = interval as Time;
setSelectedInterval(interval as Time);
if (interval === 'custom' && dateTimeRange) {
setModalTimeRange({
startTime: Math.floor(dateTimeRange[0] / 1000),
endTime: Math.floor(dateTimeRange[1] / 1000),
});
} else {
const { maxTime, minTime } = GetMinMax(interval);
setModalTimeRange({
startTime: Math.floor(minTime / TimeRangeOffset),
endTime: Math.floor(maxTime / TimeRangeOffset),
});
}
logEvent(InfraMonitoringEvents.TimeUpdated, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
interval,
view: effectiveView,
});
},
[eventCategory, effectiveView],
);
const handleExplorePagesRedirect = (): void => {
const urlQuery = new URLSearchParams();
if (selectedInterval !== 'custom') {
urlQuery.set(QueryParams.relativeTime, selectedInterval);
} else {
urlQuery.delete(QueryParams.relativeTime);
urlQuery.set(QueryParams.startTime, modalTimeRange.startTime.toString());
urlQuery.set(QueryParams.endTime, modalTimeRange.endTime.toString());
}
logEvent(InfraMonitoringEvents.ExploreClicked, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
view: selectedView,
});
if (selectedView === VIEW_TYPES.LOGS) {
const fullExpression = combineInitialAndUserExpression(
logsAndTracesInitialExpression,
userLogsExpression || '',
);
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.logs,
aggregateOperator: LogsAggregatorOperator.NOOP,
expression: fullExpression,
filter: { expression: fullExpression },
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
} else if (selectedView === VIEW_TYPES.TRACES) {
const fullExpression = combineInitialAndUserExpression(
logsAndTracesInitialExpression,
userTracesExpression || '',
);
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
expression: fullExpression,
filter: { expression: fullExpression },
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
}
};
return (
<Drawer
width="70%"
@@ -480,8 +163,9 @@ export default function K8sBaseDetails<T>({
destroyOnClose
closeIcon={<X size={16} style={{ marginTop: Spacing.MARGIN_1 }} />}
>
{isEntityLoading && <LoadingContainer />}
{(isEntityError || hasResponseError) && (
{(isEntityLoading || !selectedItem) && <LoadingContainer />}
{selectedItem && (isEntityError || hasResponseError) && (
<div className="entity-error-container">
<ErrorContent
error={
@@ -497,208 +181,34 @@ export default function K8sBaseDetails<T>({
/>
</div>
)}
{entity && !isEntityLoading && !hasResponseError && (
<>
<div className="entity-detail-drawer__entity">
<div className="entity-details-grid">
<div className="labels-row">
{metadataConfig.map((config) => (
<Typography.Text
key={config.label}
color="muted"
className="entity-details-metadata-label"
>
{config.label}
</Typography.Text>
))}
</div>
<div className="values-row">
{metadataConfig.map((config) => {
const value = config.getValue(entity);
const displayValue = String(value);
return (
<Typography.Text
key={config.label}
className="entity-details-metadata-value"
>
{config.render ? (
config.render(value, entity)
) : (
<Tooltip title={displayValue}>{displayValue}</Tooltip>
)}
</Typography.Text>
);
})}
</div>
</div>
{countsConfig &&
countsConfig.length > 0 &&
selectedItem &&
getCountsFilterExpression && (
<EntityCountsSection
entity={entity}
countsConfig={countsConfig}
selectedItem={selectedItem}
filterExpression={getCountsFilterExpression(entity)}
closeDrawer={handleClose}
/>
)}
</div>
{!hideDetailViewTabs && (
<div className="views-tabs-container">
<ToggleGroupSimple
type="single"
className="views-tabs"
onChange={handleTabChange}
value={selectedView}
items={[
...(tabVisibility.showMetrics
? [
{
value: VIEW_TYPES.METRICS,
label: (
<div className="view-title">
<BarChart size={14} />
Metrics
</div>
),
},
]
: []),
...(tabVisibility.showLogs
? [
{
value: VIEW_TYPES.LOGS,
label: (
<div className="view-title">
<ScrollText size={14} />
Logs
</div>
),
},
]
: []),
...(tabVisibility.showTraces
? [
{
value: VIEW_TYPES.TRACES,
label: (
<div className="view-title">
<DraftingCompass size={14} />
Traces
</div>
),
},
]
: []),
...(tabVisibility.showEvents
? [
{
value: VIEW_TYPES.EVENTS,
label: (
<div className="view-title">
<ChevronsLeftRight size={14} />
Events
</div>
),
},
]
: []),
...(customTabs?.map((tab) => ({
value: tab.key,
label: (
<div className="view-title">
{tab.icon}
{tab.label}
</div>
),
})) ?? []),
]}
/>
{selectedView === VIEW_TYPES.LOGS && (
<Tooltip title="Go to Logs Explorer" placement="left">
<Button
icon={<Compass size={18} />}
className="compass-button"
onClick={handleExplorePagesRedirect}
/>
</Tooltip>
)}
{selectedView === VIEW_TYPES.TRACES && (
<Tooltip title="Go to Traces Explorer" placement="left">
<Button
icon={<Compass size={18} />}
className="compass-button"
onClick={handleExplorePagesRedirect}
/>
</Tooltip>
)}
</div>
)}
{effectiveView === VIEW_TYPES.METRICS && (
<EntityMetrics<T>
entity={entity}
selectedInterval={selectedInterval}
timeRange={modalTimeRange}
handleTimeChange={handleTimeChange}
isModalTimeSelection
entityWidgetInfo={entityWidgetInfo}
getEntityQueryPayload={getEntityQueryPayload}
category={category}
queryKey={`${queryKeyPrefix}Metrics`}
/>
)}
{effectiveView === VIEW_TYPES.LOGS && (
<EntityLogs
timeRange={modalTimeRange}
isModalTimeSelection
handleTimeChange={handleTimeChange}
selectedInterval={selectedInterval}
queryKey={`${queryKeyPrefix}Logs`}
category={category}
initialExpression={logsAndTracesInitialExpression}
/>
)}
{effectiveView === VIEW_TYPES.TRACES && (
<EntityTraces
timeRange={modalTimeRange}
isModalTimeSelection
handleTimeChange={handleTimeChange}
selectedInterval={selectedInterval}
queryKey={`${queryKeyPrefix}Traces`}
category={category}
initialExpression={logsAndTracesInitialExpression}
/>
)}
{effectiveView === VIEW_TYPES.EVENTS && tabVisibility.showEvents && (
<EntityEvents
timeRange={modalTimeRange}
isModalTimeSelection
handleTimeChange={handleTimeChange}
selectedInterval={selectedInterval}
category={category}
queryKey={`${queryKeyPrefix}Events`}
initialExpression={eventsInitialExpression}
/>
)}
{customTabs?.map((tab) =>
selectedView === tab.key ? (
<React.Fragment key={tab.key}>
{tab.render({
entity,
timeRange: modalTimeRange,
selectedInterval,
handleTimeChange,
})}
</React.Fragment>
) : null,
)}
</>
{selectedItem && entity && !isEntityLoading && !hasResponseError && (
<GlobalTimeProvider
inheritGlobalTime
enableUrlParams={{
relativeTimeKey: INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_RELATIVE_TIME,
startTimeKey: INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_START_TIME,
endTimeKey: INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_END_TIME,
}}
>
<K8sBaseDetailsContent<T>
entity={entity}
category={category}
eventCategory={eventCategory}
metadataConfig={metadataConfig}
countsConfig={countsConfig}
getCountsFilterExpression={getCountsFilterExpression}
selectedItem={selectedItem}
handleClose={handleClose}
entityWidgetInfo={entityWidgetInfo}
getEntityQueryPayload={getEntityQueryPayload}
queryKeyPrefix={queryKeyPrefix}
hideDetailViewTabs={hideDetailViewTabs}
tabsConfig={tabsConfig}
customTabs={customTabs}
logsAndTracesInitialExpression={logsAndTracesInitialExpression}
eventsInitialExpression={eventsInitialExpression}
/>
</GlobalTimeProvider>
)}
</Drawer>
);

View File

@@ -0,0 +1,401 @@
import { Fragment, useEffect, useMemo } from 'react';
import {
BarChart,
ChevronsLeftRight,
Compass,
DraftingCompass,
ScrollText,
} from '@signozhq/icons';
import { ToggleGroupSimple } from '@signozhq/ui/toggle-group';
import { Typography } from '@signozhq/ui/typography';
import { Button, Tooltip } from 'antd';
import logEvent from 'api/common/logEvent';
import { combineInitialAndUserExpression } from 'components/QueryBuilderV2/QueryV2/QuerySearch/utils';
import { InfraMonitoringEvents } from 'constants/events';
import { QueryParams } from 'constants/query';
import {
initialQueryBuilderFormValuesMap,
initialQueryState,
} from 'constants/queryBuilder';
import ROUTES from 'constants/routes';
import { parseAsString, useQueryState } from 'nuqs';
import {
LogsAggregatorOperator,
TracesAggregatorOperator,
} from 'types/common/queryBuilder';
import { openInNewTab } from 'utils/navigation';
import { VIEW_TYPES } from '../constants';
import { useEntityDetailsTime } from '../EntityDetailsUtils/EntityDateTimeSelector/useEntityDetailsTime';
import EntityEvents from '../EntityDetailsUtils/EntityEvents';
import EntityLogs from '../EntityDetailsUtils/EntityLogs';
import { K8S_ENTITY_LOGS_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityLogs/hooks';
import EntityMetrics from '../EntityDetailsUtils/EntityMetrics';
import EntityTraces from '../EntityDetailsUtils/EntityTraces';
import { K8S_ENTITY_TRACES_EXPRESSION_KEY } from '../EntityDetailsUtils/EntityTraces/hooks';
import {
useInfraMonitoringEventsFilters,
useInfraMonitoringLogFilters,
useInfraMonitoringTracesFilters,
useInfraMonitoringView,
} from '../hooks';
import { EntityCountsSection } from './components/EntityCountsSection/EntityCountsSection';
import { K8sBaseDetailsContentProps } from './types';
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function K8sBaseDetailsContent<T>({
entity,
category,
eventCategory,
metadataConfig,
countsConfig,
getCountsFilterExpression,
selectedItem,
handleClose,
entityWidgetInfo,
getEntityQueryPayload,
queryKeyPrefix,
hideDetailViewTabs,
tabsConfig,
customTabs,
logsAndTracesInitialExpression,
eventsInitialExpression,
}: K8sBaseDetailsContentProps<T>): JSX.Element {
const { timeRange, selectedInterval, handleTimeChange } =
useEntityDetailsTime();
const tabVisibility = useMemo(
() => ({
showMetrics: true,
showLogs: true,
showTraces: true,
showEvents: true,
...tabsConfig,
}),
[tabsConfig],
);
const [selectedView, setSelectedView] = useInfraMonitoringView();
const validTabs = useMemo(() => {
const tabs: string[] = [];
if (tabVisibility.showMetrics) {
tabs.push(VIEW_TYPES.METRICS);
}
if (tabVisibility.showLogs) {
tabs.push(VIEW_TYPES.LOGS);
}
if (tabVisibility.showTraces) {
tabs.push(VIEW_TYPES.TRACES);
}
if (tabVisibility.showEvents) {
tabs.push(VIEW_TYPES.EVENTS);
}
if (customTabs) {
tabs.push(...customTabs.map((t) => t.key));
}
return tabs;
}, [tabVisibility, customTabs]);
useEffect(() => {
if (!hideDetailViewTabs && !validTabs.includes(selectedView)) {
const firstValid = validTabs[0] || VIEW_TYPES.METRICS;
void setSelectedView(firstValid);
}
}, [hideDetailViewTabs, selectedView, validTabs, setSelectedView]);
const effectiveView = hideDetailViewTabs ? VIEW_TYPES.METRICS : selectedView;
const [, setLogFiltersParam] = useInfraMonitoringLogFilters();
const [, setTracesFiltersParam] = useInfraMonitoringTracesFilters();
const [, setEventsFiltersParam] = useInfraMonitoringEventsFilters();
const [userLogsExpression] = useQueryState(
K8S_ENTITY_LOGS_EXPRESSION_KEY,
parseAsString,
);
const [userTracesExpression] = useQueryState(
K8S_ENTITY_TRACES_EXPRESSION_KEY,
parseAsString,
);
const handleTabChange = (value: string | null): void => {
if (!value) {
return;
}
void setSelectedView(value);
void setLogFiltersParam(null);
void setTracesFiltersParam(null);
void setEventsFiltersParam(null);
void logEvent(InfraMonitoringEvents.TabChanged, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
view: value,
});
};
const handleExplorePagesRedirect = (): void => {
const urlQuery = new URLSearchParams();
if (selectedInterval !== 'custom') {
urlQuery.set(QueryParams.relativeTime, selectedInterval);
} else {
urlQuery.delete(QueryParams.relativeTime);
// Explorer URL params are in milliseconds, timeRange is in seconds
urlQuery.set(QueryParams.startTime, (timeRange.startTime * 1000).toString());
urlQuery.set(QueryParams.endTime, (timeRange.endTime * 1000).toString());
}
void logEvent(InfraMonitoringEvents.ExploreClicked, {
entity: InfraMonitoringEvents.K8sEntity,
page: InfraMonitoringEvents.DetailedPage,
category: eventCategory,
view: selectedView,
});
if (selectedView === VIEW_TYPES.LOGS) {
const fullExpression = combineInitialAndUserExpression(
logsAndTracesInitialExpression,
userLogsExpression || '',
);
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.logs,
aggregateOperator: LogsAggregatorOperator.NOOP,
expression: fullExpression,
filter: { expression: fullExpression },
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
openInNewTab(`${ROUTES.LOGS_EXPLORER}?${urlQuery.toString()}`);
} else if (selectedView === VIEW_TYPES.TRACES) {
const fullExpression = combineInitialAndUserExpression(
logsAndTracesInitialExpression,
userTracesExpression || '',
);
const compositeQuery = {
...initialQueryState,
queryType: 'builder',
builder: {
...initialQueryState.builder,
queryData: [
{
...initialQueryBuilderFormValuesMap.traces,
aggregateOperator: TracesAggregatorOperator.NOOP,
expression: fullExpression,
filter: { expression: fullExpression },
},
],
},
};
urlQuery.set('compositeQuery', JSON.stringify(compositeQuery));
openInNewTab(`${ROUTES.TRACES_EXPLORER}?${urlQuery.toString()}`);
}
};
return (
<>
<div className="entity-detail-drawer__entity">
<div className="entity-details-grid">
<div className="labels-row">
{metadataConfig.map((config) => (
<Typography.Text
key={config.label}
color="muted"
className="entity-details-metadata-label"
>
{config.label}
</Typography.Text>
))}
</div>
<div className="values-row">
{metadataConfig.map((config) => {
const value = config.getValue(entity);
const displayValue = String(value);
return (
<Typography.Text
key={config.label}
className="entity-details-metadata-value"
>
{config.render ? (
config.render(value, entity)
) : (
<Tooltip title={displayValue}>{displayValue}</Tooltip>
)}
</Typography.Text>
);
})}
</div>
</div>
{countsConfig &&
countsConfig.length > 0 &&
selectedItem &&
getCountsFilterExpression && (
<EntityCountsSection
entity={entity}
countsConfig={countsConfig}
selectedItem={selectedItem}
filterExpression={getCountsFilterExpression(entity)}
closeDrawer={handleClose}
/>
)}
</div>
{!hideDetailViewTabs && (
<div className="views-tabs-container">
<ToggleGroupSimple
type="single"
className="views-tabs"
onChange={handleTabChange}
value={selectedView}
items={[
...(tabVisibility.showMetrics
? [
{
value: VIEW_TYPES.METRICS,
label: (
<div className="view-title">
<BarChart size={14} />
Metrics
</div>
),
},
]
: []),
...(tabVisibility.showLogs
? [
{
value: VIEW_TYPES.LOGS,
label: (
<div className="view-title">
<ScrollText size={14} />
Logs
</div>
),
},
]
: []),
...(tabVisibility.showTraces
? [
{
value: VIEW_TYPES.TRACES,
label: (
<div className="view-title">
<DraftingCompass size={14} />
Traces
</div>
),
},
]
: []),
...(tabVisibility.showEvents
? [
{
value: VIEW_TYPES.EVENTS,
label: (
<div className="view-title">
<ChevronsLeftRight size={14} />
Events
</div>
),
},
]
: []),
...(customTabs?.map((tab) => ({
value: tab.key,
label: (
<div className="view-title">
{tab.icon}
{tab.label}
</div>
),
})) ?? []),
]}
/>
{selectedView === VIEW_TYPES.LOGS && (
<Tooltip title="Go to Logs Explorer" placement="left">
<Button
icon={<Compass size={18} />}
className="compass-button"
onClick={handleExplorePagesRedirect}
/>
</Tooltip>
)}
{selectedView === VIEW_TYPES.TRACES && (
<Tooltip title="Go to Traces Explorer" placement="left">
<Button
icon={<Compass size={18} />}
className="compass-button"
onClick={handleExplorePagesRedirect}
/>
</Tooltip>
)}
</div>
)}
{effectiveView === VIEW_TYPES.METRICS && (
<EntityMetrics<T>
entity={entity}
eventEntity={eventCategory}
entityWidgetInfo={entityWidgetInfo}
getEntityQueryPayload={getEntityQueryPayload}
category={category}
queryKey={`${queryKeyPrefix}Metrics`}
/>
)}
{effectiveView === VIEW_TYPES.LOGS && (
<EntityLogs
eventEntity={eventCategory}
queryKey={`${queryKeyPrefix}Logs`}
category={category}
initialExpression={logsAndTracesInitialExpression}
/>
)}
{effectiveView === VIEW_TYPES.TRACES && (
<EntityTraces
eventEntity={eventCategory}
queryKey={`${queryKeyPrefix}Traces`}
category={category}
initialExpression={logsAndTracesInitialExpression}
/>
)}
{effectiveView === VIEW_TYPES.EVENTS && tabVisibility.showEvents && (
<EntityEvents
eventEntity={eventCategory}
queryKey={`${queryKeyPrefix}Events`}
initialExpression={eventsInitialExpression}
category={category}
/>
)}
{customTabs?.map((tab) =>
selectedView === tab.key ? (
<Fragment key={tab.key}>
{tab.render({
entity,
timeRange,
selectedInterval,
handleTimeChange,
})}
</Fragment>
) : null,
)}
</>
);
}

View File

@@ -74,14 +74,15 @@ export type K8sBaseListProps<
warning?: Querybuildertypesv5QueryWarnDataDTO | null;
}>;
/** Function to get the unique key for a row. */
getRowKey?: (record: T) => string;
getRowKey: (record: T) => string;
/** Function to get the item key used for selection. Can return string or SelectedItemParams. */
getItemKey?: (record: T) => TItemKey;
getItemKey: (record: T) => TItemKey;
eventCategory: InfraMonitoringEvents;
renderEmptyState?: (
context: K8sBaseListEmptyStateContext,
) => React.ReactNode | null;
extraQueryKeyParts?: string[];
detailsQueryKeyPrefix: string;
};
export function K8sBaseList<
@@ -98,6 +99,7 @@ export function K8sBaseList<
eventCategory,
renderEmptyState,
extraQueryKeyParts = [],
detailsQueryKeyPrefix,
}: K8sBaseListProps<T, TItemKey>): JSX.Element {
const { currentQuery } = useQueryBuilder();
const expression = currentQuery.builder.queryData[0]?.filter?.expression || '';
@@ -234,17 +236,29 @@ export function K8sBaseList<
}, [eventCategory, totalCount]);
const handleRowClick = useCallback(
(_record: T, itemKey: TItemKey): void => {
(record: T, itemKey: TItemKey): void => {
if (groupBy.length === 0) {
if (typeof itemKey === 'object' && itemKey !== null) {
setSelectedItemParams(itemKey);
} else {
setSelectedItemParams({
selectedItem: itemKey,
clusterName: null,
namespaceName: null,
});
const params: SelectedItemParams =
typeof itemKey === 'object'
? itemKey
: {
selectedItem: itemKey,
clusterName: null,
namespaceName: null,
};
if (detailsQueryKeyPrefix) {
const detailQueryKey = getAutoRefreshQueryKey(
selectedTime,
`${detailsQueryKeyPrefix}EntityDetails`,
params.selectedItem,
params.clusterName,
params.namespaceName,
);
queryClient.setQueryData(detailQueryKey, { data: record });
}
setSelectedItemParams(params);
}
void logEvent(InfraMonitoringEvents.ItemClicked, {
@@ -253,7 +267,15 @@ export function K8sBaseList<
category: eventCategory,
});
},
[eventCategory, groupBy.length, setSelectedItemParams],
[
eventCategory,
groupBy.length,
setSelectedItemParams,
detailsQueryKeyPrefix,
getAutoRefreshQueryKey,
selectedTime,
queryClient,
],
);
const handleRowClickNewTab = useCallback(
@@ -403,7 +425,7 @@ export function K8sBaseList<
onRowClickNewTab={handleRowClickNewTab}
renderExpandedRow={isGroupedByAttribute ? renderExpandedRow : undefined}
getRowCanExpand={isGroupedByAttribute ? getRowCanExpand : undefined}
className={cx(styles.k8SListTable, expandedRowColumns)}
className={cx(styles.k8SListTable)}
enableQueryParams={{
page: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE,
limit: INFRA_MONITORING_K8S_PARAMS_KEYS.PAGE_SIZE,

View File

@@ -0,0 +1,43 @@
import { useMemo } from 'react';
import { useInfraMonitoringCategory } from '../hooks';
import { getEntityConfig } from './entity.registry';
import { K8sBaseList } from './K8sBaseList';
import K8sBaseDetails from './K8sBaseDetails';
export interface K8sDynamicListProps {
controlListPrefix?: React.ReactNode;
leftFilters?: React.ReactNode;
}
export function K8sDynamicList({
controlListPrefix,
leftFilters,
}: K8sDynamicListProps): JSX.Element | null {
const [selectedCategory] = useInfraMonitoringCategory();
const config = useMemo(
() => getEntityConfig(selectedCategory),
[selectedCategory],
);
if (!config) {
return null;
}
const { list, details } = config;
return (
<>
<K8sBaseList
{...list}
controlListPrefix={controlListPrefix}
leftFilters={leftFilters}
/>
<K8sBaseDetails {...details} />
</>
);
}
export default K8sDynamicList;

View File

@@ -245,6 +245,7 @@ function K8sHeader<TData>({
showAutoRefresh={showAutoRefresh}
showRefreshText={false}
hideShareModal
defaultRelativeTime="30m"
/>
</div>

View File

@@ -1,5 +1,6 @@
import { Box } from '@signozhq/icons';
import { screen } from '@testing-library/react';
import { InfraMonitoringEvents } from 'constants/events';
import userEvent from '@testing-library/user-event';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import { act, render, waitFor } from 'tests/test-utils';
@@ -31,7 +32,7 @@ const mockEntity: TestEntity = {
function createBaseProps() {
return {
category: InfraMonitoringEntity.PODS,
eventCategory: 'Pod',
eventCategory: InfraMonitoringEvents.Pod,
getSelectedItemExpression: (): string => 'k8s.pod.name = "test-pod"',
fetchEntityData: jest
.fn()

View File

@@ -175,8 +175,10 @@ function renderComponent<
>({
queryParams,
onUrlUpdate,
detailsQueryKeyPrefix = 'testEntity',
...props
}: K8sBaseListProps<T, TItemKey> & {
}: Omit<K8sBaseListProps<T, TItemKey>, 'detailsQueryKeyPrefix'> & {
detailsQueryKeyPrefix?: string;
queryParams?: Record<string, string>;
onUrlUpdate?: OnUrlUpdateFunction;
}) {
@@ -203,7 +205,10 @@ function renderComponent<
value={{ viewportHeight: 800, itemHeight: 50 }}
>
<TooltipProvider>
<K8sBaseList<T, TItemKey> {...props} />
<K8sBaseList<T, TItemKey>
{...props}
detailsQueryKeyPrefix={detailsQueryKeyPrefix}
/>
</TooltipProvider>
</VirtuosoMockContext.Provider>
</NuqsTestingAdapter>

View File

@@ -52,7 +52,6 @@ export function EntityCountsSection<T>({
},
};
// TODO(H4ad): After https://github.com/SigNoz/signoz/pull/12038, inherit custom time of drawer to list
const urlParams = new URLSearchParams();
urlParams.set(INFRA_MONITORING_K8S_PARAMS_KEYS.CATEGORY, targetCategory);
urlParams.set(
@@ -60,6 +59,44 @@ export function EntityCountsSection<T>({
encodeURIComponent(JSON.stringify(compositeQuery)),
);
const currentSearchParams = new URLSearchParams(window.location.search);
const detailRelativeTime = currentSearchParams.get(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_RELATIVE_TIME,
);
const detailStartTime = currentSearchParams.get(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_START_TIME,
);
const detailEndTime = currentSearchParams.get(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_END_TIME,
);
const listRelativeTime = currentSearchParams.get(QueryParams.relativeTime);
const listStartTime = currentSearchParams.get(QueryParams.startTime);
const listEndTime = currentSearchParams.get(QueryParams.endTime);
if (listRelativeTime) {
urlParams.set(QueryParams.relativeTime, listRelativeTime);
} else if (listStartTime && listEndTime) {
urlParams.set(QueryParams.startTime, listStartTime);
urlParams.set(QueryParams.endTime, listEndTime);
}
if (detailRelativeTime) {
urlParams.set(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_RELATIVE_TIME,
detailRelativeTime,
);
} else if (detailStartTime && detailEndTime) {
urlParams.set(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_START_TIME,
detailStartTime,
);
urlParams.set(
INFRA_MONITORING_K8S_PARAMS_KEYS.DETAIL_END_TIME,
detailEndTime,
);
}
return `${ROUTES.INFRASTRUCTURE_MONITORING_KUBERNETES}?${urlParams.toString()}`;
};

View File

@@ -0,0 +1,21 @@
import { SelectedItemParams } from '../hooks';
import { K8sBaseListProps } from './K8sBaseList';
import { K8sBaseDetailsProps } from './types';
export type K8sEntityData = { meta?: Record<string, string> | null };
export type K8sEntityListConfig<
T extends K8sEntityData,
TItemKey extends string | SelectedItemParams = string,
> = Omit<K8sBaseListProps<T, TItemKey>, 'controlListPrefix' | 'leftFilters'>;
export type K8sEntityDetailsConfig<T extends K8sEntityData> =
K8sBaseDetailsProps<T>;
export interface K8sEntityConfig<
T extends K8sEntityData,
TItemKey extends string | SelectedItemParams = string,
> {
list: K8sEntityListConfig<T, TItemKey>;
details: K8sEntityDetailsConfig<T>;
}

View File

@@ -0,0 +1,41 @@
import { K8sCategories } from '../constants';
import { SelectedItemParams } from '../hooks';
import { K8sEntityConfig, K8sEntityData } from './entity.config.types';
import { podEntityConfig } from '../Pods/entity.config';
import { nodeEntityConfig } from '../Nodes/entity.config';
import { clusterEntityConfig } from '../Clusters/entity.config';
import { deploymentEntityConfig } from '../Deployments/entity.config';
import { namespaceEntityConfig } from '../Namespaces/entity.config';
import { jobEntityConfig } from '../Jobs/entity.config';
import { daemonSetEntityConfig } from '../DaemonSets/entity.config';
import { statefulSetEntityConfig } from '../StatefulSets/entity.config';
import { volumeEntityConfig } from '../Volumes/entity.config';
type AnyEntityConfig = K8sEntityConfig<
K8sEntityData,
string | SelectedItemParams
>;
function registerConfig<
T extends K8sEntityData,
TItemKey extends string | SelectedItemParams,
>(config: K8sEntityConfig<T, TItemKey>): AnyEntityConfig {
return config as unknown as AnyEntityConfig;
}
export const entityRegistry: Record<string, AnyEntityConfig> = {
[K8sCategories.PODS]: registerConfig(podEntityConfig),
[K8sCategories.NODES]: registerConfig(nodeEntityConfig),
[K8sCategories.CLUSTERS]: registerConfig(clusterEntityConfig),
[K8sCategories.DEPLOYMENTS]: registerConfig(deploymentEntityConfig),
[K8sCategories.NAMESPACES]: registerConfig(namespaceEntityConfig),
[K8sCategories.JOBS]: registerConfig(jobEntityConfig),
[K8sCategories.DAEMONSETS]: registerConfig(daemonSetEntityConfig),
[K8sCategories.STATEFULSETS]: registerConfig(statefulSetEntityConfig),
[K8sCategories.VOLUMES]: registerConfig(volumeEntityConfig),
};
export function getEntityConfig(category: string): AnyEntityConfig | undefined {
return entityRegistry[category];
}

View File

@@ -1,3 +1,18 @@
import { ReactNode } from 'react';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { InfraMonitoringEvents } from 'constants/events';
import { GetQueryResultsProps } from 'lib/dashboard/getQueryResults';
import APIError from 'types/api/error';
import { EntityCountConfig } from './components/EntityCountsSection/EntityCountsSection';
import { InfraMonitoringEntity } from '../constants';
import { SelectedItemParams } from '../hooks';
export type K8sDetailsCountConfig<T> = EntityCountConfig<T>;
export type K8sBaseFilters = {
filter: {
expression: string;
@@ -33,3 +48,99 @@ export type K8sTableRowData<T> = T & {
/** Metadata about which attributes were used for grouping */
groupedByMeta?: Record<string, string>;
};
export interface K8sDetailsMetadataConfig<T> {
label: string;
getValue: (entity: T) => string | number;
render?: (value: string | number, entity: T) => ReactNode;
}
export interface K8sDetailsFilters {
filter: { expression: string };
start: number;
end: number;
}
export interface K8sDetailsWidgetInfo {
title: string;
yAxisUnit: string;
}
export type GetEntityQueryPayload<T> = (
entity: T,
start: number,
end: number,
) => GetQueryResultsProps[];
export interface K8sDetailsTabsConfig {
showMetrics?: boolean;
showLogs?: boolean;
showTraces?: boolean;
showEvents?: boolean;
}
export interface K8sDetailsCustomTabRenderProps<T> {
entity: T;
/** Time range in seconds — see useEntityDetailsTime */
timeRange: { startTime: number; endTime: number };
selectedInterval: Time;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
}
export interface K8sDetailsCustomTab<T> {
key: string;
label: string;
icon: ReactNode;
render: (props: K8sDetailsCustomTabRenderProps<T>) => ReactNode;
}
export interface K8sBaseDetailsProps<T> {
category: InfraMonitoringEntity;
eventCategory: InfraMonitoringEvents;
// Data fetching configuration
getSelectedItemExpression: (params: SelectedItemParams) => string;
fetchEntityData: (
filters: K8sDetailsFilters,
signal?: AbortSignal,
) => Promise<{ data: T | null; error?: APIError | null }>;
// Entity configuration
getEntityName: (entity: T) => string;
getInitialLogTracesExpression: (entity: T) => string;
getInitialEventsExpression: (entity: T) => string;
metadataConfig: K8sDetailsMetadataConfig<T>[];
countsConfig?: K8sDetailsCountConfig<T>[];
getCountsFilterExpression?: (entity: T) => string;
entityWidgetInfo: K8sDetailsWidgetInfo[];
getEntityQueryPayload: GetEntityQueryPayload<T>;
queryKeyPrefix: string;
/** When true, only metrics are shown and the Metrics/Logs/Traces/Events tab bar is hidden. */
hideDetailViewTabs?: boolean;
tabsConfig?: K8sDetailsTabsConfig;
customTabs?: K8sDetailsCustomTab<T>[];
}
export interface K8sBaseDetailsContentProps<T> {
entity: T;
category: InfraMonitoringEntity;
eventCategory: InfraMonitoringEvents;
metadataConfig: K8sDetailsMetadataConfig<T>[];
countsConfig?: K8sDetailsCountConfig<T>[];
getCountsFilterExpression?: (entity: T) => string;
selectedItem: string | null;
handleClose: () => void;
entityWidgetInfo: K8sDetailsWidgetInfo[];
getEntityQueryPayload: GetEntityQueryPayload<T>;
queryKeyPrefix: string;
hideDetailViewTabs: boolean;
tabsConfig?: K8sDetailsTabsConfig;
customTabs?: K8sDetailsCustomTab<T>[];
logsAndTracesInitialExpression: string;
eventsInitialExpression: string;
}
// Aliases for backward compatibility
export type CustomTab<T> = K8sDetailsCustomTab<T>;
export type CustomTabRenderProps<T> = K8sDetailsCustomTabRenderProps<T>;

View File

@@ -9,33 +9,14 @@ import {
import { SelectedItemParams } from 'container/InfraMonitoringK8sV2/hooks';
import { INFRA_MONITORING_ATTR_KEYS } from 'container/InfraMonitoringK8sV2/constants';
const dotToUnder: Record<string, string> = {
'os.type': 'os_type',
'host.name': 'host_name',
'deployment.environment': 'deployment_environment',
'k8s.node.name': 'k8s_node_name',
'k8s.cluster.name': 'k8s_cluster_name',
'k8s.node.uid': 'k8s_node_uid',
'k8s.cronjob.name': 'k8s_cronjob_name',
'k8s.daemonset.name': 'k8s_daemonset_name',
'k8s.deployment.name': 'k8s_deployment_name',
'k8s.job.name': 'k8s_job_name',
'k8s.namespace.name': 'k8s_namespace_name',
'k8s.pod.name': 'k8s_pod_name',
'k8s.pod.uid': 'k8s_pod_uid',
'k8s.statefulset.name': 'k8s_statefulset_name',
'k8s.persistentvolumeclaim.name': 'k8s_persistentvolumeclaim_name',
};
export function getGroupedByMeta<
T extends { meta?: Record<string, string> | null },
>(itemData: T, groupBy: string[]): Record<string, string> {
const result: Record<string, string> = {};
const meta = itemData.meta ?? {};
groupBy.forEach((rawKey) => {
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof meta;
result[rawKey] = (meta[metaKey] || meta[rawKey]) ?? '';
groupBy.forEach((key) => {
result[key] = meta[key] ?? '';
});
return result;
@@ -47,9 +28,8 @@ export function getGroupByEl<
const groupByValues: string[] = [];
const meta = itemData.meta ?? {};
groupBy.forEach((rawKey) => {
const metaKey = (dotToUnder[rawKey] ?? rawKey) as keyof typeof meta;
const value = meta[metaKey] || meta[rawKey] || '<no-value>';
groupBy.forEach((key) => {
const value = meta[key] || '<no-value>';
groupByValues.push(value);
});

View File

@@ -1,153 +0,0 @@
import { useCallback } from 'react';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listClusters } from 'api/generated/services/inframonitoring';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import {
InframonitoringtypesClusterRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { InfraMonitoringEvents } from 'constants/events';
import APIError from 'types/api/error';
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
import { K8sBaseList } from '../Base/K8sBaseList';
import { K8sBaseFilters } from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import {
clusterWidgetInfo,
getClusterMetricsQueryPayload,
k8sClusterDetailsCountsConfig,
k8sClusterDetailsMetadataConfig,
k8sClusterGetCountsFilterExpression,
k8sClusterGetEntityName,
k8sClusterGetSelectedItemExpression,
k8sClusterInitialEventsExpression,
k8sClusterInitialLogTracesExpression,
} from './constants';
import {
getK8sClusterItemKey,
getK8sClusterRowKey,
k8sClustersColumnsConfig,
} from './table.config';
function K8sClustersList({
controlListPrefix,
}: {
controlListPrefix?: React.ReactNode;
}): JSX.Element {
const fetchListData = useCallback(
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
try {
const response = await listClusters(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesClusterRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
const fetchEntityData = useCallback(
async (
filters: K8sDetailsFilters,
signal?: AbortSignal,
): Promise<{
data: InframonitoringtypesClusterRecordDTO | null;
error?: APIError | null;
}> => {
try {
const response = await listClusters(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
return (
<>
<K8sBaseList<InframonitoringtypesClusterRecordDTO>
controlListPrefix={controlListPrefix}
entity={InfraMonitoringEntity.CLUSTERS}
tableColumns={k8sClustersColumnsConfig}
fetchListData={fetchListData}
getRowKey={getK8sClusterRowKey}
getItemKey={getK8sClusterItemKey}
eventCategory={InfraMonitoringEvents.Cluster}
/>
<K8sBaseDetails<InframonitoringtypesClusterRecordDTO>
category={InfraMonitoringEntity.CLUSTERS}
eventCategory={InfraMonitoringEvents.Cluster}
getSelectedItemExpression={k8sClusterGetSelectedItemExpression}
fetchEntityData={fetchEntityData}
getEntityName={k8sClusterGetEntityName}
getInitialLogTracesExpression={k8sClusterInitialLogTracesExpression}
getInitialEventsExpression={k8sClusterInitialEventsExpression}
metadataConfig={k8sClusterDetailsMetadataConfig}
countsConfig={k8sClusterDetailsCountsConfig}
getCountsFilterExpression={k8sClusterGetCountsFilterExpression}
entityWidgetInfo={clusterWidgetInfo}
getEntityQueryPayload={getClusterMetricsQueryPayload}
queryKeyPrefix="cluster"
/>
</>
);
}
export default K8sClustersList;

View File

@@ -24,7 +24,7 @@ import {
export const k8sClusterGetSelectedItemExpression = (
params: SelectedItemParams,
): string =>
`k8s.cluster.name = ${formatValueForExpression(params.selectedItem ?? '')}`;
`${INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME} = ${formatValueForExpression(params.selectedItem ?? '')}`;
export const k8sClusterDetailsMetadataConfig: K8sDetailsMetadataConfig<InframonitoringtypesClusterRecordDTO>[] =
[{ label: 'Cluster Name', getValue: (p): string => p.clusterName || '' }];
@@ -86,7 +86,7 @@ export const k8sClusterGetEntityName = (
export const k8sClusterGetCountsFilterExpression = (
item: InframonitoringtypesClusterRecordDTO,
): string =>
`k8s.cluster.name = ${formatValueForExpression(item.clusterName ?? '')}`;
`${INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME} = ${formatValueForExpression(item.clusterName ?? '')}`;
export const clusterWidgetInfo = [
{
@@ -138,96 +138,7 @@ export const getClusterMetricsQueryPayload = (
cluster: InframonitoringtypesClusterRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const getKey = (dotKey: string, underscoreKey: string): string =>
dotMetricsEnabled ? dotKey : underscoreKey;
const k8sPodCpuUtilizationKey = getKey(
'k8s.pod.cpu.usage',
'k8s_pod_cpu_usage',
);
const k8sNodeAllocatableCpuKey = getKey(
'k8s.node.allocatable_cpu',
'k8s_node_allocatable_cpu',
);
const k8sPodMemoryUsageKey = getKey(
'k8s.pod.memory.usage',
'k8s_pod_memory_usage',
);
const k8sNodeAllocatableMemoryKey = getKey(
'k8s.node.allocatable_memory',
'k8s_node_allocatable_memory',
);
const k8sNodeConditionReadyKey = getKey(
'k8s.node.condition_ready',
'k8s_node_condition_ready',
);
const k8sDeploymentAvailableKey = getKey(
'k8s.deployment.available',
'k8s_deployment_available',
);
const k8sDeploymentDesiredKey = getKey(
'k8s.deployment.desired',
'k8s_deployment_desired',
);
const k8sStatefulsetCurrentPodsKey = getKey(
'k8s.statefulset.current_pods',
'k8s_statefulset_current_pods',
);
const k8sStatefulsetDesiredPodsKey = getKey(
'k8s.statefulset.desired_pods',
'k8s_statefulset_desired_pods',
);
const k8sStatefulsetReadyPodsKey = getKey(
'k8s.statefulset.ready_pods',
'k8s_statefulset_ready_pods',
);
const k8sStatefulsetUpdatedPodsKey = getKey(
'k8s.statefulset.updated_pods',
'k8s_statefulset_updated_pods',
);
const k8sDaemonsetCurrentScheduledNodesKey = getKey(
'k8s.daemonset.current_scheduled_nodes',
'k8s_daemonset_current_scheduled_nodes',
);
const k8sDaemonsetDesiredScheduledNodesKey = getKey(
'k8s.daemonset.desired_scheduled_nodes',
'k8s_daemonset_desired_scheduled_nodes',
);
const k8sDaemonsetReadyNodesKey = getKey(
'k8s.daemonset.ready_nodes',
'k8s_daemonset_ready_nodes',
);
const k8sJobActivePodsKey = getKey(
'k8s.job.active_pods',
'k8s_job_active_pods',
);
const k8sJobSuccessfulPodsKey = getKey(
'k8s.job.successful_pods',
'k8s_job_successful_pods',
);
const k8sJobFailedPodsKey = getKey(
'k8s.job.failed_pods',
'k8s_job_failed_pods',
);
const k8sJobDesiredSuccessfulPodsKey = getKey(
'k8s.job.desired_successful_pods',
'k8s_job_desired_successful_pods',
);
const k8sClusterNameKey = getKey('k8s.cluster.name', 'k8s_cluster_name');
const k8sNodeNameKey = getKey('k8s.node.name', 'k8s_node_name');
const k8sDeploymentNameKey = getKey(
'k8s.deployment.name',
'k8s_deployment_name',
);
const k8sNamespaceNameKey = getKey('k8s.namespace.name', 'k8s_namespace_name');
const k8sStatefulsetNameKey = getKey(
'k8s.statefulset.name',
'k8s_statefulset_name',
);
const k8sDaemonsetNameKey = getKey('k8s.daemonset.name', 'k8s_daemonset_name');
const k8sJobNameKey = getKey('k8s.job.name', 'k8s_job_name');
return [
{
selectedTime: 'GLOBAL_TIME',
@@ -239,7 +150,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
key: k8sPodCpuUtilizationKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -253,7 +164,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -278,7 +189,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
key: k8sPodCpuUtilizationKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
type: 'Gauge',
},
aggregateOperator: 'min',
@@ -292,7 +203,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -317,7 +228,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
key: k8sPodCpuUtilizationKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -331,7 +242,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -356,7 +267,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_allocatable_cpu--float64--Gauge--true',
key: k8sNodeAllocatableCpuKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_ALLOCATABLE_CPU,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -370,7 +281,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -429,7 +340,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
key: k8sPodMemoryUsageKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_MEMORY_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -443,7 +354,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -468,7 +379,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
key: k8sPodMemoryUsageKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_MEMORY_USAGE,
type: 'Gauge',
},
aggregateOperator: 'min',
@@ -482,7 +393,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -507,7 +418,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
key: k8sPodMemoryUsageKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_MEMORY_USAGE,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -521,7 +432,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -546,7 +457,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_allocatable_memory--float64--Gauge--true',
key: k8sNodeAllocatableMemoryKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_ALLOCATABLE_MEMORY,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -560,7 +471,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -619,7 +530,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_condition_ready--float64--Gauge--true',
key: k8sNodeConditionReadyKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY,
type: 'Gauge',
},
aggregateOperator: 'latest',
@@ -633,7 +544,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -647,18 +558,18 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_node_name--string--tag--false',
key: k8sNodeNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME,
type: 'tag',
},
],
having: [
{
columnName: `MAX(${k8sNodeConditionReadyKey})`,
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY})`,
op: '=',
value: 1,
},
],
legend: `{{${k8sNodeNameKey}}}`,
legend: `{{${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME}}}`,
limit: null,
orderBy: [],
queryName: 'A',
@@ -705,7 +616,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_node_condition_ready--float64--Gauge--true',
key: k8sNodeConditionReadyKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY,
type: 'Gauge',
},
aggregateOperator: 'latest',
@@ -719,7 +630,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -733,18 +644,18 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_node_name--string--tag--false',
key: k8sNodeNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME,
type: 'tag',
},
],
having: [
{
columnName: `MAX(${k8sNodeConditionReadyKey})`,
columnName: `MAX(${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_CONDITION_READY})`,
op: '=',
value: 0,
},
],
legend: `{{${k8sNodeNameKey}}}`,
legend: `{{${INFRA_MONITORING_ATTR_KEYS.K8S_NODE_NAME}}}`,
limit: null,
orderBy: [],
queryName: 'A',
@@ -791,7 +702,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_deployment_available--float64--Gauge--true',
key: k8sDeploymentAvailableKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_AVAILABLE,
type: 'Gauge',
},
aggregateOperator: 'latest',
@@ -805,7 +716,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -819,13 +730,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -843,7 +754,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_deployment_desired--float64--Gauge--true',
key: k8sDeploymentDesiredKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_DESIRED,
type: 'Gauge',
},
aggregateOperator: 'latest',
@@ -857,7 +768,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -871,13 +782,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -941,7 +852,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_statefulset_current_pods--float64--Gauge--true',
key: k8sStatefulsetCurrentPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_CURRENT_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -955,7 +866,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -969,13 +880,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_statefulset_name--string--tag--false',
key: k8sStatefulsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -993,7 +904,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_statefulset_desired_pods--float64--Gauge--true',
key: k8sStatefulsetDesiredPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_DESIRED_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1007,7 +918,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1021,13 +932,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_statefulset_name--string--tag--false',
key: k8sStatefulsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1045,7 +956,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_statefulset_ready_pods--float64--Gauge--true',
key: k8sStatefulsetReadyPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_READY_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1059,7 +970,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1073,13 +984,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_statefulset_name--string--tag--false',
key: k8sStatefulsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1097,7 +1008,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_statefulset_updated_pods--float64--Gauge--true',
key: k8sStatefulsetUpdatedPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_UPDATED_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1111,7 +1022,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1125,13 +1036,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_statefulset_name--string--tag--false',
key: k8sStatefulsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_STATEFULSET_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1219,7 +1130,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_daemonset_current_scheduled_nodes--float64--Gauge--true',
key: k8sDaemonsetCurrentScheduledNodesKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_CURRENT_SCHEDULED_NODES,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -1233,7 +1144,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1247,7 +1158,7 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
],
@@ -1265,7 +1176,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_daemonset_desired_scheduled_nodes--float64--Gauge--true',
key: k8sDaemonsetDesiredScheduledNodesKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_DESIRED_SCHEDULED_NODES,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -1279,7 +1190,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1293,7 +1204,7 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
],
@@ -1311,7 +1222,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_daemonset_ready_nodes--float64--Gauge--true',
key: k8sDaemonsetReadyNodesKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_READY_NODES,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -1325,7 +1236,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1339,7 +1250,7 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonsetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
],
@@ -1415,7 +1326,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_active_pods--float64--Gauge--true',
key: k8sJobActivePodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_ACTIVE_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1429,7 +1340,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1443,13 +1354,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
key: k8sJobNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1467,7 +1378,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_successful_pods--float64--Gauge--true',
key: k8sJobSuccessfulPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_SUCCESSFUL_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1481,7 +1392,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1495,13 +1406,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
key: k8sJobNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1519,7 +1430,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_failed_pods--float64--Gauge--true',
key: k8sJobFailedPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_FAILED_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1533,7 +1444,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1547,13 +1458,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
key: k8sJobNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],
@@ -1571,7 +1482,7 @@ export const getClusterMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_job_desired_successful_pods--float64--Gauge--true',
key: k8sJobDesiredSuccessfulPodsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_DESIRED_SUCCESSFUL_PODS,
type: 'Gauge',
},
aggregateOperator: 'max',
@@ -1585,7 +1496,7 @@ export const getClusterMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -1599,13 +1510,13 @@ export const getClusterMetricsQueryPayload = (
{
dataType: DataTypes.String,
id: 'k8s_job_name--string--tag--false',
key: k8sJobNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_JOB_NAME,
type: 'tag',
},
{
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
],

View File

@@ -0,0 +1,138 @@
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listClusters } from 'api/generated/services/inframonitoring';
import {
InframonitoringtypesClusterRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { InfraMonitoringEvents } from 'constants/events';
import { K8sEntityConfig } from '../Base/entity.config.types';
import { K8sBaseFilters, K8sDetailsFilters } from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import {
clusterWidgetInfo,
getClusterMetricsQueryPayload,
k8sClusterDetailsCountsConfig,
k8sClusterDetailsMetadataConfig,
k8sClusterGetCountsFilterExpression,
k8sClusterGetEntityName,
k8sClusterGetSelectedItemExpression,
k8sClusterInitialEventsExpression,
k8sClusterInitialLogTracesExpression,
} from './constants';
import {
getK8sClusterItemKey,
getK8sClusterRowKey,
k8sClustersColumnsConfig,
} from './table.config';
async function fetchListData(
filters: K8sBaseFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<InframonitoringtypesClusterRecordDTO>['list']['fetchListData']
> {
try {
const response = await listClusters(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesClusterRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
async function fetchEntityData(
filters: K8sDetailsFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<InframonitoringtypesClusterRecordDTO>['details']['fetchEntityData']
> {
try {
const response = await listClusters(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
export const clusterEntityConfig: K8sEntityConfig<InframonitoringtypesClusterRecordDTO> =
{
list: {
entity: InfraMonitoringEntity.CLUSTERS,
eventCategory: InfraMonitoringEvents.Cluster,
tableColumns: k8sClustersColumnsConfig,
fetchListData,
getRowKey: getK8sClusterRowKey,
getItemKey: getK8sClusterItemKey,
detailsQueryKeyPrefix: 'cluster',
},
details: {
category: InfraMonitoringEntity.CLUSTERS,
eventCategory: InfraMonitoringEvents.Cluster,
queryKeyPrefix: 'cluster',
getSelectedItemExpression: k8sClusterGetSelectedItemExpression,
fetchEntityData,
getEntityName: k8sClusterGetEntityName,
getInitialLogTracesExpression: k8sClusterInitialLogTracesExpression,
getInitialEventsExpression: k8sClusterInitialEventsExpression,
metadataConfig: k8sClusterDetailsMetadataConfig,
entityWidgetInfo: clusterWidgetInfo,
getEntityQueryPayload: getClusterMetricsQueryPayload,
countsConfig: k8sClusterDetailsCountsConfig,
getCountsFilterExpression: k8sClusterGetCountsFilterExpression,
},
};

View File

@@ -93,13 +93,14 @@ export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
row.nodeCountsByReadiness,
width: { min: 180 },
enableSort: false,
cell: ({ row }): React.ReactNode => {
cell: ({ row, rowId }): React.ReactNode => {
if (!row.nodeCountsByReadiness) {
return <TanStackTable.Text>-</TanStackTable.Text>;
}
return (
<GroupedStatusCounts
rowId={rowId}
items={[
{
value: row.nodeCountsByReadiness.ready,
@@ -129,13 +130,16 @@ export const k8sClustersColumnsConfig: ClusterTableColumnConfig[] = [
row.podCountsByStatus,
width: { min: 250 },
enableSort: false,
cell: ({ row }): React.ReactNode => {
cell: ({ row, rowId }): React.ReactNode => {
const podCountsByStatus = row.podCountsByStatus;
if (!podCountsByStatus) {
return <TanStackTable.Text>-</TanStackTable.Text>;
}
return (
<GroupedStatusCounts items={getPodStatusItems(row.podCountsByStatus)} />
<GroupedStatusCounts
rowId={rowId}
items={getPodStatusItems(row.podCountsByStatus)}
/>
);
},
},

View File

@@ -1,158 +0,0 @@
import { useCallback, useMemo } from 'react';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listDaemonSets } from 'api/generated/services/inframonitoring';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import {
InframonitoringtypesDaemonSetRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { InfraMonitoringEvents } from 'constants/events';
import APIError from 'types/api/error';
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
import { K8sBaseList } from '../Base/K8sBaseList';
import { K8sBaseFilters } from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import { SelectedItemParams } from '../hooks';
import {
daemonSetWidgetInfo,
getDaemonSetMetricsQueryPayload,
getDaemonSetPodMetricsQueryPayload,
k8sDaemonSetDetailsMetadataConfig,
k8sDaemonSetGetEntityName,
k8sDaemonSetGetSelectedItemExpression,
k8sDaemonSetInitialEventsExpression,
k8sDaemonSetInitialLogTracesExpression,
} from './constants';
import {
getK8sDaemonSetItemKey,
getK8sDaemonSetRowKey,
k8sDaemonSetsColumnsConfig,
} from './table.config';
import { createPodMetricsTab } from 'container/InfraMonitoringK8sV2/EntityDetailsUtils/createPodMetricsTab';
function K8sDaemonSetsList({
controlListPrefix,
}: {
controlListPrefix?: React.ReactNode;
}): JSX.Element {
const fetchListData = useCallback(
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
try {
const response = await listDaemonSets(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesDaemonSetRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
const fetchEntityData = useCallback(
async (
filters: K8sDetailsFilters,
signal?: AbortSignal,
): Promise<{
data: InframonitoringtypesDaemonSetRecordDTO | null;
error?: APIError | null;
}> => {
try {
const response = await listDaemonSets(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
const customTabs = useMemo(
() => [
createPodMetricsTab<InframonitoringtypesDaemonSetRecordDTO>({
getQueryPayload: getDaemonSetPodMetricsQueryPayload,
category: InfraMonitoringEntity.DAEMONSETS,
queryKey: 'daemonSetPodMetrics',
}),
],
[],
);
return (
<>
<K8sBaseList<InframonitoringtypesDaemonSetRecordDTO, SelectedItemParams>
controlListPrefix={controlListPrefix}
entity={InfraMonitoringEntity.DAEMONSETS}
tableColumns={k8sDaemonSetsColumnsConfig}
fetchListData={fetchListData}
getRowKey={getK8sDaemonSetRowKey}
getItemKey={getK8sDaemonSetItemKey}
eventCategory={InfraMonitoringEvents.DaemonSet}
/>
<K8sBaseDetails<InframonitoringtypesDaemonSetRecordDTO>
category={InfraMonitoringEntity.DAEMONSETS}
eventCategory={InfraMonitoringEvents.DaemonSet}
getSelectedItemExpression={k8sDaemonSetGetSelectedItemExpression}
fetchEntityData={fetchEntityData}
getEntityName={k8sDaemonSetGetEntityName}
getInitialLogTracesExpression={k8sDaemonSetInitialLogTracesExpression}
getInitialEventsExpression={k8sDaemonSetInitialEventsExpression}
metadataConfig={k8sDaemonSetDetailsMetadataConfig}
entityWidgetInfo={daemonSetWidgetInfo}
getEntityQueryPayload={getDaemonSetMetricsQueryPayload}
queryKeyPrefix="daemonset"
customTabs={customTabs}
/>
</>
);
}
export default K8sDaemonSetsList;

View File

@@ -100,54 +100,7 @@ export const getDaemonSetMetricsQueryPayload = (
daemonSet: InframonitoringtypesDaemonSetRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sPodCpuUtilizationKey = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
const k8sContainerCpuRequestKey = dotMetricsEnabled
? 'k8s.container.cpu_request'
: 'k8s_container_cpu_request';
const k8sContainerCpuLimitKey = dotMetricsEnabled
? 'k8s.container.cpu_limit'
: 'k8s_container_cpu_limit';
const k8sPodMemoryUsageKey = dotMetricsEnabled
? 'k8s.pod.memory.usage'
: 'k8s_pod_memory_usage';
const k8sContainerMemoryRequestKey = dotMetricsEnabled
? 'k8s.container.memory_request'
: 'k8s_container_memory_request';
const k8sContainerMemoryLimitKey = dotMetricsEnabled
? 'k8s.container.memory_limit'
: 'k8s_container_memory_limit';
const k8sPodNetworkIoKey = dotMetricsEnabled
? 'k8s.pod.network.io'
: 'k8s_pod_network_io';
const k8sPodNetworkErrorsKey = dotMetricsEnabled
? 'k8s.pod.network.errors'
: 'k8s_pod_network_errors';
const k8sDaemonSetNameKey = dotMetricsEnabled
? 'k8s.daemonset.name'
: 'k8s_daemonset_name';
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
const k8sNamespaceNameKey = dotMetricsEnabled
? 'k8s.namespace.name'
: 'k8s_namespace_name';
const k8sClusterNameKey = dotMetricsEnabled
? 'k8s.cluster.name'
: 'k8s_cluster_name';
const clusterName =
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] ?? '';
const namespaceName =
@@ -159,7 +112,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -170,7 +123,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
op: '=',
@@ -189,7 +142,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
key: k8sPodCpuUtilizationKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -203,7 +156,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonSetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
op: '=',
@@ -231,7 +184,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_cpu_request--float64--Gauge--true',
key: k8sContainerCpuRequestKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_CPU_REQUEST,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -245,7 +198,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -273,7 +226,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_cpu_limit--float64--Gauge--true',
key: k8sContainerCpuLimitKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_CPU_LIMIT,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -287,7 +240,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -349,7 +302,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
key: k8sPodMemoryUsageKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_MEMORY_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -363,7 +316,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonSetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
op: '=',
@@ -391,7 +344,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_memory_request--float64--Gauge--true',
key: k8sContainerMemoryRequestKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_MEMORY_REQUEST,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -405,7 +358,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -433,7 +386,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_memory_limit--float64--Gauge--true',
key: k8sContainerMemoryLimitKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_MEMORY_LIMIT,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -447,7 +400,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -509,7 +462,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_io--float64--Sum--true',
key: k8sPodNetworkIoKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NETWORK_IO,
type: 'Sum',
},
aggregateOperator: 'rate',
@@ -523,7 +476,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonSetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
op: '=',
@@ -598,7 +551,7 @@ export const getDaemonSetMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_errors--float64--Sum--true',
key: k8sPodNetworkErrorsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NETWORK_ERRORS,
type: 'Sum',
},
aggregateOperator: 'increase',
@@ -612,7 +565,7 @@ export const getDaemonSetMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_daemonset_name--string--tag--false',
key: k8sDaemonSetNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
type: 'tag',
},
op: '=',
@@ -684,15 +637,10 @@ export const getDaemonSetPodMetricsQueryPayload = (
daemonSet: InframonitoringtypesDaemonSetRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sDaemonSetNameKey = dotMetricsEnabled
? 'k8s.daemonset.name'
: 'k8s_daemonset_name';
return getPodUtilizationByPodQueryPayloads(
{
workloadNameKey: k8sDaemonSetNameKey,
workloadNameKey: INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME,
workloadNameValue:
daemonSet.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DAEMONSET_NAME] ?? '',
clusterName:
@@ -702,6 +650,5 @@ export const getDaemonSetPodMetricsQueryPayload = (
},
start,
end,
dotMetricsEnabled,
);
};

View File

@@ -0,0 +1,153 @@
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listDaemonSets } from 'api/generated/services/inframonitoring';
import {
InframonitoringtypesDaemonSetRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { InfraMonitoringEvents } from 'constants/events';
import { K8sEntityConfig } from '../Base/entity.config.types';
import { K8sBaseFilters, K8sDetailsFilters } from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import { createPodMetricsTab } from '../EntityDetailsUtils/createPodMetricsTab';
import { SelectedItemParams } from '../hooks';
import {
daemonSetWidgetInfo,
getDaemonSetMetricsQueryPayload,
getDaemonSetPodMetricsQueryPayload,
k8sDaemonSetDetailsMetadataConfig,
k8sDaemonSetGetEntityName,
k8sDaemonSetGetSelectedItemExpression,
k8sDaemonSetInitialEventsExpression,
k8sDaemonSetInitialLogTracesExpression,
} from './constants';
import {
getK8sDaemonSetItemKey,
getK8sDaemonSetRowKey,
k8sDaemonSetsColumnsConfig,
} from './table.config';
async function fetchListData(
filters: K8sBaseFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<
InframonitoringtypesDaemonSetRecordDTO,
SelectedItemParams
>['list']['fetchListData']
> {
try {
const response = await listDaemonSets(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesDaemonSetRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
async function fetchEntityData(
filters: K8sDetailsFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<
InframonitoringtypesDaemonSetRecordDTO,
SelectedItemParams
>['details']['fetchEntityData']
> {
try {
const response = await listDaemonSets(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
export const daemonSetEntityConfig: K8sEntityConfig<
InframonitoringtypesDaemonSetRecordDTO,
SelectedItemParams
> = {
list: {
entity: InfraMonitoringEntity.DAEMONSETS,
eventCategory: InfraMonitoringEvents.DaemonSet,
tableColumns: k8sDaemonSetsColumnsConfig,
fetchListData,
getRowKey: getK8sDaemonSetRowKey,
getItemKey: getK8sDaemonSetItemKey,
detailsQueryKeyPrefix: 'daemonset',
},
details: {
category: InfraMonitoringEntity.DAEMONSETS,
eventCategory: InfraMonitoringEvents.DaemonSet,
queryKeyPrefix: 'daemonset',
getSelectedItemExpression: k8sDaemonSetGetSelectedItemExpression,
fetchEntityData,
getEntityName: k8sDaemonSetGetEntityName,
getInitialLogTracesExpression: k8sDaemonSetInitialLogTracesExpression,
getInitialEventsExpression: k8sDaemonSetInitialEventsExpression,
metadataConfig: k8sDaemonSetDetailsMetadataConfig,
entityWidgetInfo: daemonSetWidgetInfo,
getEntityQueryPayload: getDaemonSetMetricsQueryPayload,
customTabs: [
createPodMetricsTab<InframonitoringtypesDaemonSetRecordDTO>({
getQueryPayload: getDaemonSetPodMetricsQueryPayload,
category: InfraMonitoringEntity.DAEMONSETS,
queryKey: 'daemonSetPodMetrics',
docBasePath: '/infrastructure-monitoring/kubernetes/daemonsets/',
}),
],
},
};

View File

@@ -121,12 +121,17 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
width: { min: 250 },
enableSort: false,
enableResize: true,
cell: ({ row }): React.ReactNode => {
cell: ({ row, rowId }): React.ReactNode => {
const podCountsByStatus = row.podCountsByStatus;
if (!podCountsByStatus) {
return <TanStackTable.Text>-</TanStackTable.Text>;
}
return <GroupedStatusCounts items={getPodStatusItems(podCountsByStatus)} />;
return (
<GroupedStatusCounts
rowId={rowId}
items={getPodStatusItems(podCountsByStatus)}
/>
);
},
},
{
@@ -140,8 +145,9 @@ export const k8sDaemonSetsColumnsConfig: DaemonSetTableColumnConfig[] = [
width: { min: 210 },
enableSort: false,
enableResize: true,
cell: ({ row }): React.ReactNode => (
cell: ({ row, rowId }): React.ReactNode => (
<GroupedStatusCounts
rowId={rowId}
items={[
{
value: row.readyNodes,

View File

@@ -1,164 +0,0 @@
import { useCallback, useMemo } from 'react';
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listDeployments } from 'api/generated/services/inframonitoring';
import { RenderErrorResponseDTO } from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import {
InframonitoringtypesDeploymentRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
} from 'api/generated/services/sigNoz.schemas';
import { InfraMonitoringEvents } from 'constants/events';
import APIError from 'types/api/error';
import K8sBaseDetails, { K8sDetailsFilters } from '../Base/K8sBaseDetails';
import { K8sBaseList } from '../Base/K8sBaseList';
import { K8sBaseFilters } from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import { SelectedItemParams } from '../hooks';
import {
deploymentWidgetInfo,
getDeploymentMetricsQueryPayload,
getDeploymentPodMetricsQueryPayload,
k8sDeploymentDetailsMetadataConfig,
k8sDeploymentGetEntityName,
k8sDeploymentGetSelectedItemExpression,
k8sDeploymentInitialEventsExpression,
k8sDeploymentInitialLogTracesExpression,
} from './constants';
import {
getK8sDeploymentItemKey,
getK8sDeploymentRowKey,
k8sDeploymentsColumnsConfig,
} from './table.config';
import { createPodMetricsTab } from 'container/InfraMonitoringK8sV2/EntityDetailsUtils/createPodMetricsTab';
function K8sDeploymentsList({
controlListPrefix,
}: {
controlListPrefix?: React.ReactNode;
}): JSX.Element {
const fetchListData = useCallback(
async (filters: K8sBaseFilters, signal?: AbortSignal) => {
try {
const response = await listDeployments(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesDeploymentRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
const fetchEntityData = useCallback(
async (
filters: K8sDetailsFilters,
signal?: AbortSignal,
): Promise<{
data: InframonitoringtypesDeploymentRecordDTO | null;
error?: APIError | null;
}> => {
try {
const response = await listDeployments(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
},
[],
);
const customTabs = useMemo(
() => [
createPodMetricsTab<InframonitoringtypesDeploymentRecordDTO>({
getQueryPayload: getDeploymentPodMetricsQueryPayload,
category: InfraMonitoringEntity.DEPLOYMENTS,
queryKey: 'deploymentPodMetrics',
}),
],
[],
);
return (
<>
<K8sBaseList<InframonitoringtypesDeploymentRecordDTO, SelectedItemParams>
controlListPrefix={controlListPrefix}
entity={InfraMonitoringEntity.DEPLOYMENTS}
tableColumns={k8sDeploymentsColumnsConfig}
fetchListData={fetchListData}
getRowKey={getK8sDeploymentRowKey}
getItemKey={getK8sDeploymentItemKey}
eventCategory={InfraMonitoringEvents.Deployment}
/>
<K8sBaseDetails<InframonitoringtypesDeploymentRecordDTO>
category={InfraMonitoringEntity.DEPLOYMENTS}
eventCategory={InfraMonitoringEvents.Deployment}
getSelectedItemExpression={k8sDeploymentGetSelectedItemExpression}
fetchEntityData={fetchEntityData}
getEntityName={k8sDeploymentGetEntityName}
getInitialLogTracesExpression={k8sDeploymentInitialLogTracesExpression}
getInitialEventsExpression={k8sDeploymentInitialEventsExpression}
metadataConfig={k8sDeploymentDetailsMetadataConfig}
entityWidgetInfo={deploymentWidgetInfo}
getEntityQueryPayload={getDeploymentMetricsQueryPayload}
queryKeyPrefix="deployment"
customTabs={customTabs}
/>
</>
);
}
export default K8sDeploymentsList;

View File

@@ -100,52 +100,7 @@ export const getDeploymentMetricsQueryPayload = (
deployment: InframonitoringtypesDeploymentRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sPodCpuUtilizationKey = dotMetricsEnabled
? 'k8s.pod.cpu.usage'
: 'k8s_pod_cpu_usage';
const k8sContainerCpuRequestKey = dotMetricsEnabled
? 'k8s.container.cpu_request'
: 'k8s_container_cpu_request';
const k8sContainerCpuLimitKey = dotMetricsEnabled
? 'k8s.container.cpu_limit'
: 'k8s_container_cpu_limit';
const k8sPodMemoryUsageKey = dotMetricsEnabled
? 'k8s.pod.memory.usage'
: 'k8s_pod_memory_usage';
const k8sContainerMemoryRequestKey = dotMetricsEnabled
? 'k8s.container.memory_request'
: 'k8s_container_memory_request';
const k8sContainerMemoryLimitKey = dotMetricsEnabled
? 'k8s.container.memory_limit'
: 'k8s_container_memory_limit';
const k8sPodNetworkIoKey = dotMetricsEnabled
? 'k8s.pod.network.io'
: 'k8s_pod_network_io';
const k8sPodNetworkErrorsKey = dotMetricsEnabled
? 'k8s.pod.network.errors'
: 'k8s_pod_network_errors';
const k8sDeploymentNameKey = dotMetricsEnabled
? 'k8s.deployment.name'
: 'k8s_deployment_name';
const k8sPodNameKey = dotMetricsEnabled ? 'k8s.pod.name' : 'k8s_pod_name';
const k8sClusterNameKey = dotMetricsEnabled
? 'k8s.cluster.name'
: 'k8s_cluster_name';
const k8sNamespaceNameKey = dotMetricsEnabled
? 'k8s.namespace.name'
: 'k8s_namespace_name';
const clusterName =
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME] ?? '';
@@ -158,7 +113,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_cluster_name--string--tag--false',
key: k8sClusterNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CLUSTER_NAME,
type: 'tag',
},
op: '=',
@@ -169,7 +124,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_namespace_name--string--tag--false',
key: k8sNamespaceNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_NAMESPACE_NAME,
type: 'tag',
},
op: '=',
@@ -188,7 +143,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_cpu_usage--float64--Gauge--true',
key: k8sPodCpuUtilizationKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_CPU_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -202,7 +157,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
op: '=',
@@ -230,7 +185,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_cpu_request--float64--Gauge--true',
key: k8sContainerCpuRequestKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_CPU_REQUEST,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -244,7 +199,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -272,7 +227,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_cpu_limit--float64--Gauge--true',
key: k8sContainerCpuLimitKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_CPU_LIMIT,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -286,7 +241,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -348,7 +303,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_memory_usage--float64--Gauge--true',
key: k8sPodMemoryUsageKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_MEMORY_USAGE,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -362,7 +317,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
op: '=',
@@ -390,7 +345,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_memory_request--float64--Gauge--true',
key: k8sContainerMemoryRequestKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_MEMORY_REQUEST,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -404,7 +359,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -432,7 +387,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_container_memory_limit--float64--Gauge--true',
key: k8sContainerMemoryLimitKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_CONTAINER_MEMORY_LIMIT,
type: 'Gauge',
},
aggregateOperator: 'avg',
@@ -446,7 +401,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_pod_name--string--tag--false',
key: k8sPodNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NAME,
type: 'tag',
},
op: 'contains',
@@ -508,7 +463,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_io--float64--Sum--true',
key: k8sPodNetworkIoKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NETWORK_IO,
type: 'Sum',
},
aggregateOperator: 'rate',
@@ -522,7 +477,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
op: '=',
@@ -597,7 +552,7 @@ export const getDeploymentMetricsQueryPayload = (
aggregateAttribute: {
dataType: DataTypes.Float64,
id: 'k8s_pod_network_errors--float64--Sum--true',
key: k8sPodNetworkErrorsKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_POD_NETWORK_ERRORS,
type: 'Sum',
},
aggregateOperator: 'increase',
@@ -611,7 +566,7 @@ export const getDeploymentMetricsQueryPayload = (
key: {
dataType: DataTypes.String,
id: 'k8s_deployment_name--string--tag--false',
key: k8sDeploymentNameKey,
key: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
type: 'tag',
},
op: '=',
@@ -683,15 +638,10 @@ export const getDeploymentPodMetricsQueryPayload = (
deployment: InframonitoringtypesDeploymentRecordDTO,
start: number,
end: number,
dotMetricsEnabled: boolean,
): GetQueryResultsProps[] => {
const k8sDeploymentNameKey = dotMetricsEnabled
? 'k8s.deployment.name'
: 'k8s_deployment_name';
return getPodUtilizationByPodQueryPayloads(
): GetQueryResultsProps[] =>
getPodUtilizationByPodQueryPayloads(
{
workloadNameKey: k8sDeploymentNameKey,
workloadNameKey: INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME,
workloadNameValue:
deployment.meta?.[INFRA_MONITORING_ATTR_KEYS.K8S_DEPLOYMENT_NAME] ?? '',
clusterName:
@@ -701,6 +651,4 @@ export const getDeploymentPodMetricsQueryPayload = (
},
start,
end,
dotMetricsEnabled,
);
};

View File

@@ -0,0 +1,161 @@
import { convertToApiError } from 'api/ErrorResponseHandlerForGeneratedAPIs';
import { listDeployments } from 'api/generated/services/inframonitoring';
import {
InframonitoringtypesDeploymentRecordDTO,
InframonitoringtypesResponseTypeDTO,
Querybuildertypesv5OrderDirectionDTO,
RenderErrorResponseDTO,
} from 'api/generated/services/sigNoz.schemas';
import { AxiosError } from 'axios';
import { InfraMonitoringEvents } from 'constants/events';
import { K8sEntityConfig } from '../Base/entity.config.types';
import {
K8sBaseFilters,
K8sDetailsCustomTab,
K8sDetailsFilters,
} from '../Base/types';
import { InfraMonitoringEntity } from '../constants';
import { createPodMetricsTab } from '../EntityDetailsUtils/createPodMetricsTab';
import { SelectedItemParams } from '../hooks';
import {
deploymentWidgetInfo,
getDeploymentMetricsQueryPayload,
getDeploymentPodMetricsQueryPayload,
k8sDeploymentDetailsMetadataConfig,
k8sDeploymentGetEntityName,
k8sDeploymentGetSelectedItemExpression,
k8sDeploymentInitialEventsExpression,
k8sDeploymentInitialLogTracesExpression,
} from './constants';
import {
getK8sDeploymentItemKey,
getK8sDeploymentRowKey,
k8sDeploymentsColumnsConfig,
} from './table.config';
async function fetchListData(
filters: K8sBaseFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<
InframonitoringtypesDeploymentRecordDTO,
SelectedItemParams
>['list']['fetchListData']
> {
try {
const response = await listDeployments(
{
filter: { expression: filters.filter.expression },
groupBy: filters.groupBy?.map((g) => ({ name: g.name })),
offset: filters.offset,
limit: filters.limit ?? 10,
start: filters.start,
end: filters.end,
orderBy: filters.orderBy
? {
key: { name: filters.orderBy.key.name },
direction:
filters.orderBy.direction === 'asc'
? Querybuildertypesv5OrderDirectionDTO.asc
: Querybuildertypesv5OrderDirectionDTO.desc,
}
: undefined,
},
signal,
);
const data = response.data;
return {
type:
data.type === InframonitoringtypesResponseTypeDTO.grouped_list
? ('grouped_list' as const)
: ('list' as const),
records: data.records,
total: data.total,
endTimeBeforeRetention: data.endTimeBeforeRetention,
warning: data.warning,
};
} catch (error) {
return {
type: 'list' as const,
records: [] as InframonitoringtypesDeploymentRecordDTO[],
total: 0,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
async function fetchEntityData(
filters: K8sDetailsFilters,
signal?: AbortSignal,
): ReturnType<
K8sEntityConfig<
InframonitoringtypesDeploymentRecordDTO,
SelectedItemParams
>['details']['fetchEntityData']
> {
try {
const response = await listDeployments(
{
filter: { expression: filters.filter.expression },
start: filters.start,
end: filters.end,
limit: 1,
offset: 0,
},
signal,
);
return {
data: response.data.records.length > 0 ? response.data.records[0] : null,
};
} catch (error) {
return {
data: null,
error:
convertToApiError(error as AxiosError<RenderErrorResponseDTO>) ?? null,
};
}
}
export function createCustomTabs(): K8sDetailsCustomTab<InframonitoringtypesDeploymentRecordDTO>[] {
return [
createPodMetricsTab<InframonitoringtypesDeploymentRecordDTO>({
getQueryPayload: getDeploymentPodMetricsQueryPayload,
category: InfraMonitoringEntity.DEPLOYMENTS,
queryKey: 'deploymentPodMetrics',
docBasePath: '/infrastructure-monitoring/kubernetes/deployments/',
}),
];
}
export const deploymentEntityConfig: K8sEntityConfig<
InframonitoringtypesDeploymentRecordDTO,
SelectedItemParams
> = {
list: {
entity: InfraMonitoringEntity.DEPLOYMENTS,
eventCategory: InfraMonitoringEvents.Deployment,
tableColumns: k8sDeploymentsColumnsConfig,
fetchListData,
getRowKey: getK8sDeploymentRowKey,
getItemKey: getK8sDeploymentItemKey,
detailsQueryKeyPrefix: 'deployment',
},
details: {
category: InfraMonitoringEntity.DEPLOYMENTS,
eventCategory: InfraMonitoringEvents.Deployment,
queryKeyPrefix: 'deployment',
getSelectedItemExpression: k8sDeploymentGetSelectedItemExpression,
fetchEntityData,
getEntityName: k8sDeploymentGetEntityName,
getInitialLogTracesExpression: k8sDeploymentInitialLogTracesExpression,
getInitialEventsExpression: k8sDeploymentInitialEventsExpression,
metadataConfig: k8sDeploymentDetailsMetadataConfig,
entityWidgetInfo: deploymentWidgetInfo,
getEntityQueryPayload: getDeploymentMetricsQueryPayload,
customTabs: createCustomTabs(),
},
};

View File

@@ -118,12 +118,17 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
width: { min: 250 },
enableSort: false,
enableResize: true,
cell: ({ row }): React.ReactNode => {
cell: ({ row, rowId }): React.ReactNode => {
const podCountsByStatus = row.podCountsByStatus;
if (!podCountsByStatus) {
return <TanStackTable.Text>-</TanStackTable.Text>;
}
return <GroupedStatusCounts items={getPodStatusItems(podCountsByStatus)} />;
return (
<GroupedStatusCounts
rowId={rowId}
items={getPodStatusItems(podCountsByStatus)}
/>
);
},
},
{
@@ -137,8 +142,9 @@ export const k8sDeploymentsColumnsConfig: TableColumnDef<InframonitoringtypesDep
width: { min: 180 },
enableSort: false,
enableResize: true,
cell: ({ row }): React.ReactNode => (
cell: ({ row, rowId }): React.ReactNode => (
<GroupedStatusCounts
rowId={rowId}
items={[
{
value: row.availablePods,

View File

@@ -0,0 +1,78 @@
import { useCallback } from 'react';
import { Undo } from '@signozhq/icons';
import { Button } from '@signozhq/ui/button';
import { TooltipSimple } from '@signozhq/ui/tooltip';
import logEvent from 'api/common/logEvent';
import { InfraMonitoringEvents } from 'constants/events';
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { useEntityDetailsTime } from './useEntityDetailsTime';
interface EntityDateTimeSelectorProps {
eventEntity: string;
category: InfraMonitoringEntity;
view: string;
}
function EntityDateTimeSelector({
eventEntity,
category,
view,
}: EntityDateTimeSelectorProps): JSX.Element {
const {
timeRange,
selectedInterval,
handleTimeChange,
handleResetToParentTime,
hasTimeChanged,
} = useEntityDetailsTime();
const onTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
void logEvent(InfraMonitoringEvents.TimeUpdated, {
entity: eventEntity,
page: InfraMonitoringEvents.DetailedPage,
category,
view,
interval,
});
handleTimeChange(interval, dateTimeRange);
},
[category, view, eventEntity, handleTimeChange],
);
return (
<div className="entity-date-time-selector">
{hasTimeChanged && (
<TooltipSimple title="Reset to list time" side="bottom">
<Button
variant="outlined"
color="secondary"
onClick={handleResetToParentTime}
data-testid="reset-to-list-time-button"
prefix={<Undo size={14} />}
>
Reset
</Button>
</TooltipSimple>
)}
<DateTimeSelectionV2
showAutoRefresh
showRefreshText={false}
hideShareModal
isModalTimeSelection
onTimeChange={onTimeChange}
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
/>
</div>
);
}
export default EntityDateTimeSelector;

View File

@@ -0,0 +1,95 @@
import { useCallback, useMemo } from 'react';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import {
createCustomTimeRange,
isCustomTimeRange,
NANO_SECOND_MULTIPLIER,
useGlobalTime,
} from 'store/globalTime';
export interface EntityDetailsTimeRange {
/** Unix timestamp in seconds — the unit API payload builders expect. Multiply by 1000 for ms-based UI (modals, URL params). */
startTime: number;
/** Unix timestamp in seconds — the unit API payload builders expect. Multiply by 1000 for ms-based UI (modals, URL params). */
endTime: number;
}
export interface UseEntityDetailsTimeResult {
/** Time range in seconds */
timeRange: EntityDetailsTimeRange;
selectedInterval: Time;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
handleResetToParentTime: () => boolean;
canResetToParent: boolean;
hasTimeChanged: boolean;
}
export function useEntityDetailsTime(): UseEntityDetailsTimeResult {
const selectedTime = useGlobalTime((s) => s.selectedTime);
const lastComputedMinMax = useGlobalTime((s) => s.lastComputedMinMax);
const setSelectedTime = useGlobalTime((s) => s.setSelectedTime);
const handleResetToParentTime = useGlobalTime((s) => s.resetToParentTime);
const parentStore = useGlobalTime((s) => s.parentStore);
const canResetToParent = !!parentStore;
const hasTimeChanged = useMemo(() => {
if (!parentStore) {
return false;
}
return selectedTime !== parentStore.getState().selectedTime;
}, [parentStore, selectedTime]);
const timeRange = useMemo<EntityDetailsTimeRange>(
() => ({
startTime: Math.floor(
lastComputedMinMax.minTime / NANO_SECOND_MULTIPLIER / 1000,
),
endTime: Math.floor(
lastComputedMinMax.maxTime / NANO_SECOND_MULTIPLIER / 1000,
),
}),
[lastComputedMinMax],
);
const selectedInterval = useMemo<Time>(
() =>
isCustomTimeRange(selectedTime)
? ('custom' as Time)
: (selectedTime as Time),
[selectedTime],
);
const handleTimeChange = useCallback(
(interval: Time | CustomTimeType, dateTimeRange?: [number, number]): void => {
if (interval === 'custom' && dateTimeRange) {
// DateTimeSelector delivers milliseconds; the store keys custom
// ranges by nanoseconds.
setSelectedTime(
createCustomTimeRange(
dateTimeRange[0] * NANO_SECOND_MULTIPLIER,
dateTimeRange[1] * NANO_SECOND_MULTIPLIER,
),
);
} else {
setSelectedTime(interval as Time);
}
},
[setSelectedTime],
);
return {
timeRange,
selectedInterval,
handleTimeChange,
handleResetToParentTime,
canResetToParent,
hasTimeChanged,
};
}

View File

@@ -21,17 +21,14 @@ import Controls from 'container/Controls';
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
import LoadingContainer from 'container/InfraMonitoringK8sV2/LoadingContainer';
import RunQueryBtn from 'container/QueryBuilder/components/RunQueryBtn/RunQueryBtn';
import DateTimeSelectionV2 from 'container/TopNav/DateTimeSelectionV2';
import {
CustomTimeType,
Time,
} from 'container/TopNav/DateTimeSelectionV2/types';
import { ChevronDown, ChevronRight } from '@signozhq/icons';
import { useQueryState } from 'nuqs';
import { DataSource } from 'types/common/queryBuilder';
import { parseAsJsonNoValidate } from 'utils/nuqsParsers';
import { validateQuery } from 'utils/queryValidationUtils';
import EntityDateTimeSelector from '../EntityDateTimeSelector/EntityDateTimeSelector';
import { useEntityDetailsTime } from '../EntityDateTimeSelector/useEntityDetailsTime';
import EntityEmptyState from '../EntityEmptyState/EntityEmptyState';
import EntityError from '../EntityError/EntityError';
import { EventContents } from './EventsContent';
@@ -53,16 +50,7 @@ interface EventDataType {
}
interface Props {
timeRange: {
startTime: number;
endTime: number;
};
isModalTimeSelection: boolean;
handleTimeChange: (
interval: Time | CustomTimeType,
dateTimeRange?: [number, number],
) => void;
selectedInterval: Time;
eventEntity: string;
queryKey: string;
category: InfraMonitoringEntity;
initialExpression: string;
@@ -77,13 +65,11 @@ const handleExpandRow = (record: EventDataType): JSX.Element => (
);
function EntityEventsContent({
timeRange,
isModalTimeSelection,
handleTimeChange,
selectedInterval,
eventEntity,
queryKey,
category,
}: Omit<Props, 'initialExpression'>): JSX.Element {
const { timeRange } = useEntityDetailsTime();
const expression = useExpression();
const inputExpression = useInputExpression();
const userExpression = useUserExpression();
@@ -131,8 +117,8 @@ function EntityEventsContent({
if (validation.isValid) {
querySearchOnRun(newUserExpression || '');
logEvent(InfraMonitoringEvents.FilterApplied, {
entity: InfraMonitoringEvents.K8sEntity,
void logEvent(InfraMonitoringEvents.FilterApplied, {
entity: eventEntity,
page: InfraMonitoringEvents.DetailedPage,
category,
view: InfraMonitoringEvents.EventsView,
@@ -141,7 +127,14 @@ function EntityEventsContent({
refetch();
}
},
[inputExpression, initialExpression, refetch, querySearchOnRun, category],
[
inputExpression,
initialExpression,
refetch,
querySearchOnRun,
category,
eventEntity,
],
);
const queryData = useMemo(
@@ -229,16 +222,10 @@ function EntityEventsContent({
<div className={styles.container}>
<div className={styles.filterContainer}>
<div className={styles.filterContainerTime}>
<DateTimeSelectionV2
showAutoRefresh
showRefreshText={false}
hideShareModal
isModalTimeSelection={isModalTimeSelection}
onTimeChange={handleTimeChange}
defaultRelativeTime="5m"
modalSelectedInterval={selectedInterval}
modalInitialStartTime={timeRange.startTime * 1000}
modalInitialEndTime={timeRange.endTime * 1000}
<EntityDateTimeSelector
eventEntity={eventEntity}
category={category}
view={InfraMonitoringEvents.EventsView}
/>
<RunQueryBtn

View File

@@ -29,25 +29,25 @@ function verifyEntityEventsV5Request({
expect(orderKeys).toContain('timestamp');
}
jest.mock('container/TopNav/DateTimeSelectionV2/index.tsx', () => ({
jest.mock('../../EntityDateTimeSelector/EntityDateTimeSelector', () => ({
__esModule: true,
default: ({
onTimeChange,
}: {
onTimeChange?: (interval: string, dateTimeRange?: [number, number]) => void;
}): JSX.Element => (
<button
type="button"
data-testid="mock-datetime-selection"
onClick={(): void => {
onTimeChange?.('5m');
}}
>
Select Time
</button>
default: (): JSX.Element => (
<div data-testid="mock-datetime-selection">Date Time</div>
),
}));
jest.mock('../../EntityDateTimeSelector/useEntityDetailsTime', () => ({
useEntityDetailsTime: (): {
timeRange: { startTime: number; endTime: number };
selectedInterval: string;
handleTimeChange: jest.Mock;
} => ({
timeRange: { startTime: 1, endTime: 2 },
selectedInterval: '5m',
handleTimeChange: jest.fn(),
}),
}));
describe('EntityEvents', () => {
let capturedQueryRangePayloads: QueryRangePayloadV5[] = [];
@@ -69,10 +69,7 @@ describe('EntityEvents', () => {
searchParams={`${K8S_ENTITY_EVENTS_EXPRESSION_KEY}=k8s.pod.name+%3D+%22x%22`}
>
<EntityEvents
timeRange={{ startTime: 1, endTime: 2 }}
isModalTimeSelection={false}
handleTimeChange={jest.fn()}
selectedInterval="5m"
eventEntity="test"
queryKey="test"
category={InfraMonitoringEntity.PODS}
initialExpression='k8s.pod.name = "x"'

View File

@@ -0,0 +1,81 @@
import { mockFieldsAPIsWithEmptyResponse } from '__tests__/fields_api.util';
import { mockQueryRangeV5WithEventsResponse } from '__tests__/query_range_v5.util';
import { InfraMonitoringEntity } from 'container/InfraMonitoringK8sV2/constants';
import { NuqsTestingAdapter } from 'nuqs/adapters/testing';
import {
createCustomTimeRange,
createGlobalTimeStore,
GlobalTimeContext,
NANO_SECOND_MULTIPLIER,
} from 'store/globalTime';
import { act, render, waitFor } from 'tests/test-utils';
import { QueryRangePayloadV5 } from 'types/api/v5/queryRange';
import EntityEvents from '../EntityEvents';
import { K8S_ENTITY_EVENTS_EXPRESSION_KEY } from '../hooks';
jest.mock('../../EntityDateTimeSelector/EntityDateTimeSelector', () => ({
__esModule: true,
default: (): JSX.Element => (
<div data-testid="mock-datetime-selection">Date Time</div>
),
}));
const START_MS = 1705315200000; // 2024-01-15T10:40:00Z
const END_MS = 1705318800000; // 2024-01-15T11:40:00Z
describe('EntityEvents time range wiring', () => {
let capturedPayloads: QueryRangePayloadV5[] = [];
beforeEach(() => {
capturedPayloads = [];
mockQueryRangeV5WithEventsResponse({
onReceiveRequest: async (req) => {
const body = (await req.json()) as QueryRangePayloadV5;
capturedPayloads.push(body);
return {};
},
});
mockFieldsAPIsWithEmptyResponse();
});
it('should query the V5 API with the global time range converted to milliseconds', async () => {
const store = createGlobalTimeStore();
store
.getState()
.setSelectedTime(
createCustomTimeRange(
START_MS * NANO_SECOND_MULTIPLIER,
END_MS * NANO_SECOND_MULTIPLIER,
),
);
const expression = 'k8s.pod.name = "test-pod"';
act(() => {
render(
<GlobalTimeContext.Provider value={store}>
<NuqsTestingAdapter
searchParams={`${K8S_ENTITY_EVENTS_EXPRESSION_KEY}=${encodeURIComponent(
expression,
)}`}
>
<EntityEvents
eventEntity="test"
queryKey="test"
category={InfraMonitoringEntity.PODS}
initialExpression={expression}
/>
</NuqsTestingAdapter>
</GlobalTimeContext.Provider>,
);
});
await waitFor(() => {
expect(capturedPayloads).toHaveLength(1);
});
expect(capturedPayloads[0].start).toBe(START_MS);
expect(capturedPayloads[0].end).toBe(END_MS);
});
});

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