From 5081c2a3e680b7f59dad9ce1e1cc7a78e0cfe323 Mon Sep 17 00:00:00 2001 From: zbydown Date: Mon, 26 Jan 2026 15:11:40 +0800 Subject: [PATCH] feat: init trace --- app/vmui/Dockerfile-build | 6 + app/vmui/Makefile | 2 +- app/vmui/package-lock.json | 11 + app/vmui/package.json | 6 + app/vmui/packages/jaeger-ui-lite/.babelrc | 16 + app/vmui/packages/jaeger-ui-lite/.eslintrc.js | 20 + app/vmui/packages/jaeger-ui-lite/.gitignore | 9 + .../packages/jaeger-ui-lite/.stylelintrc.json | 21 + app/vmui/packages/jaeger-ui-lite/package.json | 118 + .../jaeger-ui-lite/src/actions/jaeger-api.ts | 56 + .../packages/jaeger-ui-lite/src/api/jaeger.ts | 138 + .../src/components/App/NotFound.tsx | 19 + .../src/components/App/Page.css | 27 + .../src/components/App/Page.tsx | 39 + .../src/components/App/ThemeProvider.tsx | 145 + .../src/components/App/ThemeStorage.ts | 67 + .../src/components/App/ThemeTokenSync.ts | 48 + .../TraceSearchPage/ScrollManager.ts | 242 + .../components/TraceSearchPage/SearchForm.css | 105 + .../components/TraceSearchPage/SearchForm.tsx | 910 ++ .../src/components/TraceSearchPage/Trace.tsx | 356 + .../SpanGraph/CanvasSpanGraph.css | 6 + .../SpanGraph/CanvasSpanGraph.tsx | 33 + .../TracePageHeader/SpanGraph/GraphTicks.css | 4 + .../TracePageHeader/SpanGraph/GraphTicks.tsx | 23 + .../TracePageHeader/SpanGraph/Scrubber.css | 30 + .../TracePageHeader/SpanGraph/Scrubber.tsx | 51 + .../TracePageHeader/SpanGraph/SpanGraph.css | 3 + .../TracePageHeader/SpanGraph/TickLabels.css | 11 + .../TracePageHeader/SpanGraph/TickLabels.tsx | 28 + .../SpanGraph/ViewingLayer.css | 59 + .../SpanGraph/ViewingLayer.tsx | 337 + .../TracePageHeader/SpanGraph/index.tsx | 71 + .../SpanGraph/render-into-canvas.ts | 120 + .../TracePageHeader/TracePageHeader.css | 113 + .../TracePageHeader/TracePageHeader.tsx | 182 + .../TraceSearchPage/TracePageHeader/index.ts | 1 + .../TraceTimelineViewer/ListView/Positions.ts | 180 + .../TraceTimelineViewer/ListView/index.tsx | 463 + .../TraceTimelineViewer/ReferencesButton.css | 20 + .../TraceTimelineViewer/ReferencesButton.tsx | 74 + .../TraceTimelineViewer/SpanBar.css | 134 + .../TraceTimelineViewer/SpanBar.tsx | 194 + .../TraceTimelineViewer/SpanBarRow.css | 184 + .../TraceTimelineViewer/SpanBarRow.tsx | 220 + .../SpanDetail/AccordionAttributes.css | 51 + .../SpanDetail/AccordionAttributes.markers.ts | 1 + .../SpanDetail/AccordionAttributes.tsx | 84 + .../SpanDetail/AccordionEvents.css | 34 + .../SpanDetail/AccordionEvents.tsx | 238 + .../SpanDetail/AccordionLinks.css | 45 + .../SpanDetail/AccordionLinks.tsx | 99 + .../SpanDetail/AccordionText.css | 11 + .../SpanDetail/AccordionText.tsx | 60 + .../SpanDetail/AttributesTable.css | 49 + .../SpanDetail/AttributesTable.tsx | 203 + .../SpanDetail/DetailState.ts | 92 + .../SpanDetail/TextList.css | 20 + .../SpanDetail/TextList.tsx | 24 + .../TraceTimelineViewer/SpanDetail/index.css | 49 + .../TraceTimelineViewer/SpanDetail/index.tsx | 163 + .../TraceTimelineViewer/SpanDetailRow.css | 40 + .../TraceTimelineViewer/SpanDetailRow.tsx | 94 + .../TraceTimelineViewer/SpanTreeOffset.css | 97 + .../TraceTimelineViewer/SpanTreeOffset.tsx | 188 + .../TraceTimelineViewer/Ticks.css | 26 + .../TraceTimelineViewer/Ticks.tsx | 44 + .../TimelineHeaderRow/TimelineCollapser.css | 37 + .../TimelineHeaderRow/TimelineCollapser.tsx | 57 + .../TimelineHeaderRow/TimelineHeaderRow.css | 33 + .../TimelineHeaderRow/TimelineHeaderRow.tsx | 69 + .../TimelineViewingLayer.css | 51 + .../TimelineViewingLayer.tsx | 203 + .../TimelineHeaderRow/index.ts | 1 + .../TraceTimelineViewer/TimelineRow.css | 5 + .../TraceTimelineViewer/TimelineRow.tsx | 37 + .../VirtualizedTraceView.css | 13 + .../VirtualizedTraceView.tsx | 610 + .../TraceTimelineViewer/duck.ts | 317 + .../TraceTimelineViewer/grid.css | 810 ++ .../TraceTimelineViewer/index.css | 77 + .../TraceTimelineViewer/index.tsx | 100 + .../TraceTimelineViewer/utils.ts | 98 + .../src/components/TraceSearchPage/Tween.ts | 100 + .../src/components/TraceSearchPage/index.css | 6 + .../src/components/TraceSearchPage/index.tsx | 614 + .../components/TraceSearchPage/scroll-page.ts | 41 + .../src/components/TraceSearchPage/types.ts | 51 + .../TraceSearchPage/url/ReferenceLink.tsx | 39 + .../components/TraceSearchPage/url/index.ts | 53 + .../common/ActionMenu/ActionsMenu.css | 32 + .../common/ActionMenu/ActionsMenu.tsx | 85 + .../src/components/common/BreakableText.css | 4 + .../src/components/common/BreakableText.tsx | 30 + .../src/components/common/ClickToCopy.tsx | 70 + .../src/components/common/CopyIcon.css | 11 + .../src/components/common/CopyIcon.tsx | 57 + .../common/DetailsCard/DetailList.tsx | 17 + .../common/DetailsCard/DetailTable.tsx | 163 + .../DetailsCard/DetailTableDropdown.css | 49 + .../DetailsCard/DetailTableDropdown.tsx | 119 + .../components/common/DetailsCard/index.css | 57 + .../components/common/DetailsCard/index.tsx | 62 + .../components/common/DetailsCard/types.ts | 28 + .../src/components/common/EmphasizedNode.css | 23 + .../src/components/common/EmphasizedNode.tsx | 31 + .../src/components/common/ErrorMessage.css | 48 + .../src/components/common/ErrorMessage.tsx | 113 + .../src/components/common/ExternalLinks.tsx | 57 + .../common/FilteredList/ListItem.css | 50 + .../common/FilteredList/ListItem.tsx | 72 + .../common/FilteredList/highlightMatches.tsx | 127 + .../components/common/FilteredList/index.css | 49 + .../components/common/FilteredList/index.tsx | 255 + .../src/components/common/LabeledList.css | 15 + .../src/components/common/LabeledList.tsx | 34 + .../components/common/LoadingIndicator.css | 45 + .../components/common/LoadingIndicator.tsx | 29 + .../src/components/common/NewWindowIcon.css | 8 + .../src/components/common/NewWindowIcon.tsx | 14 + .../components/common/SearchableSelect.tsx | 61 + .../src/components/common/TraceId.css | 13 + .../src/components/common/TraceId.tsx | 24 + .../src/components/common/TraceName.css | 3 + .../src/components/common/TraceName.tsx | 47 + .../src/components/common/UiFindInput.tsx | 115 + .../src/components/common/VerticalResizer.css | 93 + .../src/components/common/VerticalResizer.tsx | 123 + .../src/components/common/url.ts | 95 + .../src/components/common/utils.css | 85 + .../src/components/common/vars.css | 267 + .../src/constants/default-config.ts | 134 + .../src/constants/default-version.ts | 5 + .../jaeger-ui-lite/src/constants/index.ts | 19 + .../src/constants/keyboard-keys.ts | 11 + .../src/constants/search-form.ts | 5 + .../jaeger-ui-lite/src/constants/tag-keys.ts | 3 + .../jaeger-ui-lite/src/middlewares/index.ts | 30 + .../src/model/OtelSpanFacade.ts | 190 + .../src/model/OtelTraceFacade.ts | 115 + .../jaeger-ui-lite/src/model/link-patterns.ts | 298 + .../jaeger-ui-lite/src/model/order-by.ts | 5 + .../jaeger-ui-lite/src/model/search.ts | 23 + .../packages/jaeger-ui-lite/src/model/span.ts | 12 + .../jaeger-ui-lite/src/model/trace-viewer.ts | 93 + .../src/model/transform-trace-data.ts | 220 + .../jaeger-ui-lite/src/reducers/config.ts | 9 + .../src/reducers/dependencies.ts | 36 + .../jaeger-ui-lite/src/reducers/embedded.ts | 12 + .../jaeger-ui-lite/src/reducers/index.ts | 16 + .../jaeger-ui-lite/src/reducers/services.ts | 111 + .../jaeger-ui-lite/src/reducers/trace.ts | 170 + .../jaeger-ui-lite/src/site-prefix.ts | 22 + .../packages/jaeger-ui-lite/src/types/TNil.ts | 3 + .../src/types/TTraceDiffState.ts | 9 + .../src/types/TTraceTimeline.ts | 13 + .../jaeger-ui-lite/src/types/api-error.ts | 10 + .../jaeger-ui-lite/src/types/archive.ts | 16 + .../jaeger-ui-lite/src/types/config.ts | 208 + .../jaeger-ui-lite/src/types/critical_path.ts | 21 + .../jaeger-ui-lite/src/types/embedded.ts | 11 + .../src/types/external-modules.d.ts | 17 + .../jaeger-ui-lite/src/types/hyperlink.ts | 4 + .../jaeger-ui-lite/src/types/index.ts | 65 + .../jaeger-ui-lite/src/types/metrics.ts | 143 + .../packages/jaeger-ui-lite/src/types/otel.ts | 119 + .../jaeger-ui-lite/src/types/search.ts | 13 + .../jaeger-ui-lite/src/types/trace.ts | 82 + .../jaeger-ui-lite/src/types/tracking.ts | 21 + .../jaeger-ui-lite/src/types/units.ts | 5 + .../DraggableManager/DraggableManager.ts | 211 + .../utils/DraggableManager/EUpdateTypes.ts | 22 + .../src/utils/DraggableManager/README.md | 287 + .../src/utils/DraggableManager/index.ts | 3 + .../src/utils/DraggableManager/types.ts | 21 + .../src/utils/ValidatedFormField.css | 4 + .../src/utils/ValidatedFormField.tsx | 32 + .../src/utils/color-generator.ts | 73 + .../src/utils/config/get-config.ts | 52 + .../src/utils/config/get-target.ts | 9 + .../src/utils/config/process-deprecation.ts | 52 + .../src/utils/config/process-scripts.ts | 16 + .../src/utils/configure-store.ts | 47 + .../jaeger-ui-lite/src/utils/constants.ts | 21 + .../packages/jaeger-ui-lite/src/utils/date.ts | 254 + .../jaeger-ui-lite/src/utils/documentTitle.ts | 35 + .../jaeger-ui-lite/src/utils/embedded-url.ts | 53 + .../jaeger-ui-lite/src/utils/filter-spans.ts | 75 + .../utils/fixtures/oltp2jaeger-multi-out.json | 524 + .../utils/fixtures/otlp2jaeger-in-error.json | 43 + .../src/utils/fixtures/otlp2jaeger-in.json | 74 + .../otlp2jaeger-multi-in-combined.json | 202 + .../fixtures/otlp2jaeger-multi-in.json.txt | 4 + .../src/utils/fixtures/otlp2jaeger-out.json | 98 + .../src/utils/generate-action-types.ts | 24 + .../jaeger-ui-lite/src/utils/guardReducer.ts | 21 + .../src/utils/link-formatting.ts | 106 + .../jaeger-ui-lite/src/utils/number.ts | 25 + .../jaeger-ui-lite/src/utils/parseQuery.ts | 23 + .../jaeger-ui-lite/src/utils/prefix-url.ts | 35 + .../jaeger-ui-lite/src/utils/readJsonFile.ts | 72 + .../jaeger-ui-lite/src/utils/regexp-escape.ts | 6 + .../packages/jaeger-ui-lite/src/utils/sort.ts | 42 + .../src/utils/span-ancestor-ids.ts | 19 + .../src/utils/stringSupplant.ts | 31 + .../src/utils/test/requestAnimationFrame.js | 23 + .../src/utils/update-ui-find.ts | 40 + .../src/utils/version/get-version.ts | 20 + .../src/utils/withRouteProps.tsx | 75 + .../packages/jaeger-ui-lite/src/vite-env.d.ts | 1 + .../jaeger-ui-lite/ts-conv-progress.sh | 21 + .../packages/jaeger-ui-lite/tsconfig.json | 8 + .../jaeger-ui-lite/tsconfig.lint.json | 28 + .../packages/jaeger-ui-lite/vite.config.mts | 127 + app/vmui/packages/vmui/package.json | 18 +- app/vmui/packages/vmui/src/App.tsx | 46 +- app/vmui/packages/vmui/src/api/types.ts | 2 +- .../Chart/BarHitsChart/BarHitsChart.tsx | 50 - .../BarHitsLegend/BarHitsLegend.tsx | 58 - .../BarHitsLegend/BarHitsLegendItem.tsx | 91 - .../BarHitsChart/BarHitsLegend/style.scss | 126 - .../BarHitsOptions/BarHitsOptions.tsx | 112 - .../BarHitsChart/BarHitsOptions/style.scss | 37 - .../BarHitsChart/BarHitsPlot/BarHitsPlot.tsx | 152 - .../BarHitsTooltip/BarHitsTooltip.tsx | 132 - .../BarHitsChart/BarHitsTooltip/style.scss | 177 - .../LegendHitsMenu/LegendHitsMenu.tsx | 50 - .../LegendHitsMenu/LegendHitsMenuBase.tsx | 64 - .../LegendHitsMenu/LegendHitsMenuFields.tsx | 74 - .../LegendHitsMenu/LegendHitsMenuRow.tsx | 115 - .../LegendHitsMenu/LegendHitsMenuStats.tsx | 23 - .../BarHitsChart/LegendHitsMenu/style.scss | 178 - .../BarHitsChart/hooks/useBarHitsOptions.ts | 142 - .../components/Chart/BarHitsChart/style.scss | 22 - .../components/Chart/BarHitsChart/types.ts | 13 - .../components/Chart/GraphTips/GraphTips.tsx | 54 - .../Chart/GraphTips/constants/tips.tsx | 67 - .../src/components/Chart/GraphTips/style.scss | 49 - .../AdditionalSettings/AdditionalSettings.tsx | 2 +- .../GlobalSettings/GlobalSettings.tsx | 2 +- .../ServerConfigurator/ServerConfigurator.tsx | 2 +- .../TenantsConfiguration.tsx | 2 +- .../TenantsConfiguration/TenantsFields.tsx | 4 +- .../hooks/useFetchAccountIds.ts | 2 +- .../GlobalSettings/Timezones/Timezones.tsx | 2 +- .../Timezones/WarningTimezone.tsx | 2 +- .../LogsSettings/LogParsingSwitches.tsx | 2 +- .../ThemeControl/ThemeControl.tsx | 2 +- .../ExecutionControls/ExecutionControls.tsx | 2 +- .../TimeDurationSelector.tsx | 2 +- .../TimeSelector/TimeSelector.tsx | 2 +- .../DownloadButton/DownloadButton.tsx | 2 +- .../GroupLogsConfigurators.tsx | 4 +- .../components/Main/Accordion/Accordion.tsx | 2 +- .../vmui/src/components/Main/Alert/Alert.tsx | 2 +- .../Main/Autocomplete/Autocomplete.tsx | 2 +- .../src/components/Main/Button/Button.tsx | 2 +- .../src/components/Main/Checkbox/Checkbox.tsx | 2 +- .../Main/CodeExample/CodeExample.tsx | 2 +- .../Main/DatePicker/Calendar/Calendar.tsx | 2 +- .../Calendar/CalendarBody/CalendarBody.tsx | 2 +- .../CalendarHeader/CalendarHeader.tsx | 2 +- .../Calendar/MonthsList/MonthsList.tsx | 2 +- .../Calendar/YearsList/YearsList.tsx | 2 +- .../components/Main/DatePicker/DatePicker.tsx | 2 +- .../DateTimeInput/DateTimeInput.tsx | 43 +- .../components/Main/Hyperlink/Hyperlink.tsx | 2 +- .../components/Main/Icons/PreviewIcons.tsx | 2 +- .../vmui/src/components/Main/Icons/index.tsx | 2 +- .../components/Main/LineLoader/LineLoader.tsx | 2 +- .../vmui/src/components/Main/Modal/Modal.tsx | 6 +- .../components/Main/Pagination/Pagination.tsx | 2 +- .../Pagination/SelectLimit/SelectLimit.tsx | 2 +- .../src/components/Main/Popper/Popper.tsx | 7 +- .../MultipleSelectedValue.tsx | 2 +- .../src/components/Main/Select/Select.tsx | 2 +- .../Main/ShortcutKeys/ShortcutKeys.tsx | 2 +- .../src/components/Main/Switch/Switch.tsx | 2 +- .../vmui/src/components/Main/Tabs/TabItem.tsx | 2 +- .../components/Main/Tabs/TabItemWrapper.tsx | 2 +- .../vmui/src/components/Main/Tabs/Tabs.tsx | 2 +- .../components/Main/TextField/TextField.tsx | 2 +- .../Main/TextField/TextFieldMessage.tsx | 2 +- .../Main/ThemeProvider/ThemeProvider.ts | 2 +- .../src/components/Main/Toggle/Toggle.tsx | 2 +- .../src/components/Main/Tooltip/Tooltip.tsx | 9 +- .../components/QueryHistory/QueryHistory.tsx | 2 +- .../QueryHistory/QueryHistoryItem.tsx | 2 +- .../ScrollToTopButton/ScrollToTopButton.tsx | 2 +- .../vmui/src/components/Table/Table.tsx | 4 +- .../Table/TableSettings/TableSettings.tsx | 4 +- .../components/Views/JsonView/JsonView.tsx | 2 +- .../packages/vmui/src/contexts/Snackbar.tsx | 2 +- .../vmui/src/hooks/uplot/useDragChart.ts | 2 +- .../vmui/src/hooks/uplot/usePlotScale.ts | 2 +- .../vmui/src/hooks/uplot/useReadyChart.ts | 2 +- .../vmui/src/hooks/uplot/useZoomChart.ts | 2 +- .../packages/vmui/src/hooks/useBoolean.ts | 2 +- .../vmui/src/hooks/useClickOutside.ts | 2 +- .../vmui/src/hooks/useDebounceCallback.ts | 2 +- .../vmui/src/hooks/useDeviceDetect.ts | 2 +- .../packages/vmui/src/hooks/useDropzone.ts | 2 +- .../packages/vmui/src/hooks/useElementSize.ts | 2 +- .../vmui/src/hooks/useEventListener.ts | 2 +- .../vmui/src/hooks/useFetchAppConfig.ts | 2 +- .../vmui/src/hooks/useLocalStorageBoolean.ts | 2 +- .../packages/vmui/src/hooks/usePrevious.ts | 2 +- .../vmui/src/hooks/useQuickAutocomplete.ts | 2 +- .../src/hooks/useSearchParamsFromObject.ts | 6 +- .../vmui/src/hooks/useSortedCategories.ts | 2 +- .../vmui/src/hooks/useStateSearchParams.ts | 4 +- .../packages/vmui/src/hooks/useSystemTheme.ts | 2 +- app/vmui/packages/vmui/src/hooks/useTenant.ts | 4 +- .../packages/vmui/src/hooks/useUnmount.ts | 2 +- .../packages/vmui/src/hooks/useWindowSize.ts | 2 +- app/vmui/packages/vmui/src/index.tsx | 17 +- .../vmui/src/jaeger/JaegerRoutesInVmui.tsx | 55 + app/vmui/packages/vmui/src/jaeger/ub.css | 744 ++ .../vmui/src/layouts/Footer/Footer.tsx | 2 +- .../vmui/src/layouts/Header/Header.tsx | 4 +- .../Header/HeaderControls/HeaderControls.tsx | 2 +- .../layouts/Header/HeaderNav/HeaderNav.tsx | 2 +- .../src/layouts/Header/HeaderNav/NavItem.tsx | 2 +- .../layouts/Header/HeaderNav/NavSubItem.tsx | 2 +- .../Header/SidebarNav/SidebarHeader.tsx | 2 +- .../layouts/LogsLayout/ControlsLogsLayout.tsx | 2 +- .../src/layouts/LogsLayout/LogsLayout.tsx | 41 +- .../TracesLayout/ControlsTracesLayout.tsx | 26 + .../src/layouts/TracesLayout/TracesLayout.tsx | 48 + .../vmui/src/layouts/TracesLayout/style.scss | 58 + .../DownloadLogsButton/DownloadLogsButton.tsx | 2 +- .../src/pages/ExploreLogs/ExploreLogs.tsx | 4 +- .../ExploreLogsBarChart.tsx | 4 +- .../ExploreLogsBody/ExploreLogsBody.tsx | 2 +- .../ExploreLogs/ExploreLogsBody/TableLogs.tsx | 2 +- .../ExploreLogs/ExploreLogsBody/types.ts | 2 +- .../views/GroupView/GroupView.tsx | 2 +- .../views/JsonView/JsonView.tsx | 5 +- .../JsonViewSettings/JsonViewSettings.tsx | 6 +- .../LiveTailingView/LiveTailingSettings.tsx | 3 +- .../views/LiveTailingView/LiveTailingView.tsx | 4 +- .../LiveTailingView/useLiveTailingLogs.ts | 2 +- .../views/TableView/TableView.tsx | 4 +- .../views/components/EmptyLogs/EmptyLogs.tsx | 2 +- .../ExploreLogsHeader/ExploreLogsHeader.tsx | 2 +- .../pages/ExploreLogs/GroupLogs/GroupLogs.tsx | 5 +- .../GroupLogs/GroupLogsFieldRow.tsx | 4 +- .../ExploreLogs/GroupLogs/GroupLogsFields.tsx | 2 +- .../ExploreLogs/GroupLogs/GroupLogsHeader.tsx | 4 +- .../GroupLogs/GroupLogsHeaderItem.tsx | 4 +- .../ExploreLogs/GroupLogs/GroupLogsItem.tsx | 4 +- .../ExploreLogs/hooks/useFetchLogHits.ts | 2 +- .../pages/ExploreLogs/hooks/useFetchLogs.ts | 2 +- .../ExploreLogs/hooks/usePaginateGroups.ts | 2 +- .../packages/vmui/src/router/useNavigate.ts | 36 + .../vmui/src/router/useSearchParams.ts | 42 + .../vmui/src/state/common/StateContext.tsx | 2 +- .../src/state/logsPanel/LogsStateContext.tsx | 2 +- .../src/state/query/QueryStateContext.tsx | 14 +- .../packages/vmui/src/state/query/reducer.ts | 4 +- .../vmui/src/state/time/TimeStateContext.tsx | 2 +- app/vmui/packages/vmui/src/styles/core.scss | 15 +- app/vmui/packages/vmui/src/styles/style.scss | 2 +- .../packages/vmui/src/styles/variables.scss | 2 +- .../packages/vmui/src/utils/ansiParser.tsx | 2 +- .../vmui/src/utils/combine-components.tsx | 2 +- app/vmui/packages/vmui/tsconfig.json | 21 +- app/vmui/packages/vmui/vite.config.ts | 11 +- app/vmui/packages/vmui/vitest.config.ts | 11 +- app/vmui/pnpm-lock.yaml | 10449 ++++++++++++++++ app/vmui/pnpm-workspace.yaml | 3 + app/vmui/tsconfig.json | 27 + 372 files changed, 29752 insertions(+), 2030 deletions(-) create mode 100644 app/vmui/package-lock.json create mode 100644 app/vmui/package.json create mode 100644 app/vmui/packages/jaeger-ui-lite/.babelrc create mode 100644 app/vmui/packages/jaeger-ui-lite/.eslintrc.js create mode 100644 app/vmui/packages/jaeger-ui-lite/.gitignore create mode 100644 app/vmui/packages/jaeger-ui-lite/.stylelintrc.json create mode 100644 app/vmui/packages/jaeger-ui-lite/package.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/actions/jaeger-api.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/api/jaeger.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/NotFound.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/Page.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/Page.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeProvider.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeStorage.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeTokenSync.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/ScrollManager.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Trace.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/SpanGraph.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/render-into-canvas.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/Positions.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.markers.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/DetailState.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/duck.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/grid.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/utils.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Tween.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/scroll-page.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/types.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/ReferenceLink.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ClickToCopy.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailList.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTable.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/types.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/ExternalLinks.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/highlightMatches.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/SearchableSelect.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/TraceId.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/TraceId.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/TraceName.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/TraceName.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/UiFindInput.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/url.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/utils.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/components/common/vars.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/default-config.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/default-version.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/keyboard-keys.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/search-form.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/constants/tag-keys.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/middlewares/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/OtelSpanFacade.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/OtelTraceFacade.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/link-patterns.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/order-by.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/search.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/span.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/trace-viewer.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/model/transform-trace-data.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/config.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/dependencies.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/embedded.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/services.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/reducers/trace.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/site-prefix.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/TNil.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/TTraceDiffState.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/TTraceTimeline.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/api-error.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/archive.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/config.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/critical_path.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/embedded.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/external-modules.d.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/hyperlink.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/metrics.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/otel.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/search.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/trace.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/tracking.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/types/units.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/DraggableManager.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/EUpdateTypes.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/README.md create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/index.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/types.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.css create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/color-generator.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/config/get-config.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/config/get-target.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/config/process-deprecation.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/config/process-scripts.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/configure-store.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/constants.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/date.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/documentTitle.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/embedded-url.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/filter-spans.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/oltp2jaeger-multi-out.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in-error.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in-combined.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in.json.txt create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-out.json create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/generate-action-types.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/guardReducer.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/link-formatting.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/number.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/parseQuery.ts create mode 100755 app/vmui/packages/jaeger-ui-lite/src/utils/prefix-url.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/readJsonFile.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/regexp-escape.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/sort.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/span-ancestor-ids.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/stringSupplant.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/test/requestAnimationFrame.js create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/update-ui-find.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/version/get-version.ts create mode 100644 app/vmui/packages/jaeger-ui-lite/src/utils/withRouteProps.tsx create mode 100644 app/vmui/packages/jaeger-ui-lite/src/vite-env.d.ts create mode 100755 app/vmui/packages/jaeger-ui-lite/ts-conv-progress.sh create mode 100644 app/vmui/packages/jaeger-ui-lite/tsconfig.json create mode 100644 app/vmui/packages/jaeger-ui-lite/tsconfig.lint.json create mode 100644 app/vmui/packages/jaeger-ui-lite/vite.config.mts delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsChart.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegend.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegendItem.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/style.scss delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/BarHitsOptions.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/style.scss delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsPlot/BarHitsPlot.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/BarHitsTooltip.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/style.scss delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenu.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuBase.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuFields.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuRow.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuStats.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/style.scss delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/hooks/useBarHitsOptions.ts delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/style.scss delete mode 100644 app/vmui/packages/vmui/src/components/Chart/BarHitsChart/types.ts delete mode 100644 app/vmui/packages/vmui/src/components/Chart/GraphTips/GraphTips.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/GraphTips/constants/tips.tsx delete mode 100644 app/vmui/packages/vmui/src/components/Chart/GraphTips/style.scss create mode 100644 app/vmui/packages/vmui/src/jaeger/JaegerRoutesInVmui.tsx create mode 100644 app/vmui/packages/vmui/src/jaeger/ub.css create mode 100644 app/vmui/packages/vmui/src/layouts/TracesLayout/ControlsTracesLayout.tsx create mode 100644 app/vmui/packages/vmui/src/layouts/TracesLayout/TracesLayout.tsx create mode 100644 app/vmui/packages/vmui/src/layouts/TracesLayout/style.scss create mode 100644 app/vmui/packages/vmui/src/router/useNavigate.ts create mode 100644 app/vmui/packages/vmui/src/router/useSearchParams.ts create mode 100644 app/vmui/pnpm-lock.yaml create mode 100644 app/vmui/pnpm-workspace.yaml create mode 100644 app/vmui/tsconfig.json diff --git a/app/vmui/Dockerfile-build b/app/vmui/Dockerfile-build index ecc635f713..c7f5c638f9 100644 --- a/app/vmui/Dockerfile-build +++ b/app/vmui/Dockerfile-build @@ -3,6 +3,12 @@ FROM node:20-alpine3.19 # Sets a custom location for the npm cache, preventing access errors in system directories ENV NPM_CONFIG_CACHE=/build/.npm +# Enable pnpm via Corepack (Node 20 ships with it) and pin a pnpm version for stability +RUN corepack enable && corepack prepare pnpm@9.15.4 --activate + +# Put pnpm store under /build (bind-mounted), so it is writable even when running as host uid:gid +ENV PNPM_STORE_DIR=/build/.pnpm-store + RUN apk update && \ apk upgrade && \ apk add --no-cache bash bash-doc bash-completion libtool autoconf automake nasm pkgconfig libpng gcc make g++ zlib-dev gawk && \ diff --git a/app/vmui/Makefile b/app/vmui/Makefile index d1bd874eb0..ce100f672a 100644 --- a/app/vmui/Makefile +++ b/app/vmui/Makefile @@ -9,7 +9,7 @@ vmui-build: vmui-package-base-image --mount type=bind,src="$(shell pwd)/app/vmui",dst=/build \ -w /build/packages/vmui \ --entrypoint=/bin/bash \ - vmui-builder-image -c "npm install && npm run build" + vmui-builder-image -c "corepack enable && pnpm install --frozen-lockfile && pnpm run build" vmui-update: vmui-build rm -rf app/vtselect/vmui/* && mv app/vmui/packages/vmui/build/* app/vtselect/vmui && rm -rf app/vtselect/vmui/dashboards diff --git a/app/vmui/package-lock.json b/app/vmui/package-lock.json new file mode 100644 index 0000000000..937bbf4436 --- /dev/null +++ b/app/vmui/package-lock.json @@ -0,0 +1,11 @@ +{ + "name": "vmui-workspace", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "vmui-workspace", + "devDependencies": {} + } + } +} diff --git a/app/vmui/package.json b/app/vmui/package.json new file mode 100644 index 0000000000..94b09439e7 --- /dev/null +++ b/app/vmui/package.json @@ -0,0 +1,6 @@ +{ + "name": "vmui-workspace", + "private": true, + "packageManager": "pnpm@9.15.0", + "devDependencies": {} +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/.babelrc b/app/vmui/packages/jaeger-ui-lite/.babelrc new file mode 100644 index 0000000000..bfae0a2f9e --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/.babelrc @@ -0,0 +1,16 @@ +{ + "env": { + "production": { + "plugins": [ + [ + "babel-plugin-react-remove-properties", + { + "properties": [ + "data-testid" + ] + } + ] + ] + } + } +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/.eslintrc.js b/app/vmui/packages/jaeger-ui-lite/.eslintrc.js new file mode 100644 index 0000000000..5db444f3dd --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + // Let ESLint know about constants injected by the build system. + // These aren't technically globals (in that they are replaced by literals at build time), + // but from a linter perspective, they are globals and so need to be explicitly listed. + // https://vitejs.dev/config/shared-options.html#define + globals: { + __REACT_APP_GA_DEBUG__: false, + __REACT_APP_VSN_STATE__: false, + __APP_ENVIRONMENT__: false, + }, + overrides: [ + { + // Instruct ESLint not to lint build tooling files as part of the project. + files: ['vite.config.ts', 'src/vite-env.d.ts'], + parserOptions: { + project: null, + }, + }, + ], +}; diff --git a/app/vmui/packages/jaeger-ui-lite/.gitignore b/app/vmui/packages/jaeger-ui-lite/.gitignore new file mode 100644 index 0000000000..f57c169939 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/.gitignore @@ -0,0 +1,9 @@ +*.tsbuildinfo +index.d.ts + +# Bundle size breakdown generated by rollup-plugin-visualizer +stats.html + +# Local UI config files for development (see README.md for details) +jaeger-ui.config.js +jaeger-ui.config.json diff --git a/app/vmui/packages/jaeger-ui-lite/.stylelintrc.json b/app/vmui/packages/jaeger-ui-lite/.stylelintrc.json new file mode 100644 index 0000000000..ab980f3b7b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/.stylelintrc.json @@ -0,0 +1,21 @@ +{ + "extends": "stylelint-config-recommended", + "plugins": ["stylelint-value-no-unknown-custom-properties"], + "rules": { + "csstools/value-no-unknown-custom-properties": [ + true, + { + "importFrom": ["src/components/common/vars.css"] + } + ], + "no-descending-specificity": null, + "font-family-no-missing-generic-family-keyword": null, + "property-no-deprecated": null, + "declaration-block-no-duplicate-properties": null, + "declaration-property-value-no-unknown": null, + "no-duplicate-selectors": null, + "shorthand-property-no-redundant-values": null, + "declaration-block-no-redundant-longhand-properties": null, + "declaration-property-value-keyword-no-deprecated": null + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/package.json b/app/vmui/packages/jaeger-ui-lite/package.json new file mode 100644 index 0000000000..612b383458 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/package.json @@ -0,0 +1,118 @@ +{ + "private": true, + "name": "jaeger-ui-lite", + "version": "1.0.0", + "main": "src/index.tsx", + "license": "Apache-2.0", + "homepage": ".", + "devDependencies": { + "@babel/preset-env": "7.28.5", + "@babel/preset-react": "7.28.5", + "@babel/preset-typescript": "7.28.5", + "@testing-library/jest-dom": "6.9.1", + "@testing-library/react": "16.3.0", + "@testing-library/user-event": "14.6.1", + "@types/deep-freeze": "0.1.5", + "@types/history": "4.7.11", + "@types/jest": "30.0.0", + "@types/lodash": "4.17.21", + "@types/object-hash": "3.0.6", + "@types/react": "19.2.7", + "@types/react-router-dom": "5.3.3", + "@types/redux-actions": "2.2.1", + "@vitejs/plugin-legacy": "7.2.1", + "@vitejs/plugin-react": "5.1.1", + "babel-jest": "30.2.0", + "babel-plugin-inline-react-svg": "2.0.2", + "babel-plugin-react-remove-properties": "0.3.1", + "jest": "30.2.0", + "jest-environment-jsdom": "30.2.0", + "jest-junit": "16.0.0", + "less": "4.5.1", + "terser": "5.44.1", + "vite": "7.3.0" + }, + "dependencies": { + "@pyroscope/flamegraph": "0.21.4", + "@sentry/browser": "10.32.0", + "antd": "6.1.1", + "chance": "1.1.13", + "classnames": "2.5.1", + "combokeys": "3.0.1", + "copy-to-clipboard": "3.3.3", + "dayjs": "1.11.19", + "deep-freeze": "0.0.1", + "drange": "2.0.1", + "history": "4.10.1", + "isomorphic-fetch": "3.0.0", + "lodash": "4.17.21", + "logfmt": "1.4.0", + "lru-memoize": "1.1.0", + "match-sorter": "8.2.0", + "memoize-one": "6.0.0", + "object-hash": "3.0.0", + "query-string": "9.3.1", + "react": "19.2.0", + "react-circular-progressbar": "2.2.0", + "react-dom": "19.2.0", + "react-icons": "5.5.0", + "react-is": "19.2.0", + "react-json-view-lite": "2.5.0", + "react-redux": "9.2.0", + "react-router-dom": "5.3.4", + "react-router-dom-v5-compat": "6.30.0", + "@tanstack/react-virtual": "3.13.12", + "recharts": "3.6.0", + "redux": "5.0.1", + "redux-actions": "3.0.3", + "redux-first-history": "5.2.0", + "redux-promise-middleware": "6.2.0", + "store": "2.0.12", + "tween-functions": "1.2.0", + "u-basscss": "2.0.1" + }, + "peerDependencies": { + "react": "^19", + "react-dom": "^19", + "react-redux": "^9" + }, + "scripts": { + "build": "NODE_ENV=production REACT_APP_VSN_STATE=$(../../scripts/get-tracking-version.js) vite build", + "coverage": "npm run test-ci -- --coverage", + "start:ga-debug": "REACT_APP_GA_DEBUG=1 REACT_APP_VSN_STATE=$(../../scripts/get-tracking-version.js) vite", + "start": "vite", + "test": "jest --silent --color --maxWorkers=50%", + "test-ci": "CI=1 jest --silent --color --maxWorkers=4" + }, + "jest": { + "globalSetup": "./test/jest.global-setup.js", + "setupFilesAfterEnv": [ + "./test/jest-per-test-setup.js" + ], + "collectCoverageFrom": [ + "src/**/*.{js,jsx,ts,tsx}", + "!src/**/*.d.ts", + "!src/setup*.js", + "!src/utils/DraggableManager/demo/*.tsx", + "!src/utils/test/**/*.js", + "!src/demo/**/*.js", + "!src/types/*" + ], + "reporters": [ + "default", + "jest-junit" + ], + "transform": { + "\\.(css|png)$": "./test/generic-file-transform.js", + "\\.([jt]sx?|svg)$": "./test/babel-transform.js" + }, + "transformIgnorePatterns": [ + "[/\\\\\\\\]node_modules[/\\\\\\\\](?!redux-actions).+\\\\.(js|jsx|mjs|cjs|ts|tsx)$" + ], + "testEnvironment": "jsdom", + "snapshotFormat": { + "escapeString": true, + "printBasicPrototype": true + } + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/actions/jaeger-api.ts b/app/vmui/packages/jaeger-ui-lite/src/actions/jaeger-api.ts new file mode 100644 index 0000000000..ffba7acf50 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/actions/jaeger-api.ts @@ -0,0 +1,56 @@ +import { createAction } from 'redux-actions'; +import JaegerAPI from '../api/jaeger'; + +const metricType: Record = { + latencies: 'latencies', + calls: 'calls', + errors: 'errors', +}; + +export const fetchTrace = createAction( + '@JAEGER_API/FETCH_TRACE', + (id: string) => JaegerAPI.fetchTrace(id), + (id: string) => ({ id }) +); + +export const fetchMultipleTraces = createAction( + '@JAEGER_API/FETCH_MULTIPLE_TRACES', + (ids: string[]) => JaegerAPI.searchTraces({ traceID: ids }), + (ids: string[]) => ({ ids }) +); + +export const archiveTrace = createAction( + '@JAEGER_API/ARCHIVE_TRACE', + (id: string) => JaegerAPI.archiveTrace(id), + (id: string) => ({ id }) +); + +export const searchTraces = createAction( + '@JAEGER_API/SEARCH_TRACES', + (query: Record) => JaegerAPI.searchTraces(query), + (query: Record) => ({ query }) +); + +export const fetchServices = createAction('@JAEGER_API/FETCH_SERVICES', () => JaegerAPI.fetchServices()); + +export const fetchServiceOperations = createAction( + '@JAEGER_API/FETCH_SERVICE_OPERATIONS', + (serviceName: string) => JaegerAPI.fetchServiceOperations(serviceName), + (serviceName: string) => ({ serviceName }) +); + +export const fetchServiceServerOps = createAction( + '@JAEGER_API/FETCH_SERVICE_SERVER_OP', + (serviceName: string) => JaegerAPI.fetchServiceServerOps(serviceName), + (serviceName: string) => ({ serviceName }) +); + +export const fetchDeepDependencyGraph = createAction( + '@JAEGER_API/FETCH_DEEP_DEPENDENCY_GRAPH', + (query: Record) => JaegerAPI.fetchDeepDependencyGraph(query), + (query: Record) => ({ query }) +); + +export const fetchDependencies = createAction('@JAEGER_API/FETCH_DEPENDENCIES', () => + JaegerAPI.fetchDependencies() +); diff --git a/app/vmui/packages/jaeger-ui-lite/src/api/jaeger.ts b/app/vmui/packages/jaeger-ui-lite/src/api/jaeger.ts new file mode 100644 index 0000000000..2218f9d3c0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/api/jaeger.ts @@ -0,0 +1,138 @@ +import fetch from 'isomorphic-fetch'; +import dayjs from 'dayjs'; +import _duration from 'dayjs/plugin/duration'; +import queryString from 'query-string'; + +import getConfig from '../utils/config/get-config'; +import prefixUrl from '../utils/prefix-url'; + +dayjs.extend(_duration); + +interface IApiError extends Error { + httpStatus?: number; + httpStatusText?: string; + httpBody?: string; + httpUrl?: string; + httpQuery?: string; +} + +type FetchOptions = { + query?: string | Record | null; + method?: string; + body?: string; + credentials?: RequestCredentials; +}; + +// export for tests +export function getMessageFromError(errData: any, status: number): string { + if (errData.code != null && errData.msg != null) { + if (errData.code === status) { + return errData.msg; + } + return `${errData.code} - ${errData.msg}`; + } + try { + return JSON.stringify(errData); + } catch { + return String(errData); + } +} + +function getJSON(url: string, options: FetchOptions = {}): Promise { + const { query = null, ...init } = options; + (init as any).credentials = 'same-origin'; + let queryStr = ''; + + if (query) { + queryStr = `?${typeof query === 'string' ? query : queryString.stringify(query)}`; + } + + return fetch(`${url}${queryStr}`, init as RequestInit).then((response: Response) => { + if (response.status < 400) { + return response.json(); + } + return response.text().then((bodyText: string) => { + let data: any; + let bodyTextFmt: string | null; + let errorMessage: string; + try { + data = JSON.parse(bodyText); + bodyTextFmt = JSON.stringify(data, null, 2); + } catch { + data = null; + bodyTextFmt = null; + } + if (data && Array.isArray(data.errors) && data.errors.length) { + errorMessage = data.errors.map((err: any) => getMessageFromError(err, response.status)).join('; '); + } else { + errorMessage = bodyText || `${response.status} - ${response.statusText}`; + } + if (typeof errorMessage === 'string') { + errorMessage = errorMessage.trim(); + } + const error: IApiError = new Error(`HTTP Error: ${errorMessage}`); + error.httpStatus = response.status; + error.httpStatusText = response.statusText; + error.httpBody = bodyTextFmt || bodyText; + error.httpUrl = url; + error.httpQuery = typeof query === 'string' ? query : queryString.stringify(query || {}); + throw error; + }); + }); +} + +export const DEFAULT_API_ROOT = '/select/jaeger/api/'; +export const ANALYTICS_ROOT = prefixUrl('/analytics/'); +export const QUALITY_METRICS_ROOT = prefixUrl(getConfig().qualityMetrics?.apiEndpoint || ''); +export const DEFAULT_DEPENDENCY_LOOKBACK = dayjs.duration(1, 'weeks').asMilliseconds(); + +const JaegerAPI = { + apiRoot: DEFAULT_API_ROOT, + archiveTrace(id: string): Promise { + return getJSON(`${this.apiRoot}archive/${id}`, { method: 'POST' }); + }, + fetchDecoration(url: string): Promise { + return getJSON(url); + }, + fetchDeepDependencyGraph(query: Record): Promise { + return getJSON(`${ANALYTICS_ROOT}v1/dependencies`, { query }); + }, + fetchDependencies(endTs = new Date().getTime(), lookback = DEFAULT_DEPENDENCY_LOOKBACK): Promise { + return getJSON(`${this.apiRoot}dependencies`, { query: { endTs, lookback } }); + }, + fetchQualityMetrics(service: string, hours: number): Promise { + return getJSON(QUALITY_METRICS_ROOT, { query: { hours, service } }); + }, + fetchServiceOperations(serviceName: string): Promise { + return getJSON(`${this.apiRoot}services/${encodeURIComponent(serviceName)}/operations`); + }, + transformOTLP(traces: any): Promise { + return getJSON(`${this.apiRoot}transform`, { method: 'POST', body: JSON.stringify(traces) }); + }, + fetchServiceServerOps(service: string): Promise { + return getJSON(`${this.apiRoot}operations`, { + query: { + service, + spanKind: 'server', + }, + }); + }, + fetchServices(): Promise { + return getJSON(`${this.apiRoot}services`); + }, + fetchTrace(id: string): Promise { + return getJSON(`${this.apiRoot}traces/${id}`); + }, + searchTraces(query: Record): Promise { + return getJSON(`${this.apiRoot}traces`, { query }); + }, + fetchMetrics(metricType: string, serviceNameList: string[], query: Record): Promise { + const servicesName = serviceNameList.map((serviceName: string) => `service=${serviceName}`).join(','); + + return getJSON(`${this.apiRoot}metrics/${metricType}`, { + query: `${servicesName}&${queryString.stringify(query)}`, + }).then((d: any) => ({ ...d, quantile: query.quantile })); + }, +}; + +export default JaegerAPI; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/NotFound.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/App/NotFound.tsx new file mode 100644 index 0000000000..d76ffa1fa9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/NotFound.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +import ErrorMessage from '../common/ErrorMessage'; +import prefixUrl from '../../utils/prefix-url'; + +type NotFoundProps = { + error: Error | string; +}; + +export default function NotFound({ error }: NotFoundProps) { + return ( +
+

Error

+ {error && } + Back home +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.css b/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.css new file mode 100644 index 0000000000..a9d10a2adb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.css @@ -0,0 +1,27 @@ +.Page--topNav { + height: auto; + padding: 0; + position: fixed; + width: 100%; + z-index: 11; + font-weight: 500; +} + +.Page--content { + display: flex; + flex-direction: column; + left: 0; + min-height: calc(100% - var(--nav-height)); + /* position: absolute; */ + right: 0; + background-color: var(--surface-primary); +} + +.Page--content--no-embedded { + top: var(--nav-height); +} + +.ant-menu-dark { + /* same color as in Docsy theme for jaegertracing.io */ + background-color: #1a4767; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.tsx new file mode 100644 index 0000000000..c686c7d50f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/Page.tsx @@ -0,0 +1,39 @@ +import * as React from 'react'; +import { Layout } from 'antd'; +import cx from 'classnames'; +import { connect } from 'react-redux'; + +import { ReduxState } from '../../types'; +import { EmbeddedState } from '../../types/embedded'; + +import './Page.css'; +import withRouteProps from '../../utils/withRouteProps'; + +type TProps = { + children: React.ReactNode; + embedded: EmbeddedState; + pathname: string; + search: string; +}; + +const { Header, Content } = Layout; + +// export for tests +export const PageImpl: React.FC = ({ children, embedded, pathname, search }) => { + const contentCls = cx({ 'Page--content': true, 'Page--content--no-embedded': !embedded }); + + return ( +
+ {children} +
+ ); +}; + +// export for tests +export function mapStateToProps(state: ReduxState) { + const { embedded } = state; + const { pathname, search } = state.router.location; + return { embedded, pathname, search }; +} + +export default connect(mapStateToProps)(withRouteProps(PageImpl)); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeProvider.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeProvider.tsx new file mode 100644 index 0000000000..557e0087d1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeProvider.tsx @@ -0,0 +1,145 @@ +import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import type { ThemeConfig } from 'antd'; +import { ConfigProvider, theme } from 'antd'; + +import { DEFAULT_MODE, ThemeMode, getInitialTheme, writeStoredTheme } from './ThemeStorage'; +import { ThemeTokenSync } from './ThemeTokenSync'; + +type ThemeContextValue = { + mode: ThemeMode; + setMode: (mode: ThemeMode) => void; + toggleMode: () => void; +}; + +type ThemeProviderProps = { + children: React.ReactNode; +}; + +const ThemeModeContext = createContext({ + mode: DEFAULT_MODE, + setMode: () => undefined, + toggleMode: () => undefined, +}); + +export function useThemeMode() { + return useContext(ThemeModeContext); +} + +// The base theme customizes some dimensional properties. +const baseThemeConfig: ThemeConfig = { + cssVar: {}, + components: { + Layout: { + headerHeight: 48, + headerPadding: '0 50', + footerPadding: '24 50', + triggerHeight: 48, + zeroTriggerWidth: 36, + zeroTriggerHeight: 42, + }, + }, +}; + +const lightTheme: ThemeConfig = { + ...baseThemeConfig, + algorithm: theme.defaultAlgorithm, + token: { + // --- Color Palette Seeds --- + colorPrimary: '#199', // Your brand's single primary color + // --- Neutral Seeds (Optional but recommended) --- + colorTextBase: '#000000', + colorBgBase: '#ffffff', + colorLink: '#199', + }, + components: { + Layout: { + ...baseThemeConfig.components?.Layout, + bodyBg: '#fff', + headerBg: '#404040', + footerBg: '#fff', + siderBg: '#404040', + triggerBg: 'tint(#fff, 20%)', + }, + Menu: { + darkItemBg: '#151515', + }, + Table: { + rowHoverBg: '#e5f2f2', + }, + }, +}; + +const darkTheme: ThemeConfig = { + ...baseThemeConfig, + algorithm: theme.darkAlgorithm, + token: { + colorPrimary: '#4dd0e1', + colorBgLayout: '#0b1625', + colorBgContainer: '#162338', + colorText: 'rgba(244, 248, 255, 0.92)', + colorTextSecondary: 'rgba(244, 248, 255, 0.7)', + colorBorder: 'rgba(125, 153, 191, 0.4)', + colorBorderSecondary: 'rgba(125, 153, 191, 0.25)', + colorLink: '#7bdcff', + colorLinkHover: '#caf3ff', + colorBgElevated: '#162338', + }, + components: { + Layout: { + ...baseThemeConfig.components?.Layout, + bodyBg: '#0b1625', + headerBg: 'transparent', + footerBg: '#0b1625', + siderBg: '#0f1c30', + triggerBg: 'rgba(255, 255, 255, 0.06)', + }, + Menu: { + darkItemBg: 'transparent', + }, + Table: { + rowHoverBg: 'rgba(100, 217, 255, 0.12)', + headerColor: 'rgba(244, 248, 255, 0.75)', + }, + }, +}; + +export default function AppThemeProvider({ children }: ThemeProviderProps) { + const [mode, setModeState] = useState(() => getInitialTheme()); + + const setMode = useCallback((value: ThemeMode) => { + setModeState(value); + }, []); + + const toggleMode = useCallback(() => { + setModeState((prev: ThemeMode) => (prev === 'dark' ? 'light' : 'dark')); + }, []); + + useEffect(() => { + if (typeof document !== 'undefined') { + document.body.dataset.theme = mode; + } + + writeStoredTheme(mode); + }, [mode]); + + const value = useMemo( + () => ({ + mode, + setMode, + toggleMode, + }), + [mode, setMode, toggleMode] + ); + + const themeConfig: ThemeConfig = mode === 'dark' ? darkTheme : lightTheme; + + return ( + + + {/* Sync is inside ConfigProvider to access the theme context */} + + {children} + + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeStorage.ts b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeStorage.ts new file mode 100644 index 0000000000..632286d53b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeStorage.ts @@ -0,0 +1,67 @@ +import { getConfigValue } from '../../utils/config/get-config'; + +export type ThemeMode = 'light' | 'dark'; + +export const THEME_STORAGE_KEY = 'jaeger-ui-theme'; +export const DEFAULT_MODE: ThemeMode = 'light'; + +export function readStoredTheme(targetWindow?: Window | null): ThemeMode | null { + const activeWindow = + targetWindow !== undefined + ? (targetWindow ?? undefined) + : typeof window !== 'undefined' + ? window + : undefined; + if (!activeWindow) { + return null; + } + + try { + const stored = activeWindow.localStorage.getItem(THEME_STORAGE_KEY) as ThemeMode | null; + if (stored === 'light' || stored === 'dark') { + return stored; + } + } catch (err) { + // Local storage may be blocked; ignore and fallback below. + } + + return null; +} + +export function writeStoredTheme(mode: ThemeMode, targetWindow?: Window | null) { + const activeWindow = + targetWindow !== undefined + ? (targetWindow ?? undefined) + : typeof window !== 'undefined' + ? window + : undefined; + if (!activeWindow) { + return; + } + + try { + activeWindow.localStorage.setItem(THEME_STORAGE_KEY, mode); + } catch (err) { + // Ignore storage errors (e.g., Safari in private mode). + } +} + +export function getInitialTheme(): ThemeMode { + if (!getConfigValue('themes.enabled')) { + return DEFAULT_MODE; + } + const stored = readStoredTheme(); + if (stored) { + return stored; + } + + if ( + typeof window !== 'undefined' && + window.matchMedia && + window.matchMedia('(prefers-color-scheme: dark)').matches + ) { + return 'dark'; + } + + return DEFAULT_MODE; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeTokenSync.ts b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeTokenSync.ts new file mode 100644 index 0000000000..081d05af75 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/App/ThemeTokenSync.ts @@ -0,0 +1,48 @@ +import { theme } from 'antd'; +import { useEffect } from 'react'; + +/** + * ThemeTokenSync acts as a bridge between Ant Design's CSS-in-JS design tokens + * and global CSS variables. + * + * ### Why is this needed? + * + * Ant Design v6 injects design tokens into a local scope (usually the `.ant-layout` container). + * Elements rendered via Portals (Modals, Tooltips, Drawers) or those with `position: fixed` + * often live outside this scope in the DOM tree, causing `--ant-` variables to be undefined. + * + * ### What it does: + * + * 1. Hooks into the current AntD theme context via `theme.useToken()`. + * 2. Iterates through all tokens (e.g., `colorBgContainer`). + * 3. Converts them to kebab-case CSS variables (e.g., `--ant-color-bg-container`). + * 4. Injects them into the `:root` (``) element, making them globally accessible + * to custom CSS variables like `--surface-primary: var(--ant-color-bg-container)`. + * + * @example + * + * + * + * + */ +export function ThemeTokenSync(): null { + const { token } = theme.useToken(); + + useEffect(() => { + const root = document.documentElement; + + // Helper to convert camelCase to kebab-case + const toKebabCase = (str: string) => str.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`); + + // Map every design token to a CSS variable + Object.entries(token).forEach(([key, value]) => { + // We prefix with --ant to match your existing custom vars + // and check if the value is a string or number (avoiding nested objects) + if (typeof value === 'string' || typeof value === 'number') { + root.style.setProperty(`--ant-${toKebabCase(key)}`, String(value)); + } + }); + }, [token]); + + return null; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/ScrollManager.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/ScrollManager.ts new file mode 100644 index 0000000000..754b724ceb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/ScrollManager.ts @@ -0,0 +1,242 @@ +import { TNil } from '../../types'; +import { IOtelSpan, IOtelTrace } from '../../types/otel'; + +/** + * `Accessors` is necessary because `ScrollManager` needs to be created by + * `TracePage` so it can be passed into the keyboard shortcut manager. But, + * `ScrollManager` needs to know about the state of `ListView` and `Positions`, + * which are very low-level. And, storing their state info in redux or + * `TracePage#state` would be inefficient because the state info only rarely + * needs to be accessed (when a keyboard shortcut is triggered). `Accessors` + * allows that state info to be accessed in a loosely coupled fashion on an + * as-needed basis. + */ +export type Accessors = { + getViewRange: () => [number, number]; + getSearchedSpanIDs: () => Set | TNil; + getCollapsedChildren: () => Set | TNil; + getViewHeight: () => number; + getBottomRowIndexVisible: () => number; + getTopRowIndexVisible: () => number; + getRowPosition: (rowIndex: number) => { height: number; y: number }; + mapRowIndexToSpanIndex: (rowIndex: number) => number; + mapSpanIndexToRowIndex: (spanIndex: number) => number; +}; + +interface IScroller { + scrollTo: (rowIndex: number) => void; + // TODO arg names throughout + scrollBy: (rowIndex: number, opt?: boolean) => void; +} + +/** + * Returns `{ isHidden: true, ... }` if one of the parents of `span` is + * collapsed, e.g. has children hidden. + * + * @param {IOtelSpan} span The Span to check for. + * @param {Set} childrenAreHidden The set of Spans known to have hidden + * children, either because it is + * collapsed or has a collapsed parent. + * @returns {{ isHidden: boolean, parentIds: Set }} + */ +function isSpanHidden(span: IOtelSpan, childrenAreHidden: Set) { + const parentIDs = new Set(); + let current: IOtelSpan | undefined = span.parentSpan; + + while (current) { + parentIDs.add(current.spanID); + if (childrenAreHidden.has(current.spanID)) { + return { isHidden: true, parentIDs }; + } + current = current.parentSpan; + } + return { parentIDs, isHidden: false }; +} + +/** + * ScrollManager is intended for scrolling the TracePage. Has two modes, paging + * and scrolling to the previous or next visible span. + */ +export default class ScrollManager { + _trace: IOtelTrace | TNil; + _scroller: IScroller | TNil; + _accessors: Accessors | TNil; + + constructor(trace: IOtelTrace | TNil, scroller: IScroller) { + this._trace = trace; + this._scroller = scroller; + this._accessors = undefined; + } + + _scrollPast(rowIndex: number, direction: 1 | -1) { + const xrs = this._accessors; + /* istanbul ignore next */ + if (!xrs) { + throw new Error('Accessors not set'); + } + const isUp = direction < 0; + const position = xrs.getRowPosition(rowIndex); + if (!position) { + console.warn('Invalid row index'); + return; + } + let { y } = position; + const vh = xrs.getViewHeight(); + if (!isUp) { + y += position.height; + // scrollTop is based on the top of the window + y -= vh; + } + y += direction * 0.5 * vh; + this._scroller?.scrollTo(y); + } + + _scrollToVisibleSpan(direction: 1 | -1, startRow?: number) { + const xrs = this._accessors; + /* istanbul ignore next */ + if (!xrs) { + throw new Error('Accessors not set'); + } + if (!this._trace) { + return; + } + const { duration, spans, startTime: traceStartTime } = this._trace; + const isUp = direction < 0; + let boundaryRow: number; + if (startRow != null) { + boundaryRow = startRow; + } else if (isUp) { + boundaryRow = xrs.getTopRowIndexVisible(); + } else { + boundaryRow = xrs.getBottomRowIndexVisible(); + } + const spanIndex = xrs.mapRowIndexToSpanIndex(boundaryRow); + if ((spanIndex === 0 && isUp) || (spanIndex === spans.length - 1 && !isUp)) { + return; + } + // fullViewSpanIndex is one row inside the view window unless already at the top or bottom + let fullViewSpanIndex = spanIndex; + if (spanIndex !== 0 && spanIndex !== spans.length - 1) { + fullViewSpanIndex -= direction; + } + const [viewStart, viewEnd] = xrs.getViewRange(); + const checkVisibility = viewStart !== 0 || viewEnd !== 1; + // use NaN as fallback to make flow happy + const startTime = checkVisibility ? traceStartTime + duration * viewStart : NaN; + const endTime = checkVisibility ? traceStartTime + duration * viewEnd : NaN; + const findMatches = xrs.getSearchedSpanIDs(); + const _collapsed = xrs.getCollapsedChildren(); + const childrenAreHidden = _collapsed ? new Set(_collapsed) : null; + + const boundary = direction < 0 ? -1 : spans.length; + let nextSpanIndex: number | undefined; + for (let i = fullViewSpanIndex + direction; i !== boundary; i += direction) { + const span = spans[i]; + const { duration: spanDuration, spanID, startTime: spanStartTime } = span; + const spanEndTime = spanStartTime + spanDuration; + if (checkVisibility && (spanStartTime > endTime || spanEndTime < startTime)) { + // span is not visible within the view range + continue; + } + if (findMatches && !findMatches.has(spanID)) { + // skip to search matches (when searching) + continue; + } + if (childrenAreHidden) { + // make sure the span is not collapsed + const { isHidden, parentIDs } = isSpanHidden(span, childrenAreHidden); + if (isHidden) { + parentIDs.forEach(id => childrenAreHidden.add(id)); + continue; + } + } + nextSpanIndex = i; + break; + } + if (!nextSpanIndex || nextSpanIndex === boundary) { + // might as well scroll to the top or bottom + nextSpanIndex = boundary - direction; + + // If there are hidden children, scroll to the last visible span + if (childrenAreHidden) { + let isFallbackHidden: boolean; + do { + const { isHidden, parentIDs } = isSpanHidden(spans[nextSpanIndex], childrenAreHidden); + if (isHidden) { + parentIDs.forEach(id => childrenAreHidden.add(id)); + nextSpanIndex--; + } + isFallbackHidden = isHidden; + } while (isFallbackHidden); + } + } + const nextRow = xrs.mapSpanIndexToRowIndex(nextSpanIndex); + this._scrollPast(nextRow, direction); + } + + /** + * Sometimes the ScrollManager is created before the trace is loaded. This + * setter allows the trace to be set asynchronously. + */ + setTrace(trace: IOtelTrace | TNil) { + this._trace = trace; + } + + /** + * `setAccessors` is bound in the ctor, so it can be passed as a prop to + * children components. + */ + setAccessors = (accessors: Accessors) => { + this._accessors = accessors; + }; + + /** + * Scrolls around one page down (0.95x). It is bounds in the ctor, so it can + * be used as a keyboard shortcut handler. + */ + scrollPageDown = () => { + if (!this._scroller || !this._accessors) { + return; + } + this._scroller.scrollBy(0.95 * this._accessors.getViewHeight(), true); + }; + + /** + * Scrolls around one page up (0.95x). It is bounds in the ctor, so it can + * be used as a keyboard shortcut handler. + */ + scrollPageUp = () => { + if (!this._scroller || !this._accessors) { + return; + } + this._scroller.scrollBy(-0.95 * this._accessors.getViewHeight(), true); + }; + + /** + * Scrolls to the next visible span, ignoring spans that do not match the + * text filter, if there is one. It is bounds in the ctor, so it can + * be used as a keyboard shortcut handler. + */ + scrollToNextVisibleSpan = () => { + this._scrollToVisibleSpan(1); + }; + + /** + * Scrolls to the previous visible span, ignoring spans that do not match the + * text filter, if there is one. It is bounds in the ctor, so it can + * be used as a keyboard shortcut handler. + */ + scrollToPrevVisibleSpan = () => { + this._scrollToVisibleSpan(-1); + }; + + scrollToFirstVisibleSpan = () => { + this._scrollToVisibleSpan(1, 0); + }; + + destroy() { + this._trace = undefined; + this._scroller = undefined; + this._accessors = undefined; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.css new file mode 100644 index 0000000000..fd06d6aba7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.css @@ -0,0 +1,105 @@ +.SearchForm--labelCount { + color: var(--text-muted); +} + +.SearchForm--hintTrigger { + border: 1px solid var(--text-muted); + border-radius: 100px; + color: var(--text-muted); + cursor: pointer; + padding: 1px; +} + +.SearchForm--hintTrigger:hover { + color: var(--text-primary); + border-color: var(--text-primary); +} + +.SearchForm--tagsHintTitle { + margin-top: 0.5em; +} + +.SearchForm--submit { + background-color: var(--interactive-primary); + color: var(--text-inverse); + float: right; +} + +.SearchForm--submit:hover { + background-color: var(--surface-primary); + float: right; +} + +.SearchForm--submit:active { + background-color: var(--surface-primary); + float: right; +} + +.SearchForm--tagsHintInfo { + padding-left: 1.7em; +} + +.SearchForm--tagsHintEg { + color: var(--text-link); +} + +.SearchForm--lookbackHint { + max-width: 250px; +} + +.SearchForm--lookbackRow { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.SearchForm--lookbackLabel { + font-weight: 600; + font-size: 14px; +} + +.SearchForm--adjustTime { + display: flex; + align-items: center; + gap: 6px; +} + +.SearchForm--adjustTimeLabel { + font-size: 11px; + color: var(--text-secondary); + white-space: nowrap; +} + +/* Reduce the default Ant Design Form.Item margin to make the form more compact */ +.SearchTracePage--find .ant-form-item { + margin-bottom: 12px; +} + +.ant-select-dropdown .rc-virtual-list-holder { + overflow-y: auto !important; +} + +/* ensure scrollbar is always visible, not just on hover */ +.ant-select-dropdown::-webkit-scrollbar, +.ant-select-dropdown .rc-virtual-list::-webkit-scrollbar, +.ant-select-dropdown .rc-virtual-list-holder::-webkit-scrollbar { + width: 8px; + background-color: var(--surface-secondary); +} + +.ant-select-dropdown::-webkit-scrollbar-thumb, +.ant-select-dropdown .rc-virtual-list::-webkit-scrollbar-thumb, +.ant-select-dropdown .rc-virtual-list-holder::-webkit-scrollbar-thumb { + background-color: var(--text-secondary); + border-radius: 4px; +} + +/* Reduce vertical spacing between form rows */ +.SearchForm--compact .ant-form-item { + margin-bottom: 6px; +} + +.SearchForm--compact .ant-form-item-label { + padding-bottom: 4px; +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.tsx new file mode 100644 index 0000000000..03d77f96c7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/SearchForm.tsx @@ -0,0 +1,910 @@ +import React, { useState, useCallback, useMemo, useEffect } from 'react'; +import { Input, Button, Popover, Select, Row, Col, Form, Switch } from 'antd'; +import _get from 'lodash/get'; +import logfmtParser from 'logfmt/lib/logfmt_parser'; +import { stringify as logfmtStringify } from 'logfmt/lib/stringify'; +import dayjs from 'dayjs'; +import memoizeOne from 'memoize-one'; +import queryString from 'query-string'; +import { IoHelp } from 'react-icons/io5'; +import { connect, ConnectedProps } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import store from 'store'; + +import * as jaegerApiActions from '../../actions/jaeger-api'; +import { formatDate, formatTime } from '../../utils/date'; +import { + DEFAULT_OPERATION, + DEFAULT_LIMIT, + DEFAULT_LOOKBACK, + CHANGE_SERVICE_ACTION_TYPE, +} from '../../constants/search-form'; +import { getConfigValue } from '../../utils/config/get-config'; +import SearchableSelect from '../common/SearchableSelect'; +import './SearchForm.css'; +import ValidatedFormField from '../../utils/ValidatedFormField'; +import { ReduxState } from '../../types'; +import { SearchQuery } from '../../types/search'; + +const FormItem = Form.Item; +const Option = Select.Option; + +const ADJUST_TIME_ENABLED_KEY = 'jaeger-ui/search-adjust-time-enabled'; + +interface TimeStampParams { + startDate: string; + startDateTime: string; + endDate: string; + endDateTime: string; +} + +interface TimeStampResult { + start: string; + end: string; +} + +export function getUnixTimeStampInMSFromForm({ + startDate, + startDateTime, + endDate, + endDateTime, +}: TimeStampParams): TimeStampResult { + const start = `${startDate} ${startDateTime}`; + const end = `${endDate} ${endDateTime}`; + return { + start: `${dayjs(start, 'YYYY-MM-DD HH:mm').valueOf()}000`, + end: `${dayjs(end, 'YYYY-MM-DD HH:mm').valueOf()}000`, + }; +} + +export function convTagsLogfmt(tags: string | null | undefined): string | null { + if (!tags) { + return null; + } + const data = logfmtParser.parse(tags); + Object.keys(data).forEach(key => { + const value = data[key]; + // make sure all values are strings + // https://github.com/jaegertracing/jaeger/issues/550#issuecomment-352850811 + if (typeof value !== 'string') { + data[key] = String(value); + } + }); + return JSON.stringify(data); +} + +export function lookbackToTimestamp(lookback: string, from: Date | number): number { + const unit = lookback.substr(-1) as any; // dayjs ManipulateType + return dayjs(from).subtract(parseInt(lookback, 10), unit).valueOf() * 1000; +} + +interface ILookbackOption { + label: string; + value: string; +} + +const lookbackOptions: ILookbackOption[] = [ + { + label: '5 Minutes', + value: '5m', + }, + { + label: '15 Minutes', + value: '15m', + }, + { + label: '30 Minutes', + value: '30m', + }, + { + label: 'Hour', + value: '1h', + }, + { + label: '2 Hours', + value: '2h', + }, + { + label: '3 Hours', + value: '3h', + }, + { + label: '6 Hours', + value: '6h', + }, + { + label: '12 Hours', + value: '12h', + }, + { + label: '24 Hours', + value: '24h', + }, + { + label: '2 Days', + value: '2d', + }, + { + label: '3 Days', + value: '3d', + }, + { + label: '5 Days', + value: '5d', + }, + { + label: '7 Days', + value: '7d', + }, + { + label: '2 Weeks', + value: '2w', + }, + { + label: '3 Weeks', + value: '3w', + }, + { + label: '4 Weeks', + value: '4w', + }, +]; + +export const optionsWithinMaxLookback = memoizeOne((maxLookback: ILookbackOption) => { + const now = new Date(); + const minTimestamp = lookbackToTimestamp(maxLookback.value, now); + const lookbackToTimestampMap = new Map(); + const options = lookbackOptions.filter(({ value }) => { + const lookbackTimestamp = lookbackToTimestamp(value, now); + lookbackToTimestampMap.set(value, lookbackTimestamp); + return lookbackTimestamp >= minTimestamp; + }); + const lastInRangeIndex = options.length - 1; + const lastInRangeOption = options[lastInRangeIndex]; + if (lastInRangeOption.label !== maxLookback.label) { + if (lookbackToTimestampMap.get(lastInRangeOption.value) !== minTimestamp) { + options.push(maxLookback); + } else { + options.splice(lastInRangeIndex, 1, maxLookback); + } + } + return options.map(({ label, value }) => ( + + )); +}); + +export function traceIDsToQuery(traceIDs: string | null | undefined): string[] | null { + if (!traceIDs) { + return null; + } + return traceIDs.split(','); +} + +export const placeholderDurationFields = 'e.g. 1.2s, 100ms, 500us'; + +interface ValidationError { + content: string; + title: string; +} + +export function validateDurationFields(value: string | null | undefined): ValidationError | undefined { + if (!value) return undefined; + return /\d[\d.]*( us|ms|s|m|h)$/.test(value) + ? undefined + : { + content: `Please enter a number followed by a duration unit, ${placeholderDurationFields}`, + title: 'Please match the requested format.', + }; +} + +interface QueryParams { + start?: string; + end?: string; +} + +interface FormDates { + queryStartDate?: string; + queryStartDateTime?: string; + queryEndDate?: string; + queryEndDateTime?: string; +} + +export function convertQueryParamsToFormDates({ start, end }: QueryParams): FormDates { + let queryStartDate: string | undefined; + let queryStartDateTime: string | undefined; + let queryEndDate: string | undefined; + let queryEndDateTime: string | undefined; + if (end) { + const endUnixNs = parseInt(end, 10); + queryEndDate = formatDate(endUnixNs); + queryEndDateTime = formatTime(endUnixNs); + } + if (start) { + const startUnixNs = parseInt(start, 10); + queryStartDate = formatDate(startUnixNs); + queryStartDateTime = formatTime(startUnixNs); + } + + return { + queryStartDate, + queryStartDateTime, + queryEndDate, + queryEndDateTime, + }; +} + +// Applies time adjustment to shift end time back by the specified duration +// This helps avoid incomplete traces that may still be receiving spans +export function applyAdjustTime(endTimestamp: number, adjustTime: string | null | undefined): number { + if (!adjustTime) { + return endTimestamp; + } + const adjustedEnd = lookbackToTimestamp(adjustTime, endTimestamp / 1000); + return adjustedEnd; +} + +interface ISearchFormFields { + resultsLimit: string; + service: string; + startDate: string; + startDateTime: string; + endDate: string; + endDateTime: string; + operation: string; + tags?: string; + minDuration?: string; + maxDuration?: string; + lookback: string; +} + +type SearchTracesFunction = typeof jaegerApiActions.searchTraces; + +export function submitForm( + fields: ISearchFormFields, + searchTraces: SearchTracesFunction, + adjustTime: string | null | undefined, + adjustTimeEnabled: boolean +): void { + const { + resultsLimit, + service, + startDate, + startDateTime, + endDate, + endDateTime, + operation, + tags, + minDuration, + maxDuration, + lookback, + } = fields; + // Note: traceID is ignored when the form is submitted + store.set('lastSearch', { service, operation }); + + let start: string | number; + let end: number; + if (lookback !== 'custom') { + const now = new Date(); + start = String(lookbackToTimestamp(lookback, now)); + end = now.valueOf() * 1000; + } else { + const times = getUnixTimeStampInMSFromForm({ + startDate, + startDateTime, + endDate, + endDateTime, + }); + start = times.start; + end = parseInt(times.end, 10); + } + + // Apply time adjustment to exclude very recent traces that may be incomplete + if (adjustTimeEnabled) { + end = applyAdjustTime(end, adjustTime); + } + + searchTraces({ + service, + operation: operation !== DEFAULT_OPERATION ? operation : undefined, + limit: resultsLimit, + lookback, + start: String(start), + end: String(end), + tags: convTagsLogfmt(tags) || undefined, + minDuration: minDuration || null, + maxDuration: maxDuration || null, + } as SearchQuery); +} + +interface IServiceWithOperations { + name: string; + operations?: string[]; +} + +interface ISearchFormImplProps { + invalid?: boolean; + submitting?: boolean; + searchMaxLookback?: ILookbackOption; + searchAdjustEndTime?: string; + useOtelTerms?: boolean; + services: IServiceWithOperations[]; + initialValues?: Partial & { traceIDs?: string | null }; + searchTraces: SearchTracesFunction; + changeServiceHandler: (service: string) => void; + submitFormHandler: ( + fields: ISearchFormFields, + adjustEndTime: string | null | undefined, + adjustTimeEnabled: boolean + ) => void; +} + +interface ISearchFormImplState { + formData: Partial; + adjustTimeEnabled: boolean; +} + +export const SearchFormImpl: React.FC = ({ + invalid = false, + submitting = false, + searchMaxLookback, + searchAdjustEndTime, + useOtelTerms, + services = [], + initialValues, + changeServiceHandler, + submitFormHandler, +}) => { + const [formData, setFormData] = useState>(() => ({ + service: initialValues?.service, + operation: initialValues?.operation, + tags: initialValues?.tags, + lookback: initialValues?.lookback, + startDate: initialValues?.startDate, + startDateTime: initialValues?.startDateTime, + endDate: initialValues?.endDate, + endDateTime: initialValues?.endDateTime, + minDuration: initialValues?.minDuration, + maxDuration: initialValues?.maxDuration, + resultsLimit: initialValues?.resultsLimit, + })); + + useEffect(() => { + // 只在 “initialValues 真的变化” 时覆盖表单 + setFormData({ + service: initialValues?.service, + operation: initialValues?.operation, + tags: initialValues?.tags, + lookback: initialValues?.lookback, + startDate: initialValues?.startDate, + startDateTime: initialValues?.startDateTime, + endDate: initialValues?.endDate, + endDateTime: initialValues?.endDateTime, + minDuration: initialValues?.minDuration, + maxDuration: initialValues?.maxDuration, + resultsLimit: initialValues?.resultsLimit, + }); + }, [ + initialValues?.service, + initialValues?.operation, + initialValues?.tags, + initialValues?.lookback, + initialValues?.startDate, + initialValues?.startDateTime, + initialValues?.endDate, + initialValues?.endDateTime, + initialValues?.minDuration, + initialValues?.maxDuration, + initialValues?.resultsLimit, + ]); + + const [adjustTimeEnabled, setAdjustTimeEnabled] = useState(() => { + const storedAdjustTimeEnabled = store.get(ADJUST_TIME_ENABLED_KEY); + return storedAdjustTimeEnabled !== undefined ? storedAdjustTimeEnabled : Boolean(searchAdjustEndTime); + }); + + const handleChange = useCallback( + (fieldData: Partial) => { + setFormData(prev => { + const nextFormData = { ...prev, ...fieldData }; + if (fieldData.service) { + changeServiceHandler(fieldData.service); + nextFormData.operation = DEFAULT_OPERATION; + } + return nextFormData; + }); + }, + [changeServiceHandler] + ); + + const handleAdjustTimeToggle = useCallback((checked: boolean) => { + setAdjustTimeEnabled(checked); + store.set(ADJUST_TIME_ENABLED_KEY, checked); + }, []); + + const handleSubmit = useCallback( + (e: React.FormEvent) => { + e.preventDefault(); + submitFormHandler(formData as ISearchFormFields, searchAdjustEndTime, adjustTimeEnabled); + }, + [formData, searchAdjustEndTime, adjustTimeEnabled, submitFormHandler] + ); + + const { service: selectedService, lookback: selectedLookback } = formData; + const selectedServicePayload = services.find(s => s.name === selectedService); + const opsForSvc = (selectedServicePayload && selectedServicePayload.operations) || []; + const noSelectedService = selectedService === '-' || !selectedService; + const tz = selectedLookback === 'custom' ? new Date().toTimeString().replace(/^.*?GMT/, 'UTC') : null; + const invalidDuration = + validateDurationFields(formData.minDuration) || validateDurationFields(formData.maxDuration); + + return ( +
+ {/* Row 1: Service / Operation / Tags */} + + + + Service ({services.length}) + + } + > + handleChange({ service: value })} + > + {services.map(service => ( + + ))} + + + + + + + {useOtelTerms ? 'Span Name' : 'Operation'}{' '} + ({opsForSvc ? opsForSvc.length : 0}) + + } + > + handleChange({ operation: value })} + > + {['all'].concat(opsForSvc).map(op => ( + + ))} + + + + + + + {useOtelTerms ? 'Attributes' : 'Tags'}{' '} + + Values should be in the{' '} + + logfmt + {' '} + format. + + } + content={ +
+
    +
  • Use space for AND conjunctions.
  • +
  • + Values containing whitespace or equal-sign '=' should be enclosed in quotes. +
  • +
  • + Elasticsearch/OpenSearch storage supports regex query, therefore{' '} + + reserved characters + {' '} + need to be escaped for exact match queries. +
  • +
+

Examples:

+
    +
  • + error=true +
  • +
  • + + db.statement="select * from User" + +
  • +
  • + + http.url="http://0.0.0.0:8081/customer\\?customer=123" + +
    + Note: when using Elasticsearch/OpenSearch the{' '} + + regex-reserved + {' '} + character "?" must be + escaped with "\\". +
    +
  • +
+
+ } + > + +
+ + } + > + handleChange({ tags: e.target.value })} + allowClear + /> +
+ +
+ + {/* Row 2: Lookback / Max / Min / Limit */} + + + + handleChange({ lookback: value })} + > + {searchMaxLookback && optionsWithinMaxLookback(searchMaxLookback)} + + + + + {searchAdjustEndTime && ( +
+ Adjusted -{searchAdjustEndTime} + + + When enabled, search end time is adjusted back by {searchAdjustEndTime} to exclude very + recent traces that may still be receiving spans. +
+ } + > + + + + )} + + + + + ) => + handleChange({ maxDuration: e.target.value }) + } + /> + + + + + + ) => + handleChange({ minDuration: e.target.value }) + } + /> + + + + + + handleChange({ resultsLimit: e.target.value })} + /> + + +
+ + {/* Custom time range fields */} + {selectedLookback === 'custom' && ( + + + + Start Time{' '} + + Times are expressed in {tz} + + } + > + + + + } + > + + + handleChange({ startDate: e.target.value })} + /> + + + + handleChange({ startDateTime: e.target.value })} + /> + + + + + + + + End Time{' '} + + Times are expressed in {tz} + + } + > + + + + } + > + + + handleChange({ endDate: e.target.value })} + /> + + + + handleChange({ endDateTime: e.target.value })} + /> + + + + + + )} + + +
+ ); + +}; + +export function mapStateToProps(state: ReduxState) { + const { + service, + limit, + start, + end, + operation, + tag: tagParams, + tags: logfmtTags, + maxDuration, + minDuration, + lookback, + traceID: traceIDParams, + } = queryString.parse(state.router.location.search); + + const nowInMicroseconds = dayjs().valueOf() * 1000; + const today = formatDate(nowInMicroseconds); + const currentTime = formatTime(nowInMicroseconds); + const lastSearch = store.get('lastSearch') as { service?: string; operation?: string } | undefined; + let lastSearchService: string | undefined; + let lastSearchOperation: string | undefined; + + if (lastSearch) { + // last search is only valid if the service is in the list of services + const { operation: lastOp, service: lastSvc } = lastSearch; + if (lastSvc && lastSvc !== '-') { + if (state.services.services && state.services.services.includes(lastSvc)) { + lastSearchService = lastSvc; + if (lastOp && lastOp !== '-') { + const ops = state.services.operationsForService[lastSvc]; + if (lastOp === 'all' || (ops && ops.includes(lastOp))) { + lastSearchOperation = lastOp; + } + } + } + } + } + + const { queryStartDate, queryStartDateTime, queryEndDate, queryEndDateTime } = + convertQueryParamsToFormDates({ + start: start as string | undefined, + end: end as string | undefined, + }); + + let tags: string | undefined; + // continue to parse tagParams to remain backward compatible with older URLs + // but, parse to logfmt format instead of the former "key:value|k2:v2" + if (tagParams) { + function convFormerTag(accum: Record, value: string): boolean { + const parts = value.split(':', 2); + const key = parts[0]; + if (key) { + accum[key] = parts[1] == null ? '' : parts[1]; + return true; + } + return false; + } + + let data: Record | null = null; + if (Array.isArray(tagParams)) { + data = tagParams + .filter((str): str is string => !!str) // skip null, undefined, empty strings + .reduce( + (accum, str) => { + convFormerTag(accum, str); + return accum; + }, + {} as Record + ); + } else if (typeof tagParams === 'string') { + const target: Record = {}; + data = convFormerTag(target, tagParams) ? target : null; + } + if (data) { + try { + tags = logfmtStringify(data); + } catch (_) { + tags = 'Parse Error'; + } + } else { + tags = 'Parse Error'; + } + } + if (logfmtTags) { + let data: Record; + try { + data = JSON.parse(logfmtTags as string); + tags = logfmtStringify(data); + } catch (_) { + tags = 'Parse Error'; + } + } + let traceIDs: string | undefined; + if (traceIDParams) { + traceIDs = traceIDParams instanceof Array ? traceIDParams.join(',') : (traceIDParams as string); + } + + return { + destroyOnUnmount: false, + initialValues: { + service: (service as string | undefined) || lastSearchService || '-', + resultsLimit: (limit as string | undefined) || String(DEFAULT_LIMIT), + lookback: (lookback as string | undefined) || DEFAULT_LOOKBACK, + startDate: queryStartDate || today, + startDateTime: queryStartDateTime || '00:00', + endDate: queryEndDate || today, + endDateTime: queryEndDateTime || currentTime, + operation: (operation as string | undefined) || lastSearchOperation || DEFAULT_OPERATION, + tags, + minDuration: (minDuration as string | undefined) || undefined, + maxDuration: (maxDuration as string | undefined) || undefined, + traceIDs: traceIDs || null, + }, + searchMaxLookback: _get(state, 'config.search.maxLookback'), + searchAdjustEndTime: _get(state, 'config.search.adjustEndTime'), + useOtelTerms: _get(state, 'config.useOpenTelemetryTerms'), + }; +} + +export function mapDispatchToProps(dispatch: Dispatch) { + const { searchTraces } = bindActionCreators(jaegerApiActions, dispatch); + return { + searchTraces, + changeServiceHandler: (service: string) => + dispatch({ + type: CHANGE_SERVICE_ACTION_TYPE, + payload: service, + }), + submitFormHandler: ( + fields: ISearchFormFields, + adjustEndTime: string | null | undefined, + adjustTimeEnabled: boolean + ) => submitForm(fields, searchTraces, adjustEndTime || null, adjustTimeEnabled), + }; +} + +const connector = connect(mapStateToProps, mapDispatchToProps); +type PropsFromRedux = ConnectedProps; + +export default connector(SearchFormImpl); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Trace.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Trace.tsx new file mode 100644 index 0000000000..a5f95bc96d --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Trace.tsx @@ -0,0 +1,356 @@ +import * as React from 'react'; +import { InputRef } from 'antd'; +import { Location, History as RouterHistory } from 'history'; +import _clamp from 'lodash/clamp'; +import _get from 'lodash/get'; +import _mapValues from 'lodash/mapValues'; +import _memoize from 'lodash/memoize'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; + +import { cancel as cancelScroll, scrollBy, scrollTo } from './scroll-page'; +import ScrollManager from './ScrollManager'; +import TracePageHeader from './TracePageHeader'; +import TraceTimelineViewer from './TraceTimelineViewer'; +import { actions as timelineActions } from './TraceTimelineViewer/duck'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate, ETraceViewType } from './types'; +import { getLocation } from './url'; +import ErrorMessage from '../common/ErrorMessage'; +import LoadingIndicator from '../common/LoadingIndicator'; +import { extractUiFindFromState } from '../common/UiFindInput'; +import * as jaegerApiActions from '../../actions/jaeger-api'; +import { fetchedState } from '../../constants'; +import { FetchedTrace, LocationState, ReduxState, TNil } from '../../types'; +import { IOtelTrace } from '../../types/otel'; +import { EmbeddedState } from '../../types/embedded'; +import filterSpans from '../../utils/filter-spans'; +import updateUiFind from '../../utils/update-ui-find'; +import { StorageCapabilities, TraceGraphConfig } from '../../types/config'; + +import './index.css'; +import withRouteProps from '../../utils/withRouteProps'; + +type TDispatchProps = { + fetchTrace: (id: string) => void; + focusUiFindMatches: (trace: IOtelTrace, uiFind: string | TNil) => void; +}; + +type TOwnProps = { + history: RouterHistory; + location: Location; + params: { id: string }; +}; + +type TReduxProps = { + storageCapabilities: StorageCapabilities | TNil; + criticalPathEnabled: boolean; + embedded: null | EmbeddedState; + id: string; + searchUrl: null | string; + disableJsonView: boolean; + trace: FetchedTrace | TNil; + uiFind: string | TNil; + traceGraphConfig?: TraceGraphConfig; + useOtelTerms: boolean; +}; + +type TProps = TDispatchProps & TOwnProps & TReduxProps; + +type TState = { + headerHeight: number | TNil; + slimView: boolean; + viewType: ETraceViewType; + viewRange: IViewRange; +}; + +// export for tests +export const VIEW_MIN_RANGE = 0.01; +const VIEW_CHANGE_BASE = 0.005; +const VIEW_CHANGE_FAST = 0.05; + +// export for tests +export const shortcutConfig: { [name: string]: [number, number] } = { + panLeft: [-VIEW_CHANGE_BASE, -VIEW_CHANGE_BASE], + panLeftFast: [-VIEW_CHANGE_FAST, -VIEW_CHANGE_FAST], + panRight: [VIEW_CHANGE_BASE, VIEW_CHANGE_BASE], + panRightFast: [VIEW_CHANGE_FAST, VIEW_CHANGE_FAST], + zoomIn: [VIEW_CHANGE_BASE, -VIEW_CHANGE_BASE], + zoomInFast: [VIEW_CHANGE_FAST, -VIEW_CHANGE_FAST], + zoomOut: [-VIEW_CHANGE_BASE, VIEW_CHANGE_BASE], + zoomOutFast: [-VIEW_CHANGE_FAST, VIEW_CHANGE_FAST], +}; + +// export for tests +export class TracePageImpl extends React.PureComponent { + state: TState; + + _headerElm: HTMLElement | TNil; + _filterSpans: typeof filterSpans; + _searchBar = React.createRef(); + _scrollManager: ScrollManager; + + constructor(props: TProps) { + super(props); + const { embedded, trace } = props; + this.state = { + headerHeight: null, + slimView: Boolean(embedded && embedded.timeline.collapseTitle), + viewType: ETraceViewType.TraceTimelineViewer, + viewRange: { + time: { + current: [0, 1], + }, + }, + }; + this._headerElm = null; + this._filterSpans = _memoize( + filterSpans, + // Do not use the memo if the filter text or trace has changed. + // trace.data.spans is populated after the initial render via mutation. + textFilter => + `${textFilter} ${_get(this.props.trace, 'traceID')} ${_get(this.props.trace, 'data.spans.length')}` + ); + this._scrollManager = new ScrollManager(trace && trace.data ? trace.data.asOtelTrace() : undefined, { + scrollBy, + scrollTo, + }); + } + + componentDidMount() { + this.ensureTraceFetched(); + this.updateViewRangeTime(0, 1); + /* istanbul ignore if */ + if (!this._scrollManager) { + throw new Error('Invalid state - scrollManager is unset'); + } + } + + componentDidUpdate({ id: prevID }: TProps) { + const { id, trace } = this.props; + + this._scrollManager.setTrace(trace && trace.data ? trace.data.asOtelTrace() : undefined); + + this.setHeaderHeight(this._headerElm); + if (!trace) { + this.ensureTraceFetched(); + return; + } + if (prevID !== id) { + this.updateViewRangeTime(0, 1); + this.clearSearch(); + } + } + + componentWillUnmount() { + cancelScroll(); + this._scrollManager.destroy(); + this._scrollManager = new ScrollManager(undefined, { + scrollBy, + scrollTo, + }); + } + + _adjustViewRange(startChange: number, endChange: number, trackSrc: string) { + const [viewStart, viewEnd] = this.state.viewRange.time.current; + let start = _clamp(viewStart + startChange, 0, 0.99); + let end = _clamp(viewEnd + endChange, 0.01, 1); + if (end - start < VIEW_MIN_RANGE) { + if (startChange < 0 && endChange < 0) { + end = start + VIEW_MIN_RANGE; + } else if (startChange > 0 && endChange > 0) { + end = start + VIEW_MIN_RANGE; + } else { + const center = viewStart + (viewEnd - viewStart) / 2; + start = center - VIEW_MIN_RANGE / 2; + end = center + VIEW_MIN_RANGE / 2; + } + } + this.updateViewRangeTime(start, end, trackSrc); + } + + setHeaderHeight = (elm: HTMLElement | TNil) => { + this._headerElm = elm; + if (elm) { + if (this.state.headerHeight !== elm.clientHeight) { + this.setState({ headerHeight: elm.clientHeight }); + } + } else if (this.state.headerHeight) { + this.setState({ headerHeight: null }); + } + }; + + clearSearch = () => { + const { history, location } = this.props; + updateUiFind({ + history, + location, + }); + if (this._searchBar.current) this._searchBar.current.blur(); + }; + + focusOnSearchBar = () => { + if (this._searchBar.current) this._searchBar.current.focus(); + }; + + updateViewRangeTime: TUpdateViewRangeTimeFunction = (start: number, end: number, trackSrc?: string) => { + const current: [number, number] = [start, end]; + const time = { current }; + this.setState((state: TState) => ({ viewRange: { ...state.viewRange, time } })); + }; + + updateNextViewRangeTime = (update: ViewRangeTimeUpdate) => { + this.setState((state: TState) => { + const time = { ...state.viewRange.time, ...update }; + return { viewRange: { ...state.viewRange, time } }; + }); + }; + + toggleSlimView = () => { + const { slimView } = this.state; + this.setState({ slimView: !slimView }); + }; + + setTraceView = (viewType: ETraceViewType) => { + }; + + ensureTraceFetched() { + const { fetchTrace, location, trace, id } = this.props; + if (!trace) { + fetchTrace(id); + return; + } + const { history } = this.props; + if (id && id !== id.toLowerCase()) { + history.replace(getLocation(id.toLowerCase(), location.state)); + } + } + + focusUiFindMatches = () => { + const { trace, focusUiFindMatches, uiFind } = this.props; + if (trace && trace.data) { + focusUiFindMatches(trace.data.asOtelTrace(), uiFind); + } + }; + + nextResult = () => { + this._scrollManager.scrollToNextVisibleSpan(); + }; + + prevResult = () => { + this._scrollManager.scrollToPrevVisibleSpan(); + }; + + render() { + const { + embedded, + id, + uiFind, + trace, + disableJsonView, + location: { state: locationState }, + } = this.props; + const { slimView, viewType, headerHeight, viewRange } = this.state; + if (!trace || trace.state === fetchedState.LOADING) { + return ; + } + const { data } = trace; + if (trace.state === fetchedState.ERROR || !data) { + return ; + } + + let findCount = 0; + let spanFindMatches: Set | null | undefined; + + const isEmbedded = true; + const headerProps = { + focusUiFindMatches: this.focusUiFindMatches, + slimView, + textFilter: uiFind, + viewType, + viewRange, + canCollapse: !embedded || !embedded.timeline.hideSummary || !embedded.timeline.hideMinimap, + clearSearch: this.clearSearch, + hideMap: Boolean( + viewType !== ETraceViewType.TraceTimelineViewer || (embedded && embedded.timeline.hideMinimap) + ), + hideSummary: Boolean(embedded && embedded.timeline.hideSummary), + nextResult: this.nextResult, + onSlimViewClicked: this.toggleSlimView, + onTraceViewChange: this.setTraceView, + prevResult: this.prevResult, + ref: this._searchBar, + resultCount: findCount, + disableJsonView, + showArchiveButton: false, + showShortcutsHelp: !isEmbedded, + showStandaloneLink: isEmbedded, + showViewOptions: !isEmbedded, + toSearch: (locationState && locationState.fromSearch) || null, + trace: data.asOtelTrace(), + updateNextViewRangeTime: this.updateNextViewRangeTime, + updateViewRangeTime: this.updateViewRangeTime, + useOtelTerms: this.props.useOtelTerms, + }; + + let view; + if (ETraceViewType.TraceTimelineViewer === viewType && headerHeight) { + view = ( + + ); + } + + return ( +
+
+ +
+ {headerHeight ?
{view}
: null} +
+ ); + } +} + +// export for tests +export function mapStateToProps(state: ReduxState, ownProps: TOwnProps): TReduxProps { + const { id } = ownProps.params; + const { config, embedded, router } = state; + const { traces } = state.trace; + const trace = id ? traces[id] : null; + const storageCapabilities = config.storageCapabilities; + const { disableJsonView, criticalPathEnabled } = config; + const { state: locationState } = router.location; + const searchUrl = (locationState && locationState.fromSearch) || null; + const { traceGraph: traceGraphConfig } = config; + + return { + ...extractUiFindFromState(state), + storageCapabilities, + criticalPathEnabled, + embedded, + id, + searchUrl, + disableJsonView, + trace, + traceGraphConfig, + useOtelTerms: config.useOpenTelemetryTerms, + }; +} + +// export for tests +export function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { + const { fetchTrace } = bindActionCreators(jaegerApiActions, dispatch); + const { focusUiFindMatches } = bindActionCreators(timelineActions, dispatch); + return { fetchTrace, focusUiFindMatches }; +} + +export default withRouteProps(connect(mapStateToProps, mapDispatchToProps)(TracePageImpl)); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.css new file mode 100644 index 0000000000..93f15f3301 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.css @@ -0,0 +1,6 @@ +.CanvasSpanGraph { + /* background: var(--surface-primary); */ + height: 60px; + position: absolute; + width: 100%; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx new file mode 100644 index 0000000000..21f4233f59 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/CanvasSpanGraph.tsx @@ -0,0 +1,33 @@ +import React, { useRef, useEffect } from 'react'; + +import renderIntoCanvas from './render-into-canvas'; +import colorGenerator from '../../../../utils/color-generator'; +import { useThemeMode } from '../../../App/ThemeProvider'; + +import './CanvasSpanGraph.css'; + +type CanvasSpanGraphProps = { + items: { valueWidth: number; valueOffset: number; serviceName: string }[]; + valueWidth: number; +}; + +export const getColor = (hex: string) => colorGenerator.getRgbColorByKey(hex); + +const CanvasSpanGraph: React.FC = ({ items, valueWidth }) => { + const canvasRef = useRef(null); + const { mode } = useThemeMode(); + + useEffect(() => { + if (canvasRef.current) { + const backgroundColor = window + .getComputedStyle(document.documentElement) + .getPropertyValue('--surface-primary') + .trim(); + renderIntoCanvas(canvasRef.current, items, valueWidth, getColor, backgroundColor || undefined); + } + }, [items, valueWidth, mode]); + + return ; +}; + +export default CanvasSpanGraph; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.css new file mode 100644 index 0000000000..01eb74a56f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.css @@ -0,0 +1,4 @@ +.GraphTick { + stroke: var(--border-strong); + stroke-width: 1px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.tsx new file mode 100644 index 0000000000..d4c10f3e9d --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/GraphTicks.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +import './GraphTicks.css'; + +type GraphTicksProps = { + numTicks: number; +}; + +export default function GraphTicks(props: GraphTicksProps) { + const { numTicks } = props; + const ticks = []; + // i starts at 1, limit is `i < numTicks` so the first and last ticks aren't drawn + for (let i = 1; i < numTicks; i++) { + const x = `${(i / numTicks) * 100}%`; + ticks.push(); + } + + return ( + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.css new file mode 100644 index 0000000000..4032e00f8e --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.css @@ -0,0 +1,30 @@ +.Scrubber--handleExpansion { + cursor: col-resize; + fill-opacity: 0; + fill: var(--interactive-primary); +} + +.Scrubber.isDragging .Scrubber--handleExpansion, +.Scrubber--handles:hover > .Scrubber--handleExpansion { + fill-opacity: 1; +} + +.Scrubber--handle { + cursor: col-resize; + fill: var(--border-strongest); +} + +.Scrubber.isDragging .Scrubber--handle, +.Scrubber--handles:hover > .Scrubber--handle { + fill: var(--interactive-primary); +} + +.Scrubber--line { + pointer-events: none; + stroke: var(--border-strongest); +} + +.Scrubber.isDragging > .Scrubber--line, +.Scrubber--handles:hover + .Scrubber--line { + stroke: var(--interactive-primary); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.tsx new file mode 100644 index 0000000000..c34174b1ae --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/Scrubber.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import cx from 'classnames'; + +import './Scrubber.css'; + +type ScrubberProps = { + isDragging: boolean; + position: number; + onMouseDown: React.MouseEventHandler; + onMouseEnter: React.MouseEventHandler; + onMouseLeave: React.MouseEventHandler; +}; + +export default function Scrubber({ + isDragging, + onMouseDown, + onMouseEnter, + onMouseLeave, + position, +}: ScrubberProps) { + const xPercent = `${position * 100}%`; + const className = cx('Scrubber', { isDragging }); + return ( + + + {/* handleExpansion is only visible when `isDragging` is true */} + + + + + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/SpanGraph.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/SpanGraph.css new file mode 100644 index 0000000000..b393756022 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/SpanGraph.css @@ -0,0 +1,3 @@ +.SpanGraph { + background-color: var(--surface-secondary); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.css new file mode 100644 index 0000000000..dd1ff15fab --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.css @@ -0,0 +1,11 @@ +.TickLabels { + height: 1rem; + position: relative; +} + +.TickLabels--label { + color: var(--text-secondary); + font-size: 0.7rem; + position: absolute; + user-select: none; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.tsx new file mode 100644 index 0000000000..f496bbd322 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/TickLabels.tsx @@ -0,0 +1,28 @@ +import React from 'react'; + +import { formatDuration } from '../../../../utils/date'; +import { Microseconds } from '../../../../types/units'; + +import './TickLabels.css'; + +type TickLabelsProps = { + numTicks: number; + duration: number; +}; + +export default function TickLabels(props: TickLabelsProps) { + const { numTicks, duration } = props; + + const ticks = []; + for (let i = 0; i < numTicks + 1; i++) { + const portion = i / numTicks; + const style = portion === 1 ? { right: '0%' } : { left: `${portion * 100}%` }; + ticks.push( +
+ {formatDuration((duration * portion) as Microseconds)} +
+ ); + } + + return
{ticks}
; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.css new file mode 100644 index 0000000000..0a56043acb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.css @@ -0,0 +1,59 @@ +.ViewingLayer { + cursor: vertical-text; + position: relative; + z-index: 1; +} + +.ViewingLayer--graph { + border: 1px solid var(--border-strongest); + /* need !important here to overcome something from semantic UI */ + overflow: visible !important; + position: relative; + transform-origin: 0 0; + width: 100%; +} + +.ViewingLayer--inactive { + fill: var(--span-graph-inactive); +} + +.ViewingLayer--cursorGuide { + stroke: #f44; + stroke-width: 1; +} + +.ViewingLayer--draggedShift { + fill-opacity: 0.2; +} + +.ViewingLayer--draggedShift.isShiftDrag, +.ViewingLayer--draggedEdge.isShiftDrag { + fill: #44f; +} + +.ViewingLayer--draggedShift.isReframeDrag, +.ViewingLayer--draggedEdge.isReframeDrag { + fill: #f44; +} + +.ViewingLayer--fullOverlay { + bottom: 0; + cursor: col-resize; + left: 0; + position: fixed; + right: 0; + top: 0; + user-select: none; +} + +.ViewingLayer--resetZoom { + display: none; + position: absolute; + right: 1%; + top: 10%; + z-index: 1; +} + +.ViewingLayer:hover > .ViewingLayer--resetZoom { + display: unset; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.tsx new file mode 100644 index 0000000000..81be402ece --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/ViewingLayer.tsx @@ -0,0 +1,337 @@ +import { Button } from 'antd'; +import cx from 'classnames'; +import * as React from 'react'; + +import GraphTicks from './GraphTicks'; +import Scrubber from './Scrubber'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate } from '../../types'; +import { TNil } from '../../../../types'; +import DraggableManager, { + DraggableBounds, + DraggingUpdate, + EUpdateTypes, +} from '../../../../utils/DraggableManager'; + +import './ViewingLayer.css'; + +type ViewingLayerProps = { + height: number; + numTicks: number; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; + viewRange: IViewRange; +}; + +type ViewingLayerState = { + /** + * Cursor line should not be drawn when the mouse is over the scrubber handle. + */ + preventCursorLine: boolean; +}; + +/** + * Designate the tags for the different dragging managers. Exported for tests. + */ +export const dragTypes = { + /** + * Tag for dragging the right scrubber, e.g. end of the current view range. + */ + SHIFT_END: 'SHIFT_END', + /** + * Tag for dragging the left scrubber, e.g. start of the current view range. + */ + SHIFT_START: 'SHIFT_START', + /** + * Tag for dragging a new view range. + */ + REFRAME: 'REFRAME', +}; + +/** + * Returns the layout information for drawing the view-range differential, e.g. + * show what will change when the mouse is released. Basically, this is the + * difference from the start of the drag to the current position. + * + * @returns {{ x: string, width: string, leadginX: string }} + */ +function getNextViewLayout(start: number, position: number) { + const [left, right] = start < position ? [start, position] : [position, start]; + return { + x: `${left * 100}%`, + width: `${(right - left) * 100}%`, + leadingX: `${position * 100}%`, + }; +} + +/** + * `ViewingLayer` is rendered on top of the Canvas rendering of the minimap and + * handles showing the current view range and handles mouse UX for modifying it. + */ +export default class ViewingLayer extends React.PureComponent { + state: ViewingLayerState; + + _root: Element | TNil; + + /** + * `_draggerReframe` handles clicking and dragging on the `ViewingLayer` to + * redefined the view range. + */ + _draggerReframe: DraggableManager; + + /** + * `_draggerStart` handles dragging the left scrubber to adjust the start of + * the view range. + */ + _draggerStart: DraggableManager; + + /** + * `_draggerEnd` handles dragging the right scrubber to adjust the end of + * the view range. + */ + _draggerEnd: DraggableManager; + + constructor(props: ViewingLayerProps) { + super(props); + + this._draggerReframe = new DraggableManager({ + getBounds: this._getDraggingBounds, + onDragEnd: this._handleReframeDragEnd, + onDragMove: this._handleReframeDragUpdate, + onDragStart: this._handleReframeDragUpdate, + onMouseMove: this._handleReframeMouseMove, + onMouseLeave: this._handleReframeMouseLeave, + tag: dragTypes.REFRAME, + }); + + this._draggerStart = new DraggableManager({ + getBounds: this._getDraggingBounds, + onDragEnd: this._handleScrubberDragEnd, + onDragMove: this._handleScrubberDragUpdate, + onDragStart: this._handleScrubberDragUpdate, + onMouseEnter: this._handleScrubberEnterLeave, + onMouseLeave: this._handleScrubberEnterLeave, + tag: dragTypes.SHIFT_START, + }); + + this._draggerEnd = new DraggableManager({ + getBounds: this._getDraggingBounds, + onDragEnd: this._handleScrubberDragEnd, + onDragMove: this._handleScrubberDragUpdate, + onDragStart: this._handleScrubberDragUpdate, + onMouseEnter: this._handleScrubberEnterLeave, + onMouseLeave: this._handleScrubberEnterLeave, + tag: dragTypes.SHIFT_END, + }); + + this._root = undefined; + this.state = { + preventCursorLine: false, + }; + } + + componentWillUnmount() { + this._draggerReframe.dispose(); + this._draggerEnd.dispose(); + this._draggerStart.dispose(); + } + + _setRoot = (elm: SVGElement | TNil) => { + this._root = elm; + }; + + _getDraggingBounds = (tag: string | TNil): DraggableBounds => { + if (!this._root) { + throw new Error('invalid state'); + } + const { left: clientXLeft, width } = this._root.getBoundingClientRect(); + const [viewStart, viewEnd] = this.props.viewRange.time.current; + let maxValue = 1; + let minValue = 0; + if (tag === dragTypes.SHIFT_START) { + maxValue = viewEnd; + } else if (tag === dragTypes.SHIFT_END) { + minValue = viewStart; + } + return { clientXLeft, maxValue, minValue, width }; + }; + + _handleReframeMouseMove = ({ value }: DraggingUpdate) => { + this.props.updateNextViewRangeTime({ cursor: value }); + }; + + _handleReframeMouseLeave = () => { + this.props.updateNextViewRangeTime({ cursor: null }); + }; + + _handleReframeDragUpdate = ({ value }: DraggingUpdate) => { + const shift = value; + const { time } = this.props.viewRange; + const anchor = time.reframe ? time.reframe.anchor : shift; + const update = { reframe: { anchor, shift } }; + this.props.updateNextViewRangeTime(update); + }; + + _handleReframeDragEnd = ({ manager, value }: DraggingUpdate) => { + const { time } = this.props.viewRange; + const anchor = time.reframe ? time.reframe.anchor : value; + const [start, end] = value < anchor ? [value, anchor] : [anchor, value]; + manager.resetBounds(); + this.props.updateViewRangeTime(start, end, 'minimap'); + }; + + _handleScrubberEnterLeave = ({ type }: DraggingUpdate) => { + const preventCursorLine = type === EUpdateTypes.MouseEnter; + this.setState({ preventCursorLine }); + }; + + _handleScrubberDragUpdate = ({ event, tag, type, value }: DraggingUpdate) => { + if (type === EUpdateTypes.DragStart) { + event.stopPropagation(); + } + if (tag === dragTypes.SHIFT_START) { + this.props.updateNextViewRangeTime({ shiftStart: value }); + } else if (tag === dragTypes.SHIFT_END) { + this.props.updateNextViewRangeTime({ shiftEnd: value }); + } + }; + + _handleScrubberDragEnd = ({ manager, tag, value }: DraggingUpdate) => { + const [viewStart, viewEnd] = this.props.viewRange.time.current; + let update: [number, number]; + if (tag === dragTypes.SHIFT_START) { + update = [value, viewEnd]; + } else if (tag === dragTypes.SHIFT_END) { + update = [viewStart, value]; + } else { + // to satisfy flow + throw new Error('bad state'); + } + manager.resetBounds(); + this.setState({ preventCursorLine: false }); + this.props.updateViewRangeTime(update[0], update[1], 'minimap'); + }; + + /** + * Resets the zoom to fully zoomed out. + */ + _resetTimeZoomClickHandler = () => { + this.props.updateViewRangeTime(0, 1); + }; + + /** + * Renders the difference between where the drag started and the current + * position, e.g. the red or blue highlight. + * + * @returns React.Node[] + */ + _getMarkers(from: number, to: number, isShift: boolean) { + const layout = getNextViewLayout(from, to); + const cls = cx({ + isShiftDrag: isShift, + isReframeDrag: !isShift, + }); + return [ + , + , + ]; + } + + render() { + const { height, viewRange, numTicks } = this.props; + const { preventCursorLine } = this.state; + const { current, cursor, shiftStart, shiftEnd, reframe } = viewRange.time; + const haveNextTimeRange = shiftStart != null || shiftEnd != null || reframe != null; + const [viewStart, viewEnd] = current; + let leftInactive = 0; + if (viewStart) { + leftInactive = viewStart * 100; + } + let rightInactive = 100; + if (viewEnd) { + rightInactive = 100 - viewEnd * 100; + } + let cursorPosition: string | undefined; + if (!haveNextTimeRange && cursor != null && !preventCursorLine) { + cursorPosition = `${cursor * 100}%`; + } + + return ( +
+ {(viewStart !== 0 || viewEnd !== 1) && ( + + )} + + {leftInactive > 0 && ( + + )} + {rightInactive > 0 && ( + + )} + + {cursorPosition && ( + + )} + {shiftStart != null && this._getMarkers(viewStart, shiftStart, true)} + {shiftEnd != null && this._getMarkers(viewEnd, shiftEnd, true)} + + + {reframe != null && this._getMarkers(reframe.anchor, reframe.shift, false)} + + {/* fullOverlay updates the mouse cursor blocks mouse events */} + {haveNextTimeRange &&
} +
+ ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/index.tsx new file mode 100644 index 0000000000..800c810440 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/index.tsx @@ -0,0 +1,71 @@ +import * as React from 'react'; +import memoizeOne from 'memoize-one'; + +import CanvasSpanGraph from './CanvasSpanGraph'; +import TickLabels from './TickLabels'; +import ViewingLayer from './ViewingLayer'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate } from '../../types'; +import { IOtelSpan, IOtelTrace } from '../../../../types/otel'; + +import './SpanGraph.css'; + +const DEFAULT_HEIGHT = 60; +const TIMELINE_TICK_INTERVAL = 4; + +type SpanGraphProps = { + height?: number; + trace: IOtelTrace; + viewRange: IViewRange; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + updateNextViewRangeTime: (nextUpdate: ViewRangeTimeUpdate) => void; +}; + +type SpanItem = { + valueOffset: number; + valueWidth: number; + serviceName: string; +}; + +function getItem(span: IOtelSpan): SpanItem { + return { + valueOffset: span.relativeStartTime, + valueWidth: span.duration, + serviceName: span.resource.serviceName, + }; +} + +function getItems(trace: IOtelTrace): SpanItem[] { + return trace.spans.map(getItem); +} + +const memoizedGetItems = memoizeOne(getItems); + +export default class SpanGraph extends React.PureComponent { + static defaultProps = { + height: DEFAULT_HEIGHT, + }; + + render() { + const { height, trace, viewRange, updateNextViewRangeTime, updateViewRangeTime } = this.props; + if (!trace) { + return
; + } + + const items = memoizedGetItems(trace); + return ( +
+ +
+ + +
+
+ ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/render-into-canvas.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/render-into-canvas.ts new file mode 100644 index 0000000000..cdb98ae678 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/SpanGraph/render-into-canvas.ts @@ -0,0 +1,120 @@ +import { TNil } from '../../../../types'; + +export const DEFAULT_BG_COLOR = '#fff'; +export const ITEM_ALPHA = 0.8; +export const MIN_ITEM_HEIGHT = 2; +export const MAX_TOTAL_HEIGHT = 200; +export const MIN_ITEM_WIDTH = 10; +export const MIN_TOTAL_HEIGHT = 60; +export const MAX_ITEM_HEIGHT = 6; + +type Item = { + valueWidth: number; + valueOffset: number; + serviceName: string; +}; + +function clamp(n: number, lo: number, hi: number) { + return Math.max(lo, Math.min(hi, n)); +} + +/** + * Render items into a canvas. + * + * Features: + * - Optional transparent background (when backgroundColor is not provided) + * - High-DPI (Retina) rendering using devicePixelRatio + * - Pixel-aligned rectangles to reduce blur + */ +export default function renderIntoCanvas( + canvas: HTMLCanvasElement, + items: Item[], + totalValueWidth: number, + getFillColor: (serviceName: string) => [number, number, number], + backgroundColor?: string | null +) { + const fillCache: Map = new Map(); + + // Logical (CSS pixel) height of the canvas + const cssHeight = + items.length < MIN_TOTAL_HEIGHT + ? MIN_TOTAL_HEIGHT + : Math.min(items.length, MAX_TOTAL_HEIGHT); + + // Logical (CSS pixel) width of the canvas + // Prefer the actual layout width; fallback to window width + const cssWidth = Math.max(1, Math.floor(canvas.clientWidth || window.innerWidth)); + + // Device pixel ratio for HiDPI displays + const dpr = clamp(window.devicePixelRatio || 1, 1, 3); + + // Set the canvas CSS size (visual size) + canvas.style.width = `${cssWidth}px`; + canvas.style.height = `${cssHeight}px`; + + // Set the canvas backing store size (physical pixels) + canvas.width = Math.floor(cssWidth * dpr); + canvas.height = Math.floor(cssHeight * dpr); + + const wantsOpaqueBg = !!backgroundColor; + + // Request a 2D context + // alpha must be true if we want a transparent background + const ctx = canvas.getContext('2d', { alpha: !wantsOpaqueBg }) as CanvasRenderingContext2D | null; + if (!ctx) return; + + // Reset transform and scale for HiDPI rendering + // setTransform avoids cumulative scaling across renders + ctx.setTransform(dpr, 0, 0, dpr, 0, 0); + + // Disable image smoothing (not critical for fillRect, but harmless) + ctx.imageSmoothingEnabled = false; + + // Clear or paint the background + // Use CSS pixel coordinates here + if (wantsOpaqueBg) { + ctx.fillStyle = backgroundColor!; + ctx.fillRect(0, 0, cssWidth, cssHeight); + } else { + ctx.clearRect(0, 0, cssWidth, cssHeight); + } + + if (!items.length || !totalValueWidth) return; + + const itemHeight = Math.min( + MAX_ITEM_HEIGHT, + Math.max(MIN_ITEM_HEIGHT, cssHeight / items.length) + ); + + const itemYChange = cssHeight / items.length; + + // Draw each item + for (let i = 0; i < items.length; i++) { + const { valueWidth, valueOffset, serviceName } = items[i]; + + const x = (valueOffset / totalValueWidth) * cssWidth; + + let width = (valueWidth / totalValueWidth) * cssWidth; + if (width < MIN_ITEM_WIDTH) width = MIN_ITEM_WIDTH; + + let fillStyle = fillCache.get(serviceName); + if (!fillStyle) { + // rgba(r, g, b, alpha) + fillStyle = `rgba(${getFillColor(serviceName).concat(ITEM_ALPHA).join()})`; + fillCache.set(serviceName, fillStyle); + } + + ctx.fillStyle = fillStyle; + + // Align to physical pixels to reduce blurriness + // We are still working in CSS pixel coordinates + const px = 1 / dpr; + const xAligned = Math.round(x / px) * px; + const wAligned = Math.round(width / px) * px; + const y = i * itemYChange; + const yAligned = Math.round(y / px) * px; + const hAligned = Math.round(itemHeight / px) * px; + + ctx.fillRect(xAligned, yAligned, wAligned, hAligned); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.css new file mode 100644 index 0000000000..11f3a72f6d --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.css @@ -0,0 +1,113 @@ +.TracePageHeader { + background: rgba(255, 255, 255, 1); +} + +.TracePageHeader> :first-child { + border-bottom: 1px solid var(--border-default); +} + +.TracePageHeader> :nth-child(2) { + background-color: var(--surface-tertiary); + border-bottom: 1px solid var(--border-strong); +} + +.TracePageHeader> :last-child { + background-color: var(--surface-secondary); + border-bottom: 1px solid var(--border-strong); +} + +/* boost the specificity for cases with only one row -- bg should be white */ +.TracePageHeader>.TracePageHeader--titleRow { + align-items: center; + background-color: var(--surface-primary); + display: flex; +} + +.TracePageHeader--back { + align-items: center; + align-self: stretch; + background-color: var(--surface-secondary); + border-bottom: 1px solid var(--border-default); + border-right: 1px solid var(--border-default); + color: inherit; + display: flex; + font-size: 1.4rem; + padding: 0 1rem; + margin-bottom: -1px; +} + +.TracePageHeader--back:hover { + background-color: var(--surface-tertiary); + border-color: var(--border-strong); +} + +.TracePageHeader--titleLink { + align-items: center; + color: var(--text-primary); + display: flex; + flex: 1; +} + +.TracePageHeader--titleLink:hover * { + text-decoration: underline; +} + +.TracePageHeader--titleLink:hover>*, +.TracePageHeader--titleLink:hover small { + text-decoration: none; +} + +.TracePageHeader--detailToggle { + font-size: 2rem; + margin: 0 12px; + transition: transform 0.07s ease-out; +} + +.TracePageHeader--detailToggle.is-expanded { + transform: rotate(90deg); +} + +.TracePageHeader--title { + color: inherit; + flex: 1; + font-size: 1.7em; + line-height: 1em; + margin: 0 0 0 0.5em; + padding: 0.2em 0; +} + +.TracePageHeader--title.is-collapsible { + margin-left: 0; +} + +.TracePageHeader--overviewItems { + border-bottom: 1px solid var(--border-default); + padding: 0.25rem 0.5rem; +} + +.TracePageHeader--overviewItem--valueDetail { + color: var(--text-muted); +} + +.TracePageHeader--overviewItem--value:hover>.TracePageHeader--overviewItem--valueDetail { + color: unset; +} + +.TracePageHeader--archiveIcon { + font-size: 1.78em; + margin-right: 0.15em; +} + +.TracePageHeader--incompleteTag { + color: var(--feedback-warning); + cursor: help; + font-size: 0.85em; + font-weight: 500; + margin-left: 0.5em; +} + +.TracePageHeader--incompleteIcon { + font-size: 1.1em; + margin-right: 0.25em; + vertical-align: middle; +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.tsx new file mode 100644 index 0000000000..825c1cd5ec --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/TracePageHeader.tsx @@ -0,0 +1,182 @@ +import * as React from 'react'; +import { Button, InputRef, Tooltip } from 'antd'; +import _get from 'lodash/get'; +import _maxBy from 'lodash/maxBy'; +import { IoArrowBack, IoChevronForward, IoWarning } from 'react-icons/io5'; +import { Link } from 'react-router-dom'; + +import DocumentTitle from '../../../utils/documentTitle'; +import SpanGraph from './SpanGraph'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate, ETraceViewType } from '../types'; +import LabeledList from '../../common/LabeledList'; +import TraceName from '../../common/TraceName'; +import { TNil } from '../../../types'; +import { IOtelTrace } from '../../../types/otel'; +import { formatDatetime, formatDuration } from '../../../utils/date'; +import { getTraceLinks } from '../../../model/link-patterns'; + +import './TracePageHeader.css'; +import ExternalLinks from '../../common/ExternalLinks'; +import TraceId from '../../common/TraceId'; + +type TracePageHeaderEmbedProps = { + canCollapse: boolean; + clearSearch: () => void; + focusUiFindMatches: () => void; + hideMap: boolean; + hideSummary: boolean; + nextResult: () => void; + onSlimViewClicked: () => void; + onTraceViewChange: (viewType: ETraceViewType) => void; + prevResult: () => void; + resultCount: number; + showArchiveButton: boolean; + showShortcutsHelp: boolean; + showStandaloneLink: boolean; + disableJsonView: boolean; + showViewOptions: boolean; + slimView: boolean; + textFilter: string | TNil; + toSearch: string | null; + trace: IOtelTrace; + viewType: ETraceViewType; + updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + viewRange: IViewRange; + useOtelTerms: boolean; +}; + +export const HEADER_ITEMS = [ + { + key: 'timestamp', + label: 'Trace Start', + renderer: (trace: IOtelTrace) => { + const dateStr = formatDatetime(trace.startTime); + const match = dateStr.match(/^(.+)(\.\d+)$/); + return match ? ( + + {match[1]} + {match[2]} + + ) : ( + dateStr + ); + }, + }, + { + key: 'duration', + label: 'Duration', + renderer: (trace: IOtelTrace) => formatDuration(trace.duration), + }, + { + key: 'service-count', + label: 'Services', + renderer: (trace: IOtelTrace) => trace.services.length, + }, + { + key: 'depth', + label: 'Depth', + renderer: (trace: IOtelTrace) => _get(_maxBy(trace.spans as any[], 'depth'), 'depth', 0) + 1, + }, + { + key: 'span-count', + label: 'Total Spans', + renderer: (trace: IOtelTrace) => { + const orphanCount = trace.orphanSpanCount ?? 0; + const tooltipText = `This trace may be incomplete. ${orphanCount} span${orphanCount !== 1 ? 's' : ''} ${orphanCount !== 1 ? 'have' : 'has'} missing parent span(s).`; + return ( + + {trace.spans.length} + {orphanCount > 0 && ( + <> + {' '} + + + + Incomplete + + + + )} + + ); + }, + }, +]; + +export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwardedRef: React.Ref }) { + const { + canCollapse, + hideMap, + hideSummary, + onSlimViewClicked, + slimView, + toSearch, + trace, + updateNextViewRangeTime, + updateViewRangeTime, + viewRange, + } = props; + + if (!trace) { + return null; + } + + const links = getTraceLinks(trace); + + const summaryItems = + !hideSummary && + !slimView && + HEADER_ITEMS.map(item => { + const { renderer, ...rest } = item; + return { ...rest, value: renderer(trace) }; + }); + + const traceShortID = trace.traceID.slice(0, 7); + + const title = ( +

+ +

+ ); + + return ( +
+ +
+ {toSearch && ( + + + + )} + {links && links.length > 0 && } + {canCollapse ? ( + + + {title} + + ) : ( + title + )} +
+ {summaryItems && } + {!hideMap && !slimView && ( + + )} +
+ ); +} + +export default React.forwardRef((props: TracePageHeaderEmbedProps, ref: React.Ref) => ( + +)); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/index.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/index.ts new file mode 100644 index 0000000000..1acdabdd48 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TracePageHeader/index.ts @@ -0,0 +1 @@ +export { default } from './TracePageHeader'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/Positions.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/Positions.ts new file mode 100644 index 0000000000..af1e114c4a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/Positions.ts @@ -0,0 +1,180 @@ +type THeightGetter = (index: number) => number; + +/** + * Keeps track of the height and y-position for anything sequenctial where + * y-positions follow one-after-another and can be derived from the height of + * the prior entries. The height is known from an accessor function parameter + * to the methods that require new knowledge the heights. + * + * @export + * @class Positions + */ +export default class Positions { + /** + * Indicates how far past the explicitly required height or y-values should + * checked. + */ + bufferLen: number; + dataLen: number; + heights: number[]; + /** + * `lastI` keeps track of which values have already been visited. In many + * scenarios, values do not need to be revisited. But, revisiting is required + * when heights have changed, so `lastI` can be forced. + */ + lastI: number; + ys: number[]; + + constructor(bufferLen: number) { + this.ys = []; + this.heights = []; + this.bufferLen = bufferLen; + this.dataLen = -1; + this.lastI = -1; + } + + /** + * Used to make sure the length of y-values and heights is consistent with + * the context; in particular `lastI` needs to remain valid. + */ + profileData(dataLength: number) { + if (dataLength !== this.dataLen) { + this.dataLen = dataLength; + this.ys.length = dataLength; + this.heights.length = dataLength; + if (this.lastI >= dataLength) { + this.lastI = dataLength - 1; + } + } + } + + /** + * Calculate and save the heights and y-values, based on `heightGetter`, from + * `lastI` until the`max` index; the starting point (`lastI`) can be forced + * via the `forcedLastI` parameter. + * @param {number=} forcedLastI + */ + calcHeights(max: number, heightGetter: THeightGetter, forcedLastI?: number) { + if (forcedLastI != null) { + this.lastI = forcedLastI; + } + let _max = max + this.bufferLen; + if (_max <= this.lastI) { + return; + } + if (_max >= this.heights.length) { + _max = this.heights.length - 1; + } + let i = this.lastI; + if (this.lastI === -1) { + i = 0; + this.ys[0] = 0; + } + while (i <= _max) { + const h = (this.heights[i] = heightGetter(i)); + this.ys[i + 1] = this.ys[i] + h; + i++; + } + this.lastI = _max; + } + + /** + * Verify the height and y-values from `lastI` up to `yValue`. + */ + calcYs(yValue: number, heightGetter: THeightGetter) { + while ((this.ys[this.lastI] == null || yValue > this.ys[this.lastI]) && this.lastI < this.dataLen - 1) { + this.calcHeights(this.lastI, heightGetter); + } + } + + /** + * Get the latest height for index `_i`. If it's in new terretory + * (_i > lastI), find the heights (and y-values) leading up to it. If it's in + * known territory (_i <= lastI) and the height is different than what is + * known, recalculate subsequent y values, but don't confirm the heights of + * those items, just update based on the difference. + */ + confirmHeight(_i: number, heightGetter: THeightGetter) { + let i = _i; + if (i > this.lastI) { + this.calcHeights(i, heightGetter); + return; + } + const h = heightGetter(i); + if (h === this.heights[i]) { + return; + } + const chg = h - this.heights[i]; + this.heights[i] = h; + // shift the y positions by `chg` for all known y positions + while (++i <= this.lastI) { + this.ys[i] += chg; + } + if (this.ys[this.lastI + 1] != null) { + this.ys[this.lastI + 1] += chg; + } + } + + /** + * Given a target y-value (`yValue`), find the closest index (in the `.ys` + * array) that is prior to the y-value; e.g. map from y-value to index in + * `.ys`. + */ + findFloorIndex(yValue: number, heightGetter: THeightGetter): number { + this.calcYs(yValue, heightGetter); + + let imin = 0; + let imax = this.lastI; + + if (this.ys.length < 2 || yValue < this.ys[1]) { + return 0; + } + if (yValue > this.ys[imax]) { + return imax; + } + let i; + while (imin < imax) { + i = (imin + 0.5 * (imax - imin)) | 0; + if (yValue > this.ys[i]) { + if (yValue <= this.ys[i + 1]) { + return i; + } + imin = i; + } else if (yValue < this.ys[i]) { + if (yValue >= this.ys[i - 1]) { + return i - 1; + } + imax = i; + } else { + return i; + } + } + throw new Error(`unable to find floor index for y=${yValue}`); + } + + /** + * Get the `y` and `height` for a given row. + * + * @returns {{ height: number, y: number }} + */ + getRowPosition(index: number, heightGetter: THeightGetter) { + this.confirmHeight(index, heightGetter); + return { + height: this.heights[index], + y: this.ys[index], + }; + } + + /** + * Get the estimated height of the whole shebang by extrapolating based on + * the average known height. + */ + getEstimatedHeight(): number { + const known = this.ys[this.lastI] + this.heights[this.lastI]; + if (this.lastI >= this.dataLen - 1) { + return known | 0; + } + + return ((known / (this.lastI + 1)) * this.heights.length) | 0; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/index.tsx new file mode 100644 index 0000000000..22c572bec0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ListView/index.tsx @@ -0,0 +1,463 @@ +import * as React from 'react'; + +import Positions from './Positions'; +import { TNil } from '../../../../types'; + +type TWrapperProps = { + style: React.CSSProperties; + ref: (elm: HTMLDivElement) => void; + onScroll?: () => void; +}; + +/** + * @typedef + */ +type TListViewProps = { + /** + * Number of elements in the list. + */ + dataLength: number; + /** + * Convert item index (number) to the key (string). ListView uses both indexes + * and keys to handle the addtion of new rows. + */ + getIndexFromKey: (key: string) => number; + /** + * Convert item key (string) to the index (number). ListView uses both indexes + * and keys to handle the addtion of new rows. + */ + getKeyFromIndex: (index: number) => string; + /** + * Number of items to draw and add to the DOM, initially. + */ + initialDraw?: number; + /** + * The parent provides fallback height measurements when there is not a + * rendered element to measure. + */ + itemHeightGetter: (index: number, key: string) => number; + /** + * Function that renders an item; rendered items are added directly to the + * DOM, they are not wrapped in list item wrapper HTMLElement. + */ + // itemRenderer(itemKey, style, i, attrs) + itemRenderer: ( + itemKey: string, + style: Record, + index: number, + attributes: Record + ) => React.ReactNode; + /** + * `className` for the HTMLElement that holds the items. + */ + itemsWrapperClassName?: string; + /** + * When adding new items to the DOM, this is the number of items to add above + * and below the current view. E.g. if list is 100 items and is srcolled + * halfway down (so items [46, 55] are in view), then when a new range of + * items is rendered, it will render items `46 - viewBuffer` to + * `55 + viewBuffer`. + */ + viewBuffer: number; + /** + * The minimum number of items offscreen in either direction; e.g. at least + * `viewBuffer` number of items must be off screen above and below the + * current view, or more items will be rendered. + */ + viewBufferMin: number; + /** + * When `true`, expect `_wrapperElm` to have `overflow: visible` and to, + * essentially, be tall to the point the entire page will will end up + * scrolling as a result of the ListView. Similar to react-virtualized + * window scroller. + * + * - Ref: https://bvaughn.github.io/react-virtualized/#/components/WindowScroller + * - Ref:https://github.com/bvaughn/react-virtualized/blob/497e2a1942529560681d65a9ef9f5e9c9c9a49ba/docs/WindowScroller.md + */ + windowScroller?: boolean; +}; + +const DEFAULT_INITIAL_DRAW = 300; + +/** + * Virtualized list view component, for the most part, only renders the window + * of items that are in-view with some buffer before and after. Listens for + * scroll events and updates which items are rendered. See react-virtualized + * for a suite of components with similar, but generalized, functinality. + * https://github.com/bvaughn/react-virtualized + * + * Note: Presently, ListView cannot be a PureComponent. This is because ListView + * is sensitive to the underlying state that drives the list items, but it + * doesn't actually receive that state. So, a render may still be required even + * if ListView's props are unchanged. + * + * @export + * @class ListView + */ +export default class ListView extends React.Component { + /** + * Keeps track of the height and y-value of items, by item index, in the + * ListView. + */ + _yPositions: Positions; + /** + * Keep track of the known / measured heights of the rendered items; populated + * with values through observation and keyed on the item key, not the item + * index. + */ + _knownHeights: Map; + /** + * The start index of the items currently drawn. + */ + _startIndexDrawn: number; + /** + * The end index of the items currently drawn. + */ + _endIndexDrawn: number; + /** + * The start index of the items currently in view. + */ + _startIndex: number; + /** + * The end index of the items currently in view. + */ + _endIndex: number; + /** + * Height of the visual window, e.g. height of the scroller element. + */ + _viewHeight: number; + /** + * `scrollTop` of the current scroll position. + */ + _scrollTop: number; + /** + * Used to keep track of whether or not a re-calculation of what should be + * drawn / viewable has been scheduled. + */ + _isScrolledOrResized: boolean; + /** + * If `windowScroller` is true, this notes how far down the page the scroller + * is located. (Note: repositioning and below-the-fold views are untested) + */ + _htmlTopOffset: number; + _windowScrollListenerAdded: boolean; + _htmlElm: HTMLElement; + /** + * HTMLElement holding the scroller. + */ + _wrapperElm: HTMLElement | TNil; + /** + * HTMLElement holding the rendered items. + */ + _itemHolderElm: HTMLElement | TNil; + + static defaultProps = { + initialDraw: DEFAULT_INITIAL_DRAW, + itemsWrapperClassName: '', + windowScroller: false, + }; + + constructor(props: TListViewProps) { + super(props); + + this._yPositions = new Positions(200); + // _knownHeights is (item-key -> observed height) of list items + this._knownHeights = new Map(); + + this._startIndexDrawn = 2 ** 20; + this._endIndexDrawn = -(2 ** 20); + this._startIndex = 0; + this._endIndex = 0; + this._viewHeight = -1; + this._scrollTop = -1; + this._isScrolledOrResized = false; + + this._htmlTopOffset = -1; + this._windowScrollListenerAdded = false; + // _htmlElm is only relevant if props.windowScroller is true + this._htmlElm = document.documentElement as any; + this._wrapperElm = undefined; + this._itemHolderElm = undefined; + } + + componentDidMount() { + if (this.props.windowScroller) { + if (this._wrapperElm) { + const { top } = this._wrapperElm.getBoundingClientRect(); + this._htmlTopOffset = top + this._htmlElm.scrollTop; + } + window.addEventListener('scroll', this._onScroll); + this._windowScrollListenerAdded = true; + } + } + + componentDidUpdate() { + if (this._itemHolderElm) { + this._scanItemHeights(); + } + } + + componentWillUnmount() { + if (this._windowScrollListenerAdded) { + window.removeEventListener('scroll', this._onScroll); + } + } + + getViewHeight = () => this._viewHeight; + + /** + * Get the index of the item at the bottom of the current view. + */ + getBottomVisibleIndex = (): number => { + const bottomY = this._scrollTop + this._viewHeight; + return this._yPositions.findFloorIndex(bottomY, this._getHeight); + }; + + /** + * Get the index of the item at the top of the current view. + */ + getTopVisibleIndex = (): number => this._yPositions.findFloorIndex(this._scrollTop, this._getHeight); + + getRowPosition = (index: number): { height: number; y: number } => + this._yPositions.getRowPosition(index, this._getHeight); + + /** + * Scroll event listener that schedules a remeasuring of which items should be + * rendered. + */ + _onScroll = () => { + if (!this._isScrolledOrResized) { + this._isScrolledOrResized = true; + window.requestAnimationFrame(this._positionList); + } + }; + + /** + * Returns true is the view height (scroll window) or scroll position have + * changed. + */ + _isViewChanged() { + if (!this._wrapperElm) { + return false; + } + const useRoot = this.props.windowScroller; + const clientHeight = useRoot ? this._htmlElm.clientHeight : this._wrapperElm.clientHeight; + const scrollTop = useRoot ? this._htmlElm.scrollTop : this._wrapperElm.scrollTop; + return clientHeight !== this._viewHeight || scrollTop !== this._scrollTop; + } + + /** + * Recalculate _startIndex and _endIndex, e.g. which items are in view. + */ + _calcViewIndexes() { + const useRoot = this.props.windowScroller; + // funky if statement is to satisfy flow + if (!useRoot) { + /* istanbul ignore next */ + if (!this._wrapperElm) { + this._viewHeight = -1; + this._startIndex = 0; + this._endIndex = 0; + return; + } + this._viewHeight = this._wrapperElm.clientHeight; + this._scrollTop = this._wrapperElm.scrollTop; + } else { + this._viewHeight = window.innerHeight - this._htmlTopOffset; + this._scrollTop = window.scrollY; + } + const yStart = this._scrollTop; + const yEnd = this._scrollTop + this._viewHeight; + this._startIndex = this._yPositions.findFloorIndex(yStart, this._getHeight); + this._endIndex = this._yPositions.findFloorIndex(yEnd, this._getHeight); + } + + /** + * Checked to see if the currently rendered items are sufficient, if not, + * force an update to trigger more items to be rendered. + */ + _positionList = () => { + this._isScrolledOrResized = false; + if (!this._wrapperElm) { + return; + } + this._calcViewIndexes(); + // indexes drawn should be padded by at least props.viewBufferMin + const maxStart = + this.props.viewBufferMin > this._startIndex ? 0 : this._startIndex - this.props.viewBufferMin; + const minEnd = + this.props.viewBufferMin < this.props.dataLength - this._endIndex + ? this._endIndex + this.props.viewBufferMin + : this.props.dataLength - 1; + if (maxStart < this._startIndexDrawn || minEnd > this._endIndexDrawn) { + this.forceUpdate(); + } + }; + + _initWrapper = (elm: HTMLElement | TNil) => { + this._wrapperElm = elm; + if (!this.props.windowScroller && elm) { + this._viewHeight = elm.clientHeight; + } + }; + + _initItemHolder = (elm: HTMLElement | TNil) => { + this._itemHolderElm = elm; + this._scanItemHeights(); + }; + + /** + * Go through all items that are rendered and save their height based on their + * item-key (which is on a data-* attribute). If any new or adjusted heights + * are found, re-measure the current known y-positions (via .yPositions). + */ + _scanItemHeights = () => { + const getIndexFromKey = this.props.getIndexFromKey; + if (!this._itemHolderElm) { + return; + } + // note the keys for the first and last altered heights, the `yPositions` + // needs to be updated + let lowDirtyKey = null; + let highDirtyKey = null; + let isDirty = false; + // iterating childNodes is faster than children + // https://jsperf.com/large-htmlcollection-vs-large-nodelist + const nodes = this._itemHolderElm.childNodes; + const max = nodes.length; + for (let i = 0; i < max; i++) { + const node: HTMLElement = nodes[i] as any; + // use `.getAttribute(...)` instead of `.dataset` for jest / JSDOM + const itemKey = node.getAttribute('data-item-key'); + if (!itemKey) { + console.warn('itemKey not found'); + continue; + } + // measure the first child, if it's available, otherwise the node itself + // (likely not transferable to other contexts, and instead is specific to + // how we have the items rendered) + const measureSrc: Element = node.firstElementChild || node; + const observed = measureSrc.clientHeight; + const known = this._knownHeights.get(itemKey); + if (observed !== known) { + this._knownHeights.set(itemKey, observed); + if (!isDirty) { + isDirty = true; + + lowDirtyKey = highDirtyKey = itemKey; + } else { + highDirtyKey = itemKey; + } + } + } + if (lowDirtyKey != null && highDirtyKey != null) { + // update yPositions, then redraw + const imin = getIndexFromKey(lowDirtyKey); + const imax = highDirtyKey === lowDirtyKey ? imin : getIndexFromKey(highDirtyKey); + this._yPositions.calcHeights(imax, this._getHeight, imin); + this.forceUpdate(); + } + }; + + /** + * Get the height of the element at index `i`; first check the known heigths, + * fallbck to `.props.itemHeightGetter(...)`. + */ + _getHeight = (i: number) => { + const key = this.props.getKeyFromIndex(i); + const known = this._knownHeights.get(key); + // known !== known iff known is NaN + + if (known != null && known === known) { + return known; + } + return this.props.itemHeightGetter(i, key); + }; + + render() { + const { + dataLength, + getKeyFromIndex, + initialDraw = DEFAULT_INITIAL_DRAW, + itemRenderer, + viewBuffer, + viewBufferMin, + } = this.props; + const heightGetter = this._getHeight; + const items = []; + let start; + let end; + + this._yPositions.profileData(dataLength); + + if (!this._wrapperElm) { + start = 0; + end = (initialDraw < dataLength ? initialDraw : dataLength) - 1; + } else { + if (this._isViewChanged()) { + this._calcViewIndexes(); + } + const maxStart = viewBufferMin > this._startIndex ? 0 : this._startIndex - viewBufferMin; + const minEnd = + viewBufferMin < dataLength - this._endIndex ? this._endIndex + viewBufferMin : dataLength - 1; + if (maxStart < this._startIndexDrawn || minEnd > this._endIndexDrawn) { + start = viewBuffer > this._startIndex ? 0 : this._startIndex - viewBuffer; + end = this._endIndex + viewBuffer; + if (end >= dataLength) { + end = dataLength - 1; + } + } else { + start = this._startIndexDrawn; + end = this._endIndexDrawn > dataLength - 1 ? dataLength - 1 : this._endIndexDrawn; + } + } + + this._yPositions.calcHeights(end, heightGetter, start || -1); + this._startIndexDrawn = start; + this._endIndexDrawn = end; + + items.length = end - start + 1; + for (let i = start; i <= end; i++) { + const { y: top, height } = this._yPositions.getRowPosition(i, heightGetter); + const style = { + height, + top, + position: 'absolute', + }; + const itemKey = getKeyFromIndex(i); + const attrs = { 'data-item-key': itemKey }; + items.push(itemRenderer(itemKey, style, i, attrs)); + } + const wrapperProps: TWrapperProps = { + style: { position: 'relative' }, + ref: this._initWrapper, + }; + if (!this.props.windowScroller) { + wrapperProps.onScroll = this._onScroll; + wrapperProps.style.height = '100%'; + wrapperProps.style.overflowY = 'auto'; + } + const scrollerStyle = { + position: 'relative' as const, + height: this._yPositions.getEstimatedHeight(), + }; + return ( +
+
+
+ {items} +
+
+
+ ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.css new file mode 100644 index 0000000000..d4ca58f4db --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.css @@ -0,0 +1,20 @@ +.ReferencesButton-MultiParent { + padding: 0 5px; + color: #000; +} +.ReferencesButton-MultiParent ~ .ReferencesButton-MultiParent { + margin-left: 5px; +} + +a.ReferencesButton--TraceRefLink { + display: flex; + justify-content: space-between; +} + +a.ReferencesButton--TraceRefLink > .NewWindowIcon { + margin: 0.2em 0 0; +} + +.ReferencesButton-tooltip { + max-width: none; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.tsx new file mode 100644 index 0000000000..dd1784f6d5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/ReferencesButton.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Dropdown, Tooltip } from 'antd'; +import { TooltipPlacement } from 'antd/es/tooltip'; +import ReferenceLink from '../url/ReferenceLink'; +import { ILink } from '../../../types/otel'; + +import './ReferencesButton.css'; + +type TReferencesButtonProps = { + links: ReadonlyArray; + children: React.ReactNode; + tooltipText: string; + focusSpan: (spanID: string) => void; +}; + +// ReferencesButton is displayed as a menu at the span level. +// Example: https://github.com/jaegertracing/jaeger-ui/assets/94157520/2b29921a-2225-4a01-9018-1a1952f186ef +export default class ReferencesButton extends React.PureComponent { + linksList = (links: ReadonlyArray) => { + const dropdownItems = links.map(link => { + const { span } = link; + // Link within the trace should have link.span defined + const isSameTrace = span !== undefined; + + return { + key: `${link.spanID}`, + label: ( + + {isSameTrace + ? `${span.resource.serviceName}:${span.name} - ${link.spanID}` + : `(another trace) - ${link.spanID}`} + + ), + }; + }); + return dropdownItems; + }; + + render() { + const { links, children, tooltipText, focusSpan } = this.props; + + const tooltipProps = { + arrowPointAtCenter: true, + mouseLeaveDelay: 0.5, + placement: 'bottom' as TooltipPlacement, + title: tooltipText, + classNames: { root: 'ReferencesButton--tooltip' }, + }; + + if (links.length > 1) { + return ( + + + {children} + + + ); + } + + const link = links[0]; + + return ( + + + {children} + + + ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.css new file mode 100644 index 0000000000..3ee5c0f6db --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.css @@ -0,0 +1,134 @@ +.SpanBar--wrapper { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + overflow: hidden; + z-index: 0; +} + +.span-row.is-expanded .SpanBar--wrapper, +.span-row:hover .SpanBar--wrapper { + opacity: 1; +} + +.SpanBar--bar { + border-radius: 3px; + min-width: 2px; + position: absolute; + height: 36%; + top: 32%; +} + +.SpanBar--rpc { + position: absolute; + top: 35%; + bottom: 35%; + z-index: 1; +} + +.SpanBar--criticalPath { + position: absolute; + top: 45%; + height: 11%; + z-index: 2; + overflow: visible; + /* Changed from hidden to show shadow */ + box-shadow: + 0 -0.5px 0 var(--critical-path-outline), + 0 0.5px 0 var(--critical-path-outline); +} + +.SpanBar--criticalPath::before { + content: ''; + position: absolute; + top: 0; + width: 10%; + height: 100%; + visibility: hidden; + background-color: white; + border: 1px solid white; +} + +.SpanBar--criticalPath:hover::before { + visibility: visible; + animation: runOnce 2s forwards; +} + +@keyframes runOnce { + 0% { + left: 0; + opacity: 1; + } + + 99% { + opacity: 1; + } + + 100% { + left: 100%; + opacity: 0; + } +} + +.SpanBar--label { + color: var(--text-muted); + font-size: 12px; + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + line-height: 1em; + white-space: nowrap; + padding: 0 0.5em; + position: absolute; +} + +.SpanBar--label.is-right { + left: 100%; +} + +.SpanBar--label.is-left { + right: 100%; +} + +.span-row.is-expanded .SpanBar--label, +.span-row:hover .SpanBar--label { + color: var(--text-primary); +} + +.SpanBar--logMarker { + background-color: var(--span-bar-log-marker); + cursor: pointer; + height: 60%; + min-width: 1px; + position: absolute; + top: 20%; +} + +.SpanBar--logMarker:hover { + background-color: var(--text-primary); + transform: scale(1.2); + transition: transform 0.3s ease; +} + +.SpanBar--logMarker::before, +.SpanBar--logMarker::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + right: 0; + border: 3px solid transparent; +} + +.SpanBar--logMarker::after { + left: 0; +} + +.SpanBar--logHint { + pointer-events: none; +} + +/* Tweak the popover aesthetics - unfortunate but necessary */ +.SpanBar--logHint .ant-popover-inner-content { + padding: 0.25rem; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.tsx new file mode 100644 index 0000000000..09b724d3cb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBar.tsx @@ -0,0 +1,194 @@ +import React, { useState } from 'react'; +import { Popover, Tooltip } from 'antd'; +import _groupBy from 'lodash/groupBy'; + +import AccordionEvents from './SpanDetail/AccordionEvents'; + +import { ViewedBoundsFunctionType } from './utils'; +import { TNil } from '../../../types'; +import { CriticalPathSection } from '../../../types/critical_path'; +import { IEvent, IOtelSpan } from '../../../types/otel'; + +import './SpanBar.css'; + +type TSpanBarProps = { + color: string; + hintSide: string; + // onClick: (evt: React.MouseEvent) => void; + onClick?: (evt: React.MouseEvent) => void; + criticalPath: CriticalPathSection[]; + viewEnd: number; + viewStart: number; + getViewedBounds: ViewedBoundsFunctionType; + rpc: + | { + viewStart: number; + viewEnd: number; + color: string; + } + | TNil; + traceStartTime: number; + span: IOtelSpan; + longLabel: string; + shortLabel: string; + traceDuration: number; + useOtelTerms: boolean; +}; + +function toPercent(value: number) { + return `${(value * 100).toFixed(1)}%`; +} + +function toPercentInDecimal(value: number) { + return `${value * 100}%`; +} + +function SpanBarCriticalPath(props: { criticalPathViewStart: number; criticalPathViewEnd: number }) { + const [shouldLoadTooltip, setShouldLoadTooltip] = useState(false); + + const criticalPath = ( +
setShouldLoadTooltip(true)} + style={{ + background: 'var(--critical-path-color)', + left: toPercentInDecimal(props.criticalPathViewStart), + width: toPercentInDecimal(props.criticalPathViewEnd - props.criticalPathViewStart), + }} + /> + ); + + // Load tooltip only when hovering over critical path segment + // to reduce initial load time of trace page by ~300ms for 500 spans + if (shouldLoadTooltip) { + return ( + + A segment on the critical path of the overall trace/request/workflow. +
+ } + > + {criticalPath} + + ); + } + + return criticalPath; +} + +function SpanBar(props: TSpanBarProps) { + const { + criticalPath, + viewEnd, + viewStart, + getViewedBounds, + color, + hintSide, + onClick, + rpc, + traceStartTime, + span, + shortLabel, + longLabel, + traceDuration, + useOtelTerms, + } = props; + + // group events based on timestamps + const eventGroups = _groupBy(span.events, (event: IEvent) => { + const posPercent = getViewedBounds(event.timestamp, event.timestamp).start; + // round to the nearest 0.2% + return toPercent(Math.round(posPercent * 500) / 500); + }); + + const [label, setLabel] = useState(shortLabel); + + const setShortLabel = () => { + setLabel(shortLabel); + }; + + const setLongLabel = () => { + setLabel(longLabel); + }; + + return ( +
+
+
{label}
+
+
+ {Object.keys(eventGroups).map(positionKey => ( + + } + > +
+ + ))} +
+ {rpc && ( +
+ )} + {criticalPath && + criticalPath.map((each, index) => { + const critcalPathViewBounds = getViewedBounds(each.sectionStart, each.sectionEnd); + const criticalPathViewStart = critcalPathViewBounds.start; + const criticalPathViewEnd = critcalPathViewBounds.end; + const key = `${each.spanID}-${index}`; + + return ( + + ); + })} +
+ ); +} + +export default SpanBar; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.css new file mode 100644 index 0000000000..93c70168d5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.css @@ -0,0 +1,184 @@ +.span-row.is-matching-filter { + background-color: var(--trace-emphasis-highlight); +} + +.span-name-column { + position: relative; + white-space: nowrap; + z-index: 1; +} + +.span-name-column:hover { + z-index: 1; +} + +.span-row.clipping-left .span-name-column::before { + content: ' '; + height: 100%; + position: absolute; + width: 6px; + background-image: linear-gradient(to right, rgba(25, 25, 25, 0.25), rgba(32, 32, 32, 0)); + left: 100%; + z-index: -1; +} + +.span-name-wrapper { + background: var(--surface-secondary); + line-height: 27px; + overflow: hidden; + display: flex; +} + +.span-name-wrapper.is-matching-filter { + background-color: var(--trace-emphasis-highlight); +} + +.span-name-wrapper:hover { + border-right: 1px solid #bbb; + float: left; + min-width: calc(100% + 1px); + overflow: visible; +} + +.span-row:hover .span-name-wrapper { + background: var(--surface-secondary); + background: linear-gradient( + 90deg, + var(--surface-tertiary), + var(--surface-secondary) 75%, + var(--surface-secondary) + ); +} + +.span-row.is-matching-filter:hover .span-name-wrapper { + background: linear-gradient(90deg, #fff5e1, #fff5e1 75%, #ffe6c9); +} + +.span-row.is-expanded .span-name-wrapper { + background: var(--surface-secondary); + box-shadow: 0 1px 0 var(--border-default); +} + +.span-row.is-expanded .span-name-wrapper.is-matching-filter { + background: var(--trace-emphasis-highlight); +} + +.span-name { + color: var(--text-primary); + cursor: pointer; + flex: 1 1 auto; + outline: none; + overflow: hidden; + padding-right: 0.25em; + position: relative; + text-overflow: ellipsis; +} + +/* This is so the hit area of the span-name extends the rest of the width of the span-name column */ +.span-name::after { + background: transparent; + bottom: 0; + content: ' '; + left: 0; + position: absolute; + top: 0; + width: 1000px; +} + +.span-name:focus { + text-decoration: none; +} + +.endpoint-name { + color: var(--text-secondary); +} + +.span-name:hover > .endpoint-name { + color: var(--text-primary); +} + +.span-svc-name { + padding: 0 0.25rem 0 0.5rem; + font-size: 1.05em; +} + +.span-svc-name.is-children-collapsed { + font-weight: bold; + font-style: italic; +} + +.span-view { + position: relative; +} + +.span-row:hover .span-view { + background-color: var(--surface-secondary); + outline: 1px solid var(--border-default); +} + +.span-row.is-matching-filter:hover .span-view { + background-color: var(--trace-emphasis-highlight); + outline: 1px solid var(--border-default); +} + +.span-row.is-expanded .span-view { + background: var(--surface-secondary); + outline: 1px solid var(--border-default); +} + +.span-row.is-expanded.is-matching-filter .span-view { + background: var(--trace-emphasis-highlight); + outline: 1px solid var(--border-default); +} + +.span-row.is-expanded:hover .span-view { + background: var(--surface-secondary); +} + +.span-row.is-expanded.is-matching-filter:hover .span-view { + background: var(--trace-emphasis-highlight); +} + +.span-row.clipping-right .span-view::before { + content: ' '; + height: 100%; + position: absolute; + width: 6px; + background-image: linear-gradient(to left, rgba(25, 25, 25, 0.25), rgba(32, 32, 32, 0)); + right: 0%; + z-index: 1; +} + +.SpanBarRow--errorIcon { + background: #db2828; + border-radius: 6.5px; + color: #fff; + font-size: 0.85em; + margin-right: 0.25rem; + padding: 1px; +} + +/* Hollow variant for bubbled errors from collapsed children */ +.SpanBarRow--errorIcon--hollow { + background: transparent; + border: 1.5px solid #db2828; + color: #db2828; + font-weight: bold; + padding: 0px; + /* Adjust padding since border adds 1.5px */ +} + +.SpanBarRow--rpcColorMarker { + border-radius: 6.5px; + display: inline-block; + font-size: 0.85em; + height: 1em; + margin-right: 0.25rem; + padding: 1px; + width: 1em; + vertical-align: middle; +} + +.SpanBarRow--arrowForwardIcon { + vertical-align: middle; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.tsx new file mode 100644 index 0000000000..49fa7b4d14 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanBarRow.tsx @@ -0,0 +1,220 @@ +import React, { useCallback } from 'react'; +import { IoAlert, IoGitNetwork, IoCloudUploadOutline, IoArrowForward } from 'react-icons/io5'; +import ReferencesButton from './ReferencesButton'; +import TimelineRow from './TimelineRow'; +import { formatDuration, ViewedBoundsFunctionType } from './utils'; +import SpanTreeOffset from './SpanTreeOffset'; +import SpanBar from './SpanBar'; +import Ticks from './Ticks'; + +import { TNil } from '../../../types'; +import { CriticalPathSection } from '../../../types/critical_path'; +import { IOtelSpan } from '../../../types/otel'; + +import './SpanBarRow.css'; + +type SpanBarRowProps = { + className?: string; + color: string; + criticalPath: CriticalPathSection[]; + columnDivision: number; + isChildrenExpanded: boolean; + isDetailExpanded: boolean; + isMatchingFilter: boolean; + onDetailToggled: (spanID: string) => void; + onChildrenToggled: (spanID: string) => void; + numTicks: number; + rpc?: + | { + viewStart: number; + viewEnd: number; + color: string; + operationName: string; + serviceName: string; + } + | TNil; + noInstrumentedServer?: + | { + color: string; + serviceName: string; + } + | TNil; + hasOwnError: boolean; + hasChildError: boolean; + getViewedBounds: ViewedBoundsFunctionType; + traceStartTime: number; + span: IOtelSpan; + focusSpan: (spanID: string) => void; + traceDuration: number; + useOtelTerms: boolean; +}; + +/** + * This was originally a stateless function, but changing to a PureComponent + * reduced the render time of expanding a span row detail by ~50%. This is + * even true in the case where the stateless function has the same prop types as + * this class and arrow functions are created in the stateless function as + * handlers to the onClick props. E.g. for now, the PureComponent is more + * performance than the stateless function. + */ +const SpanBarRow: React.FC = ({ + className = '', + color, + criticalPath, + columnDivision, + isChildrenExpanded, + isDetailExpanded, + isMatchingFilter, + numTicks, + rpc = null, + noInstrumentedServer, + hasOwnError, + hasChildError, + getViewedBounds, + traceStartTime, + span, + focusSpan, + traceDuration, + onDetailToggled, + onChildrenToggled, + useOtelTerms, +}) => { + const _detailToggle = useCallback(() => { + onDetailToggled(span.spanID); + }, [onDetailToggled, span.spanID]); + + const _childrenToggle = useCallback(() => { + onChildrenToggled(span.spanID); + }, [onChildrenToggled, span.spanID]); + + const { + duration, + hasChildren: isParent, + name: operationName, + resource: { serviceName }, + } = span; + const label = formatDuration(duration); + const viewBounds = getViewedBounds(span.startTime, span.endTime); + const viewStart = viewBounds.start; + const viewEnd = viewBounds.end; + + const labelDetail = `${serviceName}::${operationName}`; + let longLabel; + let hintSide; + if (viewStart > 1 - viewEnd) { + longLabel = `${labelDetail} | ${label}`; + hintSide = 'left'; + } else { + longLabel = `${label} | ${labelDetail}`; + hintSide = 'right'; + } + + // In OTEL model, links are used for "related" spans (not parent spans). + // Show the references button if there's at least one link. + const hasLinks = span.links && span.links.length > 0; + const hasInboundLinks = span.inboundLinks && span.inboundLinks.length > 0; + + return ( + + + + + + + + + + ); +}; + +export default React.memo(SpanBarRow); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.css new file mode 100644 index 0000000000..f6837667ea --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.css @@ -0,0 +1,51 @@ +.AccordionAttributes--header { + cursor: pointer; + overflow: hidden; + padding: 0.25em 0.1em; + text-overflow: ellipsis; + white-space: nowrap; +} + +.AccordionAttributes--header:hover { + background: var(--surface-tertiary); +} + +.AccordionAttributes--header.is-empty { + background: none; + cursor: initial; +} + +.AccordionAttributes--header.is-high-contrast:not(.is-empty):hover { + background: var(--surface-tertiary); +} + +.AccordionAttributes--emptyIcon { + color: var(--text-muted); +} + +.AccordionAttributes--summary { + display: inline; + list-style: none; + padding: 0; +} + +.AccordionAttributes--summaryItem { + display: inline; + margin-left: 0.7em; + padding-right: 0.5rem; + border-right: 1px solid var(--border-default); +} + +.AccordionAttributes--summaryItem:last-child { + padding-right: 0; + border-right: none; +} + +.AccordionAttributes--summaryLabel { + color: var(--text-secondary); +} + +.AccordionAttributes--summaryDelim { + color: var(--text-muted); + padding: 0 0.2em; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.markers.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.markers.ts new file mode 100644 index 0000000000..e1c70864d1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.markers.ts @@ -0,0 +1 @@ +export const LABEL = 'label'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.tsx new file mode 100644 index 0000000000..aea7739b12 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionAttributes.tsx @@ -0,0 +1,84 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { IoChevronDown, IoChevronForward } from 'react-icons/io5'; + +import * as markers from './AccordionAttributes.markers'; +import AttributesTable from './AttributesTable'; +import { TNil } from '../../../../types'; +import { Hyperlink } from '../../../../types/hyperlink'; +import { IAttribute } from '../../../../types/otel'; + +import './AccordionAttributes.css'; + +// export for tests +export function AttributesSummary({ data }: { data: ReadonlyArray }) { + if (!Array.isArray(data) || !data.length) { + return null; + } + return ( +
    + {data.map((item, i) => ( + // `i` is necessary in the key because item.key can repeat + +
  • + {item.key} + = + {String(item.value)} +
  • + ))} +
+ ); +} + +export default function AccordionAttributes({ + className = null, + data, + highContrast = false, + interactive = true, + isOpen, + label, + linksGetter, + onToggle = null, +}: { + className?: string | TNil; + data: ReadonlyArray; + highContrast?: boolean; + interactive?: boolean; + isOpen: boolean; + label: string; + linksGetter: ((pairs: ReadonlyArray, index: number) => Hyperlink[]) | TNil; + onToggle?: null | (() => void); +}) { + const isEmpty = !Array.isArray(data) || !data.length; + const iconCls = cx('u-align-icon', { 'AccordionAttributes--emptyIcon': isEmpty }); + let arrow: React.ReactNode | null = null; + let headerProps: object | null = null; + if (interactive) { + arrow = isOpen ? : ; + headerProps = { + 'aria-checked': isOpen, + onClick: onToggle, + role: 'switch', + }; + } + + return ( +
+
+ {arrow} + + {label} + {isOpen || ':'} + + {!isOpen && } +
+ {isOpen && } +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.css new file mode 100644 index 0000000000..5c35b73f51 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.css @@ -0,0 +1,34 @@ +.AccordionEvents { + border: 1px solid var(--border-default); + position: relative; + margin-bottom: 0.25rem; +} + +.AccordionEvents--header { + background: var(--surface-secondary); + color: inherit; + display: block; + padding: 0.25rem 0.5rem; +} + +.AccordionEvents--header:hover { + background: var(--surface-tertiary); +} + +.AccordionEvents--toggle { + border: none; + background: transparent; + cursor: pointer; + font-style: italic; + text-decoration: underline; +} + +.AccordionEvents--content { + background: var(--surface-primary); + border-top: 1px solid var(--border-default); + padding: 0.5rem 0.5rem 0.25rem 0.5rem; +} + +.AccordionEvents--footer { + color: var(--text-secondary); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.tsx new file mode 100644 index 0000000000..c4d6c3502e --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionEvents.tsx @@ -0,0 +1,238 @@ +// Copyright (c) 2026 The Jaeger Authors. + +import * as React from 'react'; +import cx from 'classnames'; +import _sortBy from 'lodash/sortBy'; +import { IoChevronDown, IoChevronForward } from 'react-icons/io5'; + +import AccordionAttributes from './AccordionAttributes'; +import { formatDuration } from '../../../../utils/date'; +import { TNil } from '../../../../types'; +import { Hyperlink } from '../../../../types/hyperlink'; +import { IEvent, IAttribute } from '../../../../types/otel'; + +import './AccordionEvents.css'; + +type AccordionEventsProps = { + interactive?: boolean; + isOpen: boolean; + linksGetter?: ((pairs: ReadonlyArray, index: number) => Hyperlink[]) | TNil; + events: ReadonlyArray; + onItemToggle?: (event: IEvent) => void; + onToggle?: () => void; + openedItems?: Set; + timestamp: number; + currentViewRangeTime: [number, number]; + traceDuration: number; + initialVisibleCount?: number; + spanID?: string; + useOtelTerms: boolean; +}; + +export default function AccordionEvents({ + interactive = true, + isOpen, + linksGetter, + events, + openedItems, + onItemToggle, + onToggle, + timestamp, + currentViewRangeTime, + traceDuration, + initialVisibleCount = 3, + spanID, + useOtelTerms, +}: AccordionEventsProps) { + let arrow: React.ReactNode | null = null; + let HeaderComponent: 'span' | 'a' = 'span'; + let headerProps: object | null = null; + const [showOutOfRangeEvents, setShowOutOfRangeEvents] = React.useState(false); + const [showAllEvents, setShowAllEvents] = React.useState(false); + const contentRef = React.useRef(null); + + const notifyListReflow = React.useCallback(() => { + const emit = () => { + try { + if (spanID) { + window.dispatchEvent(new CustomEvent('jaeger:detail-measure', { detail: { spanID } })); + } else { + window.dispatchEvent(new Event('jaeger:list-resize')); + } + } catch (error) { + void error; + } + }; + + setTimeout(emit); + if (typeof window !== 'undefined' && 'requestAnimationFrame' in window) { + window.requestAnimationFrame(emit); + } + setTimeout(emit, 50); + }, []); + + // Observe height changes in the content area to notify virtualized list to reflow + React.useEffect(() => { + if (!interactive || !isOpen) return; + const target = contentRef.current; + if (!target) return; + + let ro: ResizeObserver | null = null; + let mo: MutationObserver | null = null; + const callback = () => notifyListReflow(); + + if (typeof ResizeObserver !== 'undefined') { + ro = new ResizeObserver(callback as ResizeObserverCallback); + if (ro) ro.observe(target); + } else if (typeof window !== 'undefined' && typeof MutationObserver !== 'undefined') { + mo = new MutationObserver(callback); + mo.observe(target, { childList: true, subtree: true }); + } + + return () => { + if (ro) { + try { + ro.disconnect(); + } catch (error) { + void error; + } + } + if (mo) { + try { + mo.disconnect(); + } catch (error) { + void error; + } + } + }; + }, [interactive, isOpen, notifyListReflow]); + + const inRangeEvents = React.useMemo(() => { + const viewStartAbsolute = timestamp + currentViewRangeTime[0] * traceDuration; + const viewEndAbsolute = timestamp + currentViewRangeTime[1] * traceDuration; + return events.filter(event => { + return event.timestamp >= viewStartAbsolute && event.timestamp <= viewEndAbsolute; + }); + }, [events, timestamp, currentViewRangeTime, traceDuration]); + + const eventsToDisplay = showOutOfRangeEvents ? events : inRangeEvents; + const displayedCount = eventsToDisplay.length; + const inRangeCount = inRangeEvents.length; + const totalCount = events.length; + + const label = useOtelTerms ? 'Events' : 'Logs'; + let title = `${label} (${displayedCount})`; + let toggleLink: React.ReactNode = null; + + if (!showOutOfRangeEvents && inRangeCount < totalCount) { + title = `${label} (${inRangeCount} of ${totalCount})`; + toggleLink = ( + + ); + } else if (showOutOfRangeEvents && inRangeCount < totalCount) { + title = `${label} (${totalCount})`; + toggleLink = ( + + ); + } + + if (interactive) { + arrow = isOpen ? ( + + ) : ( + + ); + HeaderComponent = 'a'; + headerProps = { + 'aria-checked': isOpen, + onClick: onToggle, + role: 'switch', + }; + } + + return ( +
+ + {arrow} {title} {toggleLink} + + {isOpen && ( +
+ {(() => { + const sortedEvents = _sortBy(eventsToDisplay, event => event.timestamp); + const visibleLogs = + interactive && !showAllEvents && sortedEvents.length > initialVisibleCount + ? sortedEvents.slice(0, initialVisibleCount) + : sortedEvents; + return visibleLogs.map((event, i) => { + const durationLabel = formatDuration((event.timestamp - timestamp) as IEvent['timestamp']); + const labelContent = useOtelTerms ? `${event.name} (${durationLabel})` : durationLabel; + return ( + onItemToggle(event) : null} + /> + ); + }); + })()} + {interactive && _sortBy(eventsToDisplay, event => event.timestamp).length > initialVisibleCount && ( +
+ {!showAllEvents ? ( + + ) : ( + + )} +
+ )} + + {useOtelTerms ? 'Event' : 'Log'} timestamps are relative to the start time of the full trace. + +
+ )} +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.css new file mode 100644 index 0000000000..73d0058b54 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.css @@ -0,0 +1,45 @@ +.ReferencesList--List { + width: 100%; + list-style: none; + padding: 0; + margin: 0; + background: var(--surface-primary); +} + +.ReferencesList { + background: var(--surface-primary); + border: 1px solid var(--border-default); + margin-bottom: 0.7em; + max-height: 450px; + overflow: auto; +} + +.ReferencesList--itemContent { + padding: 0.25rem 0.5rem; + display: flex; + width: 100%; + justify-content: space-between; +} + +.ReferencesList--itemContent > a { + width: 100%; + display: inline-block; +} + +.ReferencesList--Item:nth-child(2n) { + background: var(--surface-secondary); +} + +.SpanReference--debugInfo { + letter-spacing: 0.25px; + margin: 0.5em 0 0; +} + +.SpanReference--debugLabel::before { + color: var(--text-muted); + content: attr(data-label); +} + +.SpanReference--debugLabel { + margin: 0 5px 0 5px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.tsx new file mode 100644 index 0000000000..55f0ceb210 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionLinks.tsx @@ -0,0 +1,99 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { IoChevronDown, IoChevronForward } from 'react-icons/io5'; +import './AccordionLinks.css'; +import { ILink } from '../../../../types/otel'; +import ReferenceLink from '../../url/ReferenceLink'; + +type AccordionLinksProps = { + data: ReadonlyArray; + highContrast?: boolean; + interactive?: boolean; + isOpen: boolean; + onToggle?: null | (() => void); + focusSpan: (uiFind: string) => void; + useOtelTerms: boolean; +}; + +type ReferenceItemProps = { + data: ReadonlyArray; + focusSpan: (uiFind: string) => void; +}; + +// export for test +export function References(props: ReferenceItemProps) { + const { data, focusSpan } = props; + + return ( +
+
    + {data.map(link => { + return ( +
  • + + + {link.span ? ( + + {link.span.resource.serviceName} + {link.span.name} + + ) : ( + < span in another trace > + )} + + + {link.spanID} + + + + +
  • + ); + })} +
+
+ ); +} + +export default class AccordionLinks extends React.PureComponent { + static defaultProps = { + highContrast: false, + interactive: true, + onToggle: null, + }; + + render() { + const { data, highContrast, interactive, isOpen, onToggle, focusSpan } = this.props; + const isEmpty = !Array.isArray(data) || !data.length; + const iconCls = cx('u-align-icon', { 'AccordianKReferences--emptyIcon': isEmpty }); + let arrow: React.ReactNode | null = null; + let headerProps: object | null = null; + if (interactive) { + arrow = isOpen ? : ; + headerProps = { + 'aria-checked': isOpen, + onClick: isEmpty ? null : onToggle, + role: 'switch', + }; + } + return ( +
+
+ {arrow} + + {this.props.useOtelTerms ? 'Links' : 'References'} + {' '} + ({data.length}) +
+ {isOpen && } +
+ ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.css new file mode 100644 index 0000000000..e12ec6d29e --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.css @@ -0,0 +1,11 @@ +.AccordionText--header { + cursor: pointer; + overflow: hidden; + padding: 0.25em 0.1em; + text-overflow: ellipsis; + white-space: nowrap; +} + +.AccordionText--header:hover { + background: #e8e8e8; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.tsx new file mode 100644 index 0000000000..1ba55575d2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AccordionText.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { IoChevronDown, IoChevronForward } from 'react-icons/io5'; +import TextList from './TextList'; +import { TNil } from '../../../../types'; + +import './AccordionText.css'; + +type AccordionTextProps = { + className?: string | TNil; + data: ReadonlyArray; + headerClassName?: string | TNil; + highContrast?: boolean; + interactive?: boolean; + isOpen: boolean; + label: React.ReactNode; + onToggle?: null | (() => void); +}; + +export default function AccordionText({ + className = null, + data, + headerClassName, + highContrast = false, + interactive = true, + isOpen, + label, + onToggle = null, +}: AccordionTextProps) { + const isEmpty = !Array.isArray(data) || !data.length; + const iconCls = cx('u-align-icon', { 'AccordionAttributes--emptyIcon': isEmpty }); + + let arrow: React.ReactNode | null = null; + let headerProps: object | null = null; + + if (interactive) { + arrow = isOpen ? : ; + headerProps = { + 'aria-checked': isOpen, + onClick: isEmpty ? null : onToggle, + role: 'switch', + }; + } + + return ( +
+
+ {arrow} {label} ({data.length}) +
+ {isOpen && } +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.css new file mode 100644 index 0000000000..45625186af --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.css @@ -0,0 +1,49 @@ +.KeyValueTable { + background: var(--surface-primary); + border: 1px solid var(--border-default); + margin-bottom: 0.7em; + max-height: 450px; + overflow: auto; +} + +.KeyValueTable--body { + vertical-align: baseline; +} + +.KeyValueTable--row > td { + padding: 0.25rem 0.5rem; +} + +.KeyValueTable--row:nth-child(2n) > td { + background: var(--surface-secondary); +} + +.KeyValueTable--keyColumn { + color: var(--text-secondary); + white-space: pre; + width: 125px; +} + +.KeyValueTable--valueColumn { + position: relative; +} + +.KeyValueTable--copyContainer { + float: right; + margin-left: 8px; + white-space: nowrap; +} + +.KeyValueTable--row:not(:hover) .KeyValueTable--copyContainer .KeyValueTable--copyIcon { + visibility: hidden; +} + +.KeyValueTable--row > td { + padding: 0.25rem 0.5rem; + vertical-align: top; +} + +.KeyValueTable--linkIcon { + vertical-align: middle; + font-weight: bold; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.tsx new file mode 100644 index 0000000000..3cfcf0bc14 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/AttributesTable.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import { Dropdown, Tooltip } from 'antd'; +import { IoOpenOutline, IoList, IoCopyOutline, IoInformationCircleOutline } from 'react-icons/io5'; +import { JsonView, allExpanded, collapseAllNested, defaultStyles } from 'react-json-view-lite'; + +import CopyIcon from '../../../common/CopyIcon'; + +import { TNil } from '../../../../types'; +import { Hyperlink } from '../../../../types/hyperlink'; +import { IAttribute } from '../../../../types/otel'; + +import './AttributesTable.css'; + +const jsonObjectOrArrayStartRegex = /^(\[|\{)/; + +function tryParseJson(value: string) { + // if the value is a string representing actual json object or array, then use json-markup + // otherwise just return as is + try { + return jsonObjectOrArrayStartRegex.test(value) ? JSON.parse(value) : value; + } catch (_) { + return value; + } +} + +function shouldDisplayAsStringList(key: string) { + return key.startsWith('http.request.header.') || key.startsWith('http.response.header.'); +} + +const stringListMarkup = (value: any[]) => ( +
+ {value.map((item, i) => ( + + {i > 0 && ', '} + {item} + + ))} +
+); + +const scalarMarkup = (value: string | number | boolean) => { + let className; + switch (typeof value) { + case 'boolean': { + className = 'json-markup-bool'; + break; + } + case 'number': { + className = 'json-markup-number'; + break; + } + default: { + className = 'json-markup-string'; + } + } + return ( +
+ {value.toString()} +
+ ); +}; + +function formatValue(key: string, value: any) { + let content; + let parsed = value; + + if (typeof value === 'string') { + parsed = tryParseJson(value); + } + + if (Array.isArray(parsed) && shouldDisplayAsStringList(key)) { + content = stringListMarkup(parsed); + } else if (typeof parsed === 'object') { + const shouldJsonTreeExpand = Object.keys(parsed).length <= 10; + content = ( + + ); + } else { + content = scalarMarkup(parsed); + } + + return
{content}
; +} + +export const LinkValue = (props: { href: string; title?: string; children: React.ReactNode }) => ( + + {props.children} + +); + +const linkValueList = (links: Hyperlink[]) => { + return links.map(({ text, url }, index) => ({ + label: {text}, + key: `${url}-${index}`, + })); +}; + +type AttributesTableProps = { + data: ReadonlyArray; + linksGetter: ((pairs: ReadonlyArray, index: number) => Hyperlink[]) | TNil; +}; + +// AttributesTable is displayed as a menu at span level. +// Example: https://github.com/jaegertracing/jaeger-ui/assets/94157520/b518cad9-cb37-4775-a3d6-b667a1235f89 +export default function AttributesTable(props: AttributesTableProps) { + const { data, linksGetter } = props; + + return ( +
+ + + {data.map((row, i) => { + const jsonTable = formatValue(row.key, row.value); + const links = linksGetter ? linksGetter(data, i) : null; + let valueMarkup; + if (links?.length === 1) { + valueMarkup = ( +
+ + {jsonTable} + +
+ ); + } else if (links && links.length > 1) { + valueMarkup = ( + + ); + } else { + valueMarkup = jsonTable; + } + const isOtel = row.key.startsWith('otel.'); + const keyMarkup = isOtel ? ( + + {row.key} + + + + + ) : ( + row.key + ); + + return ( + // `i` is necessary in the key because row.key can repeat + + + + + ); + })} + +
{keyMarkup} +
+ + } + copyText={JSON.stringify(row, null, 2)} + tooltipTitle="Copy JSON" + buttonText="JSON" + /> +
+ {valueMarkup} +
+
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/DetailState.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/DetailState.ts new file mode 100644 index 0000000000..1c2f9396d2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/DetailState.ts @@ -0,0 +1,92 @@ +import { IEvent } from '../../../../types/otel'; + +/** + * Which items of a {@link SpanDetail} component are expanded. + */ +export default class DetailState { + isAttributesOpen: boolean; + isResourceOpen: boolean; + events: { isOpen: boolean; openedItems: Set }; + isWarningsOpen: boolean; + isLinksOpen: boolean; + + constructor(oldState?: DetailState) { + const { + isAttributesOpen, + isResourceOpen, + isLinksOpen, + isWarningsOpen, + events, + }: DetailState | Record = oldState || {}; + this.isAttributesOpen = Boolean(isAttributesOpen); + this.isResourceOpen = Boolean(isResourceOpen); + this.isLinksOpen = Boolean(isLinksOpen); + this.isWarningsOpen = Boolean(isWarningsOpen); + this.events = { + isOpen: events ? Boolean(events.isOpen) : true, + openedItems: + events && events.openedItems ? new Set(events.openedItems as Iterable) : new Set(), + }; + } + + toggleAttributes() { + const next = new DetailState(this); + next.isAttributesOpen = !this.isAttributesOpen; + return next; + } + + toggleResource() { + const next = new DetailState(this); + next.isResourceOpen = !this.isResourceOpen; + return next; + } + + toggleLinks() { + const next = new DetailState(this); + next.isLinksOpen = !this.isLinksOpen; + return next; + } + + toggleWarnings() { + const next = new DetailState(this); + next.isWarningsOpen = !this.isWarningsOpen; + return next; + } + + toggleEvents() { + const next = new DetailState(this); + next.events.isOpen = !this.events.isOpen; + return next; + } + + toggleEventItem(eventItem: IEvent) { + const next = new DetailState(this); + if (next.events.openedItems.has(eventItem)) { + next.events.openedItems.delete(eventItem); + } else { + next.events.openedItems.add(eventItem); + } + return next; + } + + // Legacy method names for backward compatibility + toggleTags() { + return this.toggleAttributes(); + } + + toggleProcess() { + return this.toggleResource(); + } + + toggleReferences() { + return this.toggleLinks(); + } + + toggleLogs() { + return this.toggleEvents(); + } + + toggleLogItem(logItem: IEvent) { + return this.toggleEventItem(logItem); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.css new file mode 100644 index 0000000000..d80adaee3f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.css @@ -0,0 +1,20 @@ +.TextList { + max-height: 450px; + overflow: auto; +} + +.TextList--List { + width: 100%; + list-style: none; + padding: 0; + margin: 0; +} + +.TextList--List > li:nth-child(2n) { + background: #f5f5f5; +} + +.TextList--List > li { + padding: 0.25rem 0.5rem; + vertical-align: top; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.tsx new file mode 100644 index 0000000000..7b1ac032c0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/TextList.tsx @@ -0,0 +1,24 @@ +import * as React from 'react'; + +import './TextList.css'; + +type TextListProps = { + data: ReadonlyArray; +}; + +export default function TextList(props: TextListProps) { + const { data } = props; + return ( +
+
    + {data.map((row, i) => { + return ( + // `i` is necessary in the key because row.key can repeat + +
  • {row}
  • + ); + })} +
+
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.css new file mode 100644 index 0000000000..855b596462 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.css @@ -0,0 +1,49 @@ +.SpanDetail--divider { + background: var(--border-default); +} + +.SpanDetail--debugInfo { + display: block; + letter-spacing: 0.25px; + margin: 0.5em 0 -0.75em; + text-align: right; +} + +.SpanDetail--debugLabel::before { + color: var(--text-muted); + content: attr(data-label); +} + +.SpanDetail--debugValue { + background-color: inherit; + border: none; + color: var(--text-muted); + cursor: pointer; +} + +.SpanDetail--debugValue:hover { + color: var(--text-primary); +} + +.AccordianWarnings { + background: var(--surface-tertiary); + border: 1px solid var(--border-default); + margin-bottom: 0.25rem; +} + +.AccordianWarnings--header { + background: var(--ant-color-warning-bg); + padding: 0.25rem 0.5rem; +} + +.AccordianWarnings--header:hover { + background: var(--ant-color-warning-bg-hover); +} + +.AccordianWarnings--header.is-open { + border-bottom: 1px solid var(--border-default); +} + +.AccordianWarnings--label { + color: var(--feedback-warning); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.tsx new file mode 100644 index 0000000000..c4f3218ac7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetail/index.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { Divider } from 'antd'; + +import { IoLinkOutline } from 'react-icons/io5'; +import AccordionAttributes from './AccordionAttributes'; +import AccordionEvents from './AccordionEvents'; +import AccordionLinks from './AccordionLinks'; +import AccordionText from './AccordionText'; +import DetailState from './DetailState'; +import { formatDuration } from '../utils'; +import CopyIcon from '../../../common/CopyIcon'; +import LabeledList from '../../../common/LabeledList'; + +import { TNil } from '../../../../types'; +import { Hyperlink } from '../../../../types/hyperlink'; +import { IOtelSpan, IAttribute, IEvent } from '../../../../types/otel'; + +import './index.css'; + +type SpanDetailProps = { + detailState: DetailState; + linksGetter: ((links: ReadonlyArray, index: number) => Hyperlink[]) | TNil; + eventItemToggle: (spanID: string, event: IEvent) => void; + eventsToggle: (spanID: string) => void; + resourceToggle: (spanID: string) => void; + span: IOtelSpan; + attributesToggle: (spanID: string) => void; + traceStartTime: number; + warningsToggle: (spanID: string) => void; + linksToggle: (spanID: string) => void; + focusSpan: (uiFind: string) => void; + currentViewRangeTime: [number, number]; + traceDuration: number; + useOtelTerms: boolean; +}; + +export default function SpanDetail(props: SpanDetailProps) { + const { + detailState, + linksGetter, + eventItemToggle, + eventsToggle, + resourceToggle, + span, + attributesToggle, + traceStartTime, + warningsToggle, + linksToggle, + focusSpan, + currentViewRangeTime, + traceDuration, + useOtelTerms, + } = props; + + const { isAttributesOpen, isResourceOpen, events: eventsState, isWarningsOpen, isLinksOpen } = detailState; + const warnings = span.warnings; + + // Get links for display in AccordionLinks + const links = span.links || []; + + // Display labels based on terminology flag + const attributesLabel = useOtelTerms ? 'Attributes' : 'Tags'; + const resourceLabel = useOtelTerms ? 'Resource' : 'Process'; + const eventsLabel = useOtelTerms ? 'Events' : 'Logs'; + + const overviewItems = [ + { + key: 'svc', + label: 'Service:', + value: span.resource.serviceName, + }, + { + key: 'duration', + label: 'Duration:', + value: formatDuration(span.duration), + }, + { + key: 'start', + label: 'Start Time:', + value: formatDuration(span.relativeStartTime), + }, + ]; + const deepLinkCopyText = `${window.location.origin}${window.location.pathname}?uiFind=${span.spanID}`; + + return ( +
+
+

{span.name}

+ +
+ +
+
+ attributesToggle(span.spanID)} + /> + {span.resource.attributes && span.resource.attributes.length > 0 && ( + resourceToggle(span.spanID)} + /> + )} +
+ {span.events && span.events.length > 0 && ( + eventsToggle(span.spanID)} + onItemToggle={eventItem => eventItemToggle(span.spanID, eventItem)} + timestamp={traceStartTime} + currentViewRangeTime={currentViewRangeTime} + traceDuration={traceDuration} + spanID={span.spanID} + useOtelTerms={useOtelTerms} + /> + )} + {warnings && warnings.length > 0 && ( + Warnings} + data={warnings} + isOpen={isWarningsOpen} + onToggle={() => warningsToggle(span.spanID)} + /> + )} + {links && links.length > 0 && ( + linksToggle(span.spanID)} + focusSpan={focusSpan} + useOtelTerms={useOtelTerms} + /> + )} + + {span.spanID} + } + placement="topRight" + tooltipTitle="Copy deep link to this span" + buttonText="Copy" + /> + +
+
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.css new file mode 100644 index 0000000000..f72eca6384 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.css @@ -0,0 +1,40 @@ +.detail-row-expanded-accent { + cursor: pointer; + height: 100%; + overflow: hidden; + position: absolute; + width: 100%; +} + +.detail-row-expanded-accent::before { + border-left: 4px solid; + pointer-events: none; + width: 1000px; +} + +.detail-row-expanded-accent::after { + border-right: 1000px solid; + border-color: inherit; + cursor: pointer; + opacity: 0.2; +} + +/* border-color inherit must come AFTER other border declarations for accent */ +.detail-row-expanded-accent::before, +.detail-row-expanded-accent::after { + border-color: inherit; + content: ' '; + position: absolute; + height: 100%; +} + +.detail-row-expanded-accent:hover::after { + opacity: 0.35; +} + +.detail-info-wrapper { + background: #f5f5f5; + border: 1px solid var(--border-strong); + border-top: 3px solid; + padding: 0.75rem; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.tsx new file mode 100644 index 0000000000..42645a8ede --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanDetailRow.tsx @@ -0,0 +1,94 @@ +import React from 'react'; + +import SpanDetail from './SpanDetail'; +import DetailState from './SpanDetail/DetailState'; +import SpanTreeOffset from './SpanTreeOffset'; +import TimelineRow from './TimelineRow'; + +import { IOtelSpan, IAttribute, IEvent } from '../../../types/otel'; +import { Hyperlink } from '../../../types/hyperlink'; + +import './SpanDetailRow.css'; + +type SpanDetailRowProps = { + color: string; + columnDivision: number; + detailState: DetailState; + onDetailToggled: (spanID: string) => void; + linksGetter: (attributes: ReadonlyArray, index: number) => Hyperlink[]; + eventItemToggle: (spanID: string, event: IEvent) => void; + eventsToggle: (spanID: string) => void; + resourceToggle: (spanID: string) => void; + linksToggle: (spanID: string) => void; + warningsToggle: (spanID: string) => void; + span: IOtelSpan; + attributesToggle: (spanID: string) => void; + traceStartTime: number; + focusSpan: (uiFind: string) => void; + currentViewRangeTime: [number, number]; + traceDuration: number; + useOtelTerms: boolean; +}; + +const SpanDetailRow = React.memo((props: SpanDetailRowProps) => { + const _detailToggle = () => { + props.onDetailToggled(props.span.spanID); + }; + + const { + color, + columnDivision, + detailState, + eventsToggle, + resourceToggle, + linksToggle, + warningsToggle, + span, + attributesToggle, + traceStartTime, + focusSpan, + currentViewRangeTime, + traceDuration, + linksGetter, + eventItemToggle, + useOtelTerms, + } = props; + return ( + + + + + + + + +
+ +
+
+
+ ); +}); + +export default SpanDetailRow; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.css new file mode 100644 index 0000000000..9f2586cb7c --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.css @@ -0,0 +1,97 @@ +.SpanTreeOffset { + color: var(--text-primary); + position: relative; + display: inline-flex; + align-items: center; + height: 28px; + padding-left: 10px; + /* Align with column header and center boxes over vertical lines */ +} + +.SpanTreeOffset.is-parent:hover { + background-color: var(--surface-component-background-hover); + cursor: pointer; +} + +.SpanTreeOffset--indentGuide { + /* Width to accommodate the box/dot plus spacing */ + width: calc(0.5rem + 20px); + height: 100%; + display: inline-flex; + position: relative; + align-items: center; + flex-shrink: 0; +} + +/* Vertical line for hierarchy - colored based on service */ +.SpanTreeOffset--indentGuide::after { + content: ''; + position: absolute; + left: calc(0.25rem + 9.5px); + /* Center on 20px box: 0.25rem + (20px / 2) - 0.5px (half of line width) */ + width: 1px; + height: 100%; + background-color: currentColor; +} + +/* For the last ancestor (immediate parent), only show vertical line in top half */ +.SpanTreeOffset--indentGuide.is-last::after { + height: 50%; + top: 0; +} + +/* For ancestors whose branches have terminated, hide the line completely */ +.SpanTreeOffset--indentGuide.is-terminated::after { + display: none; +} + +/* Horizontal line connecting parent to child */ +.SpanTreeOffset--horizontalLine { + position: absolute; + left: calc(0.25rem + 9.5px); + /* Start from vertical line center */ + width: calc(0.25rem + 10.5px); + /* Extend to icon center */ + height: 1px; + top: 50%; + transform: translateY(-50%); + background-color: inherit; +} + +.SpanTreeOffset--iconWrapper { + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +/* Colored box for parent spans - hollow outline */ +.SpanTreeOffset--box { + display: flex; + align-items: center; + justify-content: center; + min-width: 20px; + height: 20px; + padding: 0 4px; + border-radius: 3px; + border: 1px solid; + background-color: var(--surface-secondary); + color: var(--text-primary); + font-size: 11px; + font-weight: 600; + cursor: pointer; +} + +/* Filled box when collapsed */ +.SpanTreeOffset--box.is-collapsed { + color: white; +} + +/* Colored dot for leaf spans */ +.SpanTreeOffset--dot { + display: block; + width: 8px; + height: 8px; + border-radius: 50%; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.tsx new file mode 100644 index 0000000000..3357c7f7ae --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/SpanTreeOffset.tsx @@ -0,0 +1,188 @@ +import React, { useMemo } from 'react'; +import cx from 'classnames'; +import _get from 'lodash/get'; +import { IoChevronDown, IoChevronForward } from 'react-icons/io5'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; + +import { actions } from './duck'; +import { ReduxState } from '../../../types'; +import { IOtelSpan } from '../../../types/otel'; +import spanAncestorIds from '../../../utils/span-ancestor-ids'; +import colorGenerator from '../../../utils/color-generator'; + +import './SpanTreeOffset.css'; + +type TDispatchProps = { + addHoverIndentGuideId: (spanID: string) => void; + removeHoverIndentGuideId: (spanID: string) => void; +}; + +type TProps = { + addHoverIndentGuideId: (spanID: string) => void; + hoverIndentGuideIds: Set; + removeHoverIndentGuideId: (spanID: string) => void; + childrenVisible?: boolean; + onClick?: () => void; + showChildrenIcon?: boolean; + span: IOtelSpan; + color: string; +}; + +export const UnconnectedSpanTreeOffset: React.FC = ({ + childrenVisible = false, + onClick = undefined, + showChildrenIcon = true, + span, + hoverIndentGuideIds, + addHoverIndentGuideId, + removeHoverIndentGuideId, + color, +}) => { + // Build ancestor chain directly from span.parentSpan + const ancestors = useMemo(() => { + const chain: IOtelSpan[] = []; + let current = span.parentSpan; + while (current) { + chain.unshift(current); + current = current.parentSpan; + } + return chain; + }, [span]); + + /** + * If the mouse leaves to anywhere except another span with the same ancestor id, this span's ancestor id is + * removed from the set of hoverIndentGuideIds. + * + * @param {Object} event - React Synthetic event tied to mouseleave. Includes the related target which is + * the element the user is now hovering. + * @param {string} ancestorId - The span id that the user was hovering over. + */ + const handleMouseLeave = (event: React.MouseEvent, ancestorId: string) => { + if ( + !(event.relatedTarget instanceof HTMLSpanElement) || + _get(event, 'relatedTarget.dataset.ancestorId') !== ancestorId + ) { + removeHoverIndentGuideId(ancestorId); + } + }; + + /** + * If the mouse entered this span from anywhere except another span with the same ancestor id, this span's + * ancestorId is added to the set of hoverIndentGuideIds. + * + * @param {Object} event - React Synthetic event tied to mouseenter. Includes the related target which is + * the last element the user was hovering. + * @param {string} ancestorId - The span id that the user is now hovering over. + */ + const handleMouseEnter = (event: React.MouseEvent, ancestorId: string) => { + if ( + !(event.relatedTarget instanceof HTMLSpanElement) || + _get(event, 'relatedTarget.dataset.ancestorId') !== ancestorId + ) { + addHoverIndentGuideId(ancestorId); + } + }; + + const { hasChildren, spanID, childSpans } = span; + const wrapperProps = hasChildren ? { onClick, role: 'switch', 'aria-checked': childrenVisible } : null; + + // Get parent color for horizontal line + const parentSpan = span.parentSpan; + const parentColor = parentSpan ? colorGenerator.getColorByKey(parentSpan.resource.serviceName) : color; + + // Check if this is a root span (no parent) + const isRootSpan = !parentSpan; + + // Check if this span is the last child of its parent + const isLastChild = parentSpan + ? parentSpan.childSpans[parentSpan.childSpans.length - 1]?.spanID === spanID + : false; + + return ( + + {ancestors.map((ancestor, index) => { + // Determine the color for this indent guide based on the ancestor + const guideColor = colorGenerator.getColorByKey(ancestor.resource.serviceName); + const isLastAncestor = index === ancestors.length - 1; + + // For the immediate parent: check if current span is last child + // For non-immediate ancestors: check if the ancestor's branch has terminated + // (i.e., the descendant of this ancestor in the chain is the last child of its parent) + let shouldTerminate = false; + + if (isLastAncestor) { + // For immediate parent, check if current span is last child + shouldTerminate = isLastChild; + } else { + // For non-immediate ancestors, check if their descendant in the chain is the last child + // The descendant of this ancestor in the chain is at index + 1 + const descendantInChain = ancestors[index + 1]; + if (descendantInChain && descendantInChain.parentSpan) { + const parentChildren = descendantInChain.parentSpan.childSpans; + shouldTerminate = parentChildren[parentChildren.length - 1]?.spanID === descendantInChain.spanID; + } + } + + return ( + handleMouseEnter(event, ancestor.spanID)} + onMouseLeave={event => handleMouseLeave(event, ancestor.spanID)} + > + {isLastAncestor && ( + + )} + + ); + })} + {showChildrenIcon && ( + handleMouseEnter(event, spanID)} + onMouseLeave={event => handleMouseLeave(event, spanID)} + > + {hasChildren ? ( + + {childSpans.length} + + ) : ( + + )} + + )} + + ); +}; + +export function mapStateToProps(state: ReduxState): { hoverIndentGuideIds: Set } { + const { hoverIndentGuideIds } = state.traceTimeline; + return { hoverIndentGuideIds }; +} + +export function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { + const { addHoverIndentGuideId, removeHoverIndentGuideId } = bindActionCreators(actions, dispatch); + return { addHoverIndentGuideId, removeHoverIndentGuideId }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(UnconnectedSpanTreeOffset); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.css new file mode 100644 index 0000000000..0b864b812b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.css @@ -0,0 +1,26 @@ +.Ticks { + pointer-events: none; +} + +.Ticks--tick { + position: absolute; + height: 100%; + width: 1px; + background: var(--border-strong); +} + +.Ticks--tick:last-child { + width: 0; +} + +.Ticks--tickLabel { + left: 0.25rem; + position: absolute; + white-space: nowrap; + color: var(--text-secondary); +} + +.Ticks--tickLabel.isEndAnchor { + left: initial; + right: 0.25rem; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.tsx new file mode 100644 index 0000000000..9d01194bb0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/Ticks.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; + +import { formatDuration } from '../../../utils/date'; +import { TNil } from '../../../types'; +import { IOtelSpan } from '../../../types/otel'; + +import './Ticks.css'; + +type TicksProps = { + numTicks: number; + showLabels?: boolean | TNil; + startTime?: IOtelSpan['startTime'] | TNil; + endTime?: IOtelSpan['endTime'] | TNil; +}; + +export default function Ticks({ endTime = null, numTicks, showLabels = null, startTime = null }: TicksProps) { + let labels: undefined | string[]; + if (showLabels) { + labels = []; + const viewingDuration = (endTime || 0) - (startTime || 0); + for (let i = 0; i < numTicks; i++) { + const durationAtTick = (startTime || 0) + (i / (numTicks - 1)) * viewingDuration; + labels.push(formatDuration(durationAtTick as IOtelSpan['duration'])); + } + } + const ticks: React.ReactNode[] = []; + for (let i = 0; i < numTicks; i++) { + const portion = i / (numTicks - 1); + ticks.push( +
+ {labels && ( + = 1 ? 'isEndAnchor' : ''}`}>{labels[i]} + )} +
+ ); + } + return
{ticks}
; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css new file mode 100644 index 0000000000..43448dd787 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.css @@ -0,0 +1,37 @@ +.TimelineCollapser { + align-items: center; + display: flex; + flex: none; + justify-content: center; + margin-right: 0.5rem; +} + +.TimelineCollapser--tooltipTitle { + white-space: pre; +} + +.TimelineCollapser--btn, +.TimelineCollapser--btn-expand { + color: var(--text-secondary); + cursor: pointer; + margin-right: 0.2rem; + padding: 0.1rem; +} + +.TimelineCollapser--btn:hover, +.TimelineCollapser--btn-expand:hover { + color: var(--interactive-primary-hover); +} + +.TimelineCollapser--btn-size { + font-size: 22px; +} + +.TimelineCollapser--btn-down { + transform: rotate(90deg); +} + +.TimelineCollapser .TimelineCollapser--btn-size:nth-child(3), +.TimelineCollapser .TimelineCollapser--btn-size:nth-child(4) { + font-size: 24px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx new file mode 100644 index 0000000000..fc4065f996 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineCollapser.tsx @@ -0,0 +1,57 @@ +import React, { useRef } from 'react'; +import { Tooltip } from 'antd'; +import { LuChevronsRight } from 'react-icons/lu'; +import { IoChevronForward } from 'react-icons/io5'; + +import './TimelineCollapser.css'; + +type CollapserProps = { + onCollapseAll: () => void; + onCollapseOne: () => void; + onExpandOne: () => void; + onExpandAll: () => void; +}; + +function getTitle(value: string) { + return {value}; +} + +export default function TimelineCollapser({ + onExpandAll, + onExpandOne, + onCollapseAll, + onCollapseOne, +}: CollapserProps) { + const containerRef = useRef(null); + + const getContainer = () => containerRef.current || document.body; + + return ( +
+ + + + + + + + + + + + +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css new file mode 100644 index 0000000000..86195f6807 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.css @@ -0,0 +1,33 @@ +.TimelineHeaderRow { + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + + border: 1px solid rgba(255, 255, 255, 0.3); + /* box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); */ + border-bottom: 1px solid var(--border-strong); + color: var(--text-primary); + height: 38px; + line-height: 38px; + position: relative; + width: 100%; + z-index: 4; +} + +.TimelineHeaderRow::before { + content: ''; + position: absolute; + top: -1px; + left: 0; + right: 0; + height: 1px; + background: inherit; +} + +.TimelineHeaderRow--title { + flex: 1; + overflow: hidden; + margin: 0; + text-overflow: ellipsis; + white-space: nowrap; +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx new file mode 100644 index 0000000000..84134f0c24 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineHeaderRow.tsx @@ -0,0 +1,69 @@ +import * as React from 'react'; +import TimelineCollapser from './TimelineCollapser'; +import TimelineViewingLayer from './TimelineViewingLayer'; +import Ticks from '../Ticks'; +import TimelineRow from '../TimelineRow'; +import { TUpdateViewRangeTimeFunction, IViewRangeTime, ViewRangeTimeUpdate } from '../../types'; +import { IOtelSpan } from '../../../../types/otel'; + +import './TimelineHeaderRow.css'; +import VerticalResizer from '../../../common/VerticalResizer'; + +type TimelineHeaderRowProps = { + duration: number; + nameColumnWidth: number; + numTicks: number; + onCollapseAll: () => void; + onCollapseOne: () => void; + onColummWidthChange: (width: number) => void; + onExpandAll: () => void; + onExpandOne: () => void; + updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + viewRangeTime: IViewRangeTime; + useOtelTerms: boolean; +}; + +export default function TimelineHeaderRow(props: TimelineHeaderRowProps) { + const { + duration, + nameColumnWidth, + numTicks, + onCollapseAll, + onCollapseOne, + onColummWidthChange, + onExpandAll, + onExpandOne, + updateViewRangeTime, + updateNextViewRangeTime, + viewRangeTime, + } = props; + const [viewStart, viewEnd] = viewRangeTime.current; + const startTime = (viewStart * duration) as IOtelSpan['startTime']; + const endTime = (viewEnd * duration) as IOtelSpan['endTime']; + return ( + + +

+ Service & {props.useOtelTerms ? 'Span Name' : 'Operation'} +

+ +
+ + + + + +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.css new file mode 100644 index 0000000000..8b89ac7f4e --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.css @@ -0,0 +1,51 @@ +.TimelineViewingLayer { + bottom: 0; + cursor: vertical-text; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.TimelineViewingLayer--cursorGuide { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 1px; + background-color: red; +} + +.TimelineViewingLayer--dragged { + position: absolute; + top: 0; + bottom: 0; +} + +.TimelineViewingLayer--dragged.isDraggingLeft { + border-left: 1px solid; +} + +.TimelineViewingLayer--dragged.isDraggingRight { + border-right: 1px solid; +} + +.TimelineViewingLayer--dragged.isShiftDrag { + background-color: rgba(68, 68, 255, 0.2); + border-color: #44f; +} + +.TimelineViewingLayer--dragged.isReframeDrag { + background-color: rgba(255, 68, 68, 0.2); + border-color: #f44; +} + +.TimelineViewingLayer--fullOverlay { + bottom: 0; + cursor: col-resize; + left: 0; + position: fixed; + right: 0; + top: 0; + user-select: none; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx new file mode 100644 index 0000000000..b590c2aab9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/TimelineViewingLayer.tsx @@ -0,0 +1,203 @@ +import * as React from 'react'; +import cx from 'classnames'; + +import { TUpdateViewRangeTimeFunction, IViewRangeTime, ViewRangeTimeUpdate } from '../../types'; +import { TNil } from '../../../../types'; +import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../../../utils/DraggableManager'; + +import './TimelineViewingLayer.css'; + +type TimelineViewingLayerProps = { + /** + * `boundsInvalidator` is an arbitrary prop that lets the component know the + * bounds for dragging need to be recalculated. In practice, the name column + * width serves fine for this. + */ + boundsInvalidator: any | null | undefined; + updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + viewRangeTime: IViewRangeTime; +}; + +type TDraggingLeftLayout = { + isDraggingLeft: boolean; + left: string; + width: string; +}; + +type TOutOfViewLayout = { + isOutOfView: true; +}; + +function isOutOfView(layout: TDraggingLeftLayout | TOutOfViewLayout): layout is TOutOfViewLayout { + return Reflect.has(layout, 'isOutOfView'); +} + +/** + * Map from a sub range to the greater view range, e.g, when the view range is + * the middle half ([0.25, 0.75]), a value of 0.25 befomes 3/8. + * @returns {number} + */ +function mapFromViewSubRange(viewStart: number, viewEnd: number, value: number) { + return viewStart + value * (viewEnd - viewStart); +} + +/** + * Map a value from the view ([0, 1]) to a sub-range, e.g, when the view range is + * the middle half ([0.25, 0.75]), a value of 3/8 becomes 1/4. + * @returns {number} + */ +function mapToViewSubRange(viewStart: number, viewEnd: number, value: number) { + return (value - viewStart) / (viewEnd - viewStart); +} + +/** + * Get the layout for the "next" view range time, e.g. the difference from the + * drag start and the drag end. This is driven by `shiftStart`, `shiftEnd` or + * `reframe` on `props.viewRangeTime`, not by the current state of the + * component. So, it reflects in-progress dragging from the span minimap. + */ +function getNextViewLayout(start: number, position: number): TDraggingLeftLayout | TOutOfViewLayout { + let [left, right] = start < position ? [start, position] : [position, start]; + if (left >= 1 || right <= 0) { + return { isOutOfView: true }; + } + if (left < 0) { + left = 0; + } + if (right > 1) { + right = 1; + } + return { + isDraggingLeft: start > position, + left: `${left * 100}%`, + width: `${(right - left) * 100}%`, + }; +} + +/** + * Render the visual indication of the "next" view range. + */ +function getMarkers( + viewStart: number, + viewEnd: number, + from: number, + to: number, + isShift: boolean +): React.ReactNode { + const mappedFrom = mapToViewSubRange(viewStart, viewEnd, from); + const mappedTo = mapToViewSubRange(viewStart, viewEnd, to); + const layout = getNextViewLayout(mappedFrom, mappedTo); + if (isOutOfView(layout)) { + return null; + } + const { isDraggingLeft, left, width } = layout; + const cls = cx({ + isDraggingLeft, + isDraggingRight: !isDraggingLeft, + isReframeDrag: !isShift, + isShiftDrag: isShift, + }); + return
; +} + +/** + * `TimelineViewingLayer` is rendered on top of the TimelineHeaderRow time + * labels; it handles showing the current view range and handles mouse UX for + * modifying it. + */ +export default class TimelineViewingLayer extends React.PureComponent { + _draggerReframe: DraggableManager; + _root = React.createRef(); + + constructor(props: TimelineViewingLayerProps) { + super(props); + this._draggerReframe = new DraggableManager({ + getBounds: this._getDraggingBounds, + onDragEnd: this._handleReframeDragEnd, + onDragMove: this._handleReframeDragUpdate, + onDragStart: this._handleReframeDragUpdate, + onMouseLeave: this._handleReframeMouseLeave, + onMouseMove: this._handleReframeMouseMove, + }); + } + + componentDidUpdate(prevProps: Readonly) { + const { boundsInvalidator } = this.props; + if (prevProps.boundsInvalidator !== boundsInvalidator) { + this._draggerReframe.resetBounds(); + } + } + + componentWillUnmount() { + this._draggerReframe.dispose(); + } + + _getDraggingBounds = (): DraggableBounds => { + const current = this._root.current; + if (!current) { + throw new Error('Component must be mounted in order to determine DraggableBounds'); + } + const { left: clientXLeft, width } = current.getBoundingClientRect(); + return { clientXLeft, width }; + }; + + _handleReframeMouseMove = ({ value }: DraggingUpdate) => { + const [viewStart, viewEnd] = this.props.viewRangeTime.current; + const cursor = mapFromViewSubRange(viewStart, viewEnd, value); + this.props.updateNextViewRangeTime({ cursor }); + }; + + _handleReframeMouseLeave = () => { + this.props.updateNextViewRangeTime({ cursor: undefined }); + }; + + _getAnchorAndShift = (value: number) => { + const { current, reframe } = this.props.viewRangeTime; + const [viewStart, viewEnd] = current; + const shift = mapFromViewSubRange(viewStart, viewEnd, value); + const anchor = reframe ? reframe.anchor : shift; + return { anchor, shift }; + }; + + _handleReframeDragUpdate = ({ value }: DraggingUpdate) => { + const { anchor, shift } = this._getAnchorAndShift(value); + const update = { reframe: { anchor, shift } }; + this.props.updateNextViewRangeTime(update); + }; + + _handleReframeDragEnd = ({ manager, value }: DraggingUpdate) => { + const { anchor, shift } = this._getAnchorAndShift(value); + const [start, end] = shift < anchor ? [shift, anchor] : [anchor, shift]; + manager.resetBounds(); + this.props.updateViewRangeTime(start, end, 'timeline-header'); + }; + + render() { + const { viewRangeTime } = this.props; + const { current, cursor, reframe, shiftEnd, shiftStart } = viewRangeTime; + const [viewStart, viewEnd] = current; + const haveNextTimeRange = reframe != null || shiftEnd != null || shiftStart != null; + let cusrorPosition: string | TNil; + if (!haveNextTimeRange && cursor != null && cursor >= viewStart && cursor <= viewEnd) { + cusrorPosition = `${mapToViewSubRange(viewStart, viewEnd, cursor) * 100}%`; + } + return ( +
+ {cusrorPosition != null && ( +
+ )} + {reframe != null && getMarkers(viewStart, viewEnd, reframe.anchor, reframe.shift, false)} + {shiftEnd != null && getMarkers(viewStart, viewEnd, viewEnd, shiftEnd, true)} + {shiftStart != null && getMarkers(viewStart, viewEnd, viewStart, shiftStart, true)} +
+ ); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/index.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/index.ts new file mode 100644 index 0000000000..07e475e3d0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineHeaderRow/index.ts @@ -0,0 +1 @@ +export { default } from './TimelineHeaderRow'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.css new file mode 100644 index 0000000000..76afcc82cf --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.css @@ -0,0 +1,5 @@ +.flex-row { + display: flex; + flex: 0 1 auto; + flex-direction: row; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.tsx new file mode 100644 index 0000000000..d975d7d0c5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/TimelineRow.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; + +import './TimelineRow.css'; + +export default function TimelineRow({ + children, + className, +}: { + children: React.ReactNode; + className?: string; +}) { + return
{children}
; +} + +function TimelineRowCell({ + children, + className, + width, + style = {}, + onClick = () => {}, +}: { + children: React.ReactNode; + className?: string; + width: number; + style?: object; + onClick?: (event: React.MouseEvent) => void; +}) { + const widthPercent = `${width * 100}%`; + const mergedStyle = { ...style, flexBasis: widthPercent, maxWidth: widthPercent }; + return ( +
+ {children} +
+ ); +} + +TimelineRow.Cell = TimelineRowCell; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.css new file mode 100644 index 0000000000..dbd2433d42 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.css @@ -0,0 +1,13 @@ +.VirtualizedTraceView--spans { + /* padding-top: 38px; */ + /* position: relative; + z-index: 1; */ +} + +.VirtualizedTraceView--rowsWrapper { + width: 100%; +} + +.VirtualizedTraceView--row { + width: 100%; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.tsx new file mode 100644 index 0000000000..85d75efe72 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/VirtualizedTraceView.tsx @@ -0,0 +1,610 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import _isEqual from 'lodash/isEqual'; +import _groupBy from 'lodash/groupBy'; + +// import { History as RouterHistory, Location } from 'history'; + +import memoizeOne from 'memoize-one'; +import { Location, History } from 'history'; +import { actions } from './duck'; +import ListView from './ListView'; +import SpanBarRow from './SpanBarRow'; +import DetailState from './SpanDetail/DetailState'; +import SpanDetailRow from './SpanDetailRow'; +import { + createViewedBoundsFunc, + ViewedBoundsFunctionType, + findServerChildSpan, + isErrorSpan, + isKindClient, + isKindProducer, + spanContainsErredSpan, +} from './utils'; +import { Accessors } from '../ScrollManager'; +import { extractUiFindFromState, TExtractUiFindFromStateReturn } from '../../common/UiFindInput'; +import getLinks from '../../../model/link-patterns'; +import colorGenerator from '../../../utils/color-generator'; +import { TNil, ReduxState } from '../../../types'; +import { CriticalPathSection } from '../../../types/critical_path'; +import { IOtelSpan, IOtelTrace, IAttribute, IEvent } from '../../../types/otel'; +import TTraceTimeline from '../../../types/TTraceTimeline'; + +import './VirtualizedTraceView.css'; +import updateUiFind from '../../../utils/update-ui-find'; +import { PEER_SERVICE } from '../../../constants/tag-keys'; +import withRouteProps from '../../../utils/withRouteProps'; + +type RowState = { + isDetail: boolean; + span: IOtelSpan; + spanIndex: number; +}; + +type TVirtualizedTraceViewOwnProps = { + currentViewRangeTime: [number, number]; + findMatchesIDs: Set | TNil; + scrollToFirstVisibleSpan: () => void; + registerAccessors: (accesors: Accessors) => void; + trace: IOtelTrace; + criticalPath: CriticalPathSection[]; + useOtelTerms: boolean; +}; + +type TDispatchProps = { + childrenToggle: (spanID: string) => void; + clearShouldScrollToFirstUiFindMatch: () => void; + detailLogItemToggle: (spanID: string, log: IEvent) => void; + detailLogsToggle: (spanID: string) => void; + detailWarningsToggle: (spanID: string) => void; + detailReferencesToggle: (spanID: string) => void; + detailProcessToggle: (spanID: string) => void; + detailTagsToggle: (spanID: string) => void; + detailToggle: (spanID: string) => void; + setSpanNameColumnWidth: (width: number) => void; + setTrace: (trace: IOtelTrace | TNil, uiFind: string | TNil) => void; + focusUiFindMatches: (trace: IOtelTrace, uiFind: string | TNil, allowHide?: boolean) => void; +}; + +type RouteProps = { + location: Location; + history: History; +}; + +type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & + TDispatchProps & + TExtractUiFindFromStateReturn & + TTraceTimeline & + RouteProps; + +// export for tests +export const DEFAULT_HEIGHTS = { + bar: 28, + detail: 161, + detailWithLogs: 197, +}; + +const NUM_TICKS = 5; + +function generateRowStates( + spans: ReadonlyArray | TNil, + childrenHiddenIDs: Set, + detailStates: Map +): RowState[] { + if (!spans) { + return []; + } + let collapseDepth = null; + const rowStates = []; + for (let i = 0; i < spans.length; i++) { + const span = spans[i]; + const { spanID, depth } = span; + let hidden = false; + if (collapseDepth != null) { + if (depth >= collapseDepth) { + hidden = true; + } else { + collapseDepth = null; + } + } + if (hidden) { + continue; + } + if (childrenHiddenIDs.has(spanID)) { + collapseDepth = depth + 1; + } + rowStates.push({ + span, + isDetail: false, + spanIndex: i, + }); + if (detailStates.has(spanID)) { + rowStates.push({ + span, + isDetail: true, + spanIndex: i, + }); + } + } + return rowStates; +} + +function generateRowStatesFromTrace( + trace: IOtelTrace | TNil, + childrenHiddenIDs: Set, + detailStates: Map +): RowState[] { + if (!trace) { + return []; + } + return generateRowStates(trace.spans, childrenHiddenIDs, detailStates); +} + +function getCssClasses(currentViewRange: [number, number]) { + const [zoomStart, zoomEnd] = currentViewRange; + return cx({ + 'clipping-left': zoomStart > 0, + 'clipping-right': zoomEnd < 1, + }); +} + +function mergeChildrenCriticalPath( + trace: IOtelTrace, + spanID: string, + criticalPath: CriticalPathSection[] +): CriticalPathSection[] { + if (!criticalPath) { + return []; + } + // Define an array to store the IDs of the span and its descendants (if the span is collapsed) + const allRequiredSpanIds = new Set([spanID]); + + // Use pre-built spanMap + const spanMap = trace.spanMap; + + // If the span is collapsed, recursively find all of its descendants. + const findAllDescendants = (span: IOtelSpan) => { + if (span.hasChildren && span.childSpans.length > 0) { + span.childSpans.forEach(child => { + allRequiredSpanIds.add(child.spanID); + findAllDescendants(child); + }); + } + }; + + // Start from the initially selected span + const startingSpan = spanMap.get(spanID); + if (startingSpan) { + findAllDescendants(startingSpan); + } + + const criticalPathSections: CriticalPathSection[] = []; + criticalPath.forEach(each => { + if (allRequiredSpanIds.has(each.spanID)) { + if (criticalPathSections.length !== 0 && each.sectionEnd === criticalPathSections[0].sectionStart) { + // Merge Critical Paths if they are consecutive + criticalPathSections[0].sectionStart = each.sectionStart; + } else { + criticalPathSections.unshift({ ...each }); + } + } + }); + + return criticalPathSections; +} + +const memoizedGenerateRowStates = memoizeOne(generateRowStatesFromTrace); +const memoizedViewBoundsFunc = memoizeOne(createViewedBoundsFunc, _isEqual); +const memoizedGetCssClasses = memoizeOne(getCssClasses, _isEqual); +const memoizedCriticalPathsBySpanID = memoizeOne((criticalPath: CriticalPathSection[]) => + _groupBy(criticalPath, x => x.spanID) +); + +// export from tests +export class VirtualizedTraceViewImpl extends React.Component { + listView: ListView | TNil; + constructor(props: VirtualizedTraceViewProps) { + super(props); + const { setTrace, trace, uiFind } = props; + setTrace(trace, uiFind); + } + + componentDidMount(): void { + window.addEventListener('jaeger:list-resize', this._handleListResize); + window.addEventListener('jaeger:detail-measure', this._handleDetailMeasure as any); + } + + shouldComponentUpdate(nextProps: VirtualizedTraceViewProps) { + // If any prop updates, VirtualizedTraceViewImpl should update. + const nextPropKeys = Object.keys(nextProps) as (keyof VirtualizedTraceViewProps)[]; + for (let i = 0; i < nextPropKeys.length; i += 1) { + if (nextProps[nextPropKeys[i]] !== this.props[nextPropKeys[i]]) { + // Unless the only change was props.shouldScrollToFirstUiFindMatch changing to false. + if (nextPropKeys[i] === 'shouldScrollToFirstUiFindMatch') { + if (nextProps[nextPropKeys[i]]) return true; + } else { + return true; + } + } + } + return false; + } + + componentDidUpdate(prevProps: Readonly) { + const { registerAccessors, trace } = prevProps; + const { + shouldScrollToFirstUiFindMatch, + clearShouldScrollToFirstUiFindMatch, + scrollToFirstVisibleSpan, + registerAccessors: nextRegisterAccessors, + setTrace, + trace: nextTrace, + uiFind, + } = this.props; + + if (trace !== nextTrace) { + setTrace(nextTrace, uiFind); + } + + if (this.listView && registerAccessors !== nextRegisterAccessors) { + nextRegisterAccessors(this.getAccessors()); + } + + if (shouldScrollToFirstUiFindMatch) { + scrollToFirstVisibleSpan(); + clearShouldScrollToFirstUiFindMatch(); + } + } + + componentWillUnmount(): void { + window.removeEventListener('jaeger:list-resize', this._handleListResize); + window.removeEventListener('jaeger:detail-measure', this._handleDetailMeasure as any); + } + + _handleListResize = () => { + if (this.listView) { + // Force ListView to update and re-scan item heights + this.listView.forceUpdate(); + } + }; + + _handleDetailMeasure = (evt: { detail?: { spanID?: string } }) => { + const spanID = evt && evt.detail && evt.detail.spanID; + if (!this.listView || !spanID) { + this._handleListResize(); + return; + } + // Force the list to re-scan heights + this.listView.forceUpdate(); + }; + + getRowStates(): RowState[] { + const { childrenHiddenIDs, detailStates, trace } = this.props; + return memoizedGenerateRowStates(trace, childrenHiddenIDs, detailStates); + } + + getClippingCssClasses(): string { + const { currentViewRangeTime } = this.props; + return memoizedGetCssClasses(currentViewRangeTime); + } + + getViewedBounds(): ViewedBoundsFunctionType { + const { currentViewRangeTime, trace } = this.props; + const [zoomStart, zoomEnd] = currentViewRangeTime; + + return memoizedViewBoundsFunc({ + min: trace.startTime, + max: trace.endTime, + viewStart: zoomStart, + viewEnd: zoomEnd, + }); + } + + focusSpan = (uiFind: string) => { + const { trace, focusUiFindMatches, location, history } = this.props; + if (trace) { + updateUiFind({ + location, + history, + uiFind, + }); + focusUiFindMatches(trace, uiFind, false); + } + }; + + getAccessors() { + const lv = this.listView; + if (!lv) { + throw new Error('ListView unavailable'); + } + return { + getViewRange: this.getViewRange, + getSearchedSpanIDs: this.getSearchedSpanIDs, + getCollapsedChildren: this.getCollapsedChildren, + getViewHeight: lv.getViewHeight, + getBottomRowIndexVisible: lv.getBottomVisibleIndex, + getTopRowIndexVisible: lv.getTopVisibleIndex, + getRowPosition: lv.getRowPosition, + mapRowIndexToSpanIndex: this.mapRowIndexToSpanIndex, + mapSpanIndexToRowIndex: this.mapSpanIndexToRowIndex, + }; + } + + getViewRange = () => this.props.currentViewRangeTime; + + getSearchedSpanIDs = () => this.props.findMatchesIDs; + + getCollapsedChildren = () => this.props.childrenHiddenIDs; + + mapRowIndexToSpanIndex = (index: number) => this.getRowStates()[index].spanIndex; + + mapSpanIndexToRowIndex = (index: number) => { + const max = this.getRowStates().length; + for (let i = 0; i < max; i++) { + const { spanIndex } = this.getRowStates()[i]; + if (spanIndex === index) { + return i; + } + } + throw new Error(`unable to find row for span index: ${index}`); + }; + + setListView = (listView: ListView | TNil) => { + const isChanged = this.listView !== listView; + this.listView = listView; + if (listView && isChanged) { + this.props.registerAccessors(this.getAccessors()); + } + }; + + // use long form syntax to avert flow error + // https://github.com/facebook/flow/issues/3076#issuecomment-290944051 + getKeyFromIndex = (index: number) => { + const { isDetail, span } = this.getRowStates()[index]; + return `${span.spanID}--${isDetail ? 'detail' : 'bar'}`; + }; + + getIndexFromKey = (key: string) => { + const parts = key.split('--'); + const _spanID = parts[0]; + const _isDetail = parts[1] === 'detail'; + const max = this.getRowStates().length; + for (let i = 0; i < max; i++) { + const { span, isDetail } = this.getRowStates()[i]; + if (span.spanID === _spanID && isDetail === _isDetail) { + return i; + } + } + return -1; + }; + + getRowHeight = (index: number) => { + const { span, isDetail } = this.getRowStates()[index]; + if (!isDetail) { + return DEFAULT_HEIGHTS.bar; + } + if (Array.isArray(span.events) && span.events.length) { + return DEFAULT_HEIGHTS.detailWithLogs; + } + return DEFAULT_HEIGHTS.detail; + }; + + linksGetter = (span: IOtelSpan, items: ReadonlyArray, itemIndex: number) => { + const { trace } = this.props; + if (!trace) return []; + return getLinks(span, items, itemIndex, trace); + }; + + // Adapter for OTEL components that need links from attributes + linksGetterFromAttributes = (span: IOtelSpan) => (attributes: ReadonlyArray, index: number) => { + return this.linksGetter(span, attributes, index); + }; + + // Adapter for OTEL event toggle to legacy log toggle + eventItemToggleAdapter = + (detailLogItemToggle: (spanID: string, log: IEvent) => void) => (spanID: string, event: IEvent) => { + // Pass the IEvent directly. + detailLogItemToggle(spanID, event); + }; + + renderRow = (key: string, style: React.CSSProperties, index: number, attrs: object) => { + const { isDetail, span, spanIndex } = this.getRowStates()[index]; + return isDetail + ? this.renderSpanDetailRow(span, key, style, attrs) + : this.renderSpanBarRow(span, spanIndex, key, style, attrs); + }; + + getCriticalPathSections( + isCollapsed: boolean, + trace: IOtelTrace, + spanID: string, + criticalPath: CriticalPathSection[] + ) { + if (isCollapsed) { + return mergeChildrenCriticalPath(trace, spanID, criticalPath); + } + + const pathBySpanID = memoizedCriticalPathsBySpanID(criticalPath); + return spanID in pathBySpanID ? pathBySpanID[spanID] : []; + } + + renderSpanBarRow( + span: IOtelSpan, + spanIndex: number, + key: string, + style: React.CSSProperties, + attrs: object + ) { + const { spanID } = span; + const { serviceName } = span.resource; + const { + childrenHiddenIDs, + childrenToggle, + detailStates, + detailToggle, + findMatchesIDs, + spanNameColumnWidth, + trace, + criticalPath, + useOtelTerms, + } = this.props; + // to avert flow error + if (!trace) { + return null; + } + + const { spans } = trace; + + const color = colorGenerator.getColorByKey(serviceName); + const isCollapsed = childrenHiddenIDs.has(spanID); + const isDetailExpanded = detailStates.has(spanID); + const isMatchingFilter = findMatchesIDs ? findMatchesIDs.has(spanID) : false; + const hasOwnError = isErrorSpan(span); + const hasChildError = isCollapsed && spanContainsErredSpan(spans, spanIndex); + const criticalPathSections = this.getCriticalPathSections(isCollapsed, trace, spanID, criticalPath); + // Check for direct child "server" span if the span is a "client" span. + let rpc = null; + if (isCollapsed) { + const rpcSpan = findServerChildSpan(spans.slice(spanIndex)); + if (rpcSpan) { + const rpcViewBounds = this.getViewedBounds()(rpcSpan.startTime, rpcSpan.endTime); + rpc = { + color: colorGenerator.getColorByKey(rpcSpan.resource.serviceName), + operationName: rpcSpan.name, + serviceName: rpcSpan.resource.serviceName, + viewEnd: rpcViewBounds.end, + viewStart: rpcViewBounds.start, + }; + } + } + const peerServiceAttr = span.attributes.find(attr => attr.key === PEER_SERVICE); + // Leaf, kind == client and has peer.service tag, is likely a client span that does a request + // to an uninstrumented/external service + let noInstrumentedServer = null; + if (!span.hasChildren && peerServiceAttr && (isKindClient(span) || isKindProducer(span))) { + noInstrumentedServer = { + serviceName: String(peerServiceAttr.value), + color: colorGenerator.getColorByKey(String(peerServiceAttr.value)), + }; + } + + return ( +
+ +
+ ); + } + + renderSpanDetailRow(span: IOtelSpan, key: string, style: React.CSSProperties, attrs: object) { + const { spanID } = span; + const { serviceName } = span.resource; + const { + detailLogItemToggle, + detailLogsToggle, + detailProcessToggle, + detailReferencesToggle, + detailWarningsToggle, + detailStates, + detailTagsToggle, + detailToggle, + spanNameColumnWidth, + trace, + currentViewRangeTime, + useOtelTerms, + } = this.props; + const detailState = detailStates.get(spanID); + if (!trace || !detailState) { + return null; + } + + const color = colorGenerator.getColorByKey(serviceName); + return ( +
+ +
+ ); + } + + render() { + return ( +
+ +
+ ); + } +} + +/* istanbul ignore next */ +function mapStateToProps(state: ReduxState): TTraceTimeline & TExtractUiFindFromStateReturn { + return { + ...extractUiFindFromState(state), + ...state.traceTimeline, + }; +} + +/* istanbul ignore next */ +function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { + return bindActionCreators(actions, dispatch) as any as TDispatchProps; +} + +export default connect< + TTraceTimeline & TExtractUiFindFromStateReturn, + TDispatchProps, + TVirtualizedTraceViewOwnProps, + ReduxState +>( + mapStateToProps, + mapDispatchToProps +)(withRouteProps(VirtualizedTraceViewImpl)); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/duck.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/duck.ts new file mode 100644 index 0000000000..35163c3dd5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/duck.ts @@ -0,0 +1,317 @@ +import { Action, ActionFunctionAny, createActions, handleActions } from 'redux-actions'; + +import DetailState from './SpanDetail/DetailState'; +import { TNil } from '../../../types'; +import { IOtelSpan, IOtelTrace, IEvent } from '../../../types/otel'; +import TTraceTimeline from '../../../types/TTraceTimeline'; +import filterSpans from '../../../utils/filter-spans'; +import generateActionTypes from '../../../utils/generate-action-types'; +import guardReducer from '../../../utils/guardReducer'; +import spanAncestorIds from '../../../utils/span-ancestor-ids'; + +// payloads +export type TSpanIdLogValue = { logItem: IEvent; spanID: string }; +export type TSpanIdValue = { spanID: string }; +type TSpansValue = { spans: IOtelSpan[] }; +type TTraceUiFindValue = { trace: IOtelTrace; uiFind: string | TNil; allowHide?: boolean }; +export type TWidthValue = { width: number }; +export type TActionTypes = + | TSpanIdLogValue + | TSpanIdValue + | TSpansValue + | TTraceUiFindValue + | TWidthValue + | object; + +type TTimelineViewerActions = { + [actionName: string]: ActionFunctionAny>; +}; + +function shouldDisableCollapse(allSpans: IOtelSpan[], hiddenSpansIds: Set) { + const allParentSpans = allSpans.filter(s => s.hasChildren); + return allParentSpans.length === hiddenSpansIds.size; +} + +export function newInitialState(): TTraceTimeline { + return { + childrenHiddenIDs: new Set(), + detailStates: new Map(), + hoverIndentGuideIds: new Set(), + shouldScrollToFirstUiFindMatch: false, + spanNameColumnWidth: parseFloat(localStorage.getItem('spanNameColumnWidth') || '0.25'), + traceID: null, + }; +} + +export const actionTypes = generateActionTypes('@jaeger-ui/trace-timeline-viewer', [ + 'ADD_HOVER_INDENT_GUIDE_ID', + 'CHILDREN_TOGGLE', + 'CLEAR_SHOULD_SCROLL_TO_FIRST_UI_FIND_MATCH', + 'COLLAPSE_ALL', + 'COLLAPSE_ONE', + 'DETAIL_TOGGLE', + 'DETAIL_TAGS_TOGGLE', + 'DETAIL_PROCESS_TOGGLE', + 'DETAIL_LOGS_TOGGLE', + 'DETAIL_LOG_ITEM_TOGGLE', + 'DETAIL_WARNINGS_TOGGLE', + 'DETAIL_REFERENCES_TOGGLE', + 'EXPAND_ALL', + 'EXPAND_ONE', + 'FOCUS_UI_FIND_MATCHES', + 'REMOVE_HOVER_INDENT_GUIDE_ID', + 'SET_SPAN_NAME_COLUMN_WIDTH', + 'SET_TRACE', +]); + +const fullActions = createActions({ + [actionTypes.ADD_HOVER_INDENT_GUIDE_ID]: (spanID: string) => ({ spanID }), + [actionTypes.CHILDREN_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.CLEAR_SHOULD_SCROLL_TO_FIRST_UI_FIND_MATCH]: () => ({}), + [actionTypes.COLLAPSE_ALL]: (spans: IOtelSpan[]) => ({ spans }), + [actionTypes.COLLAPSE_ONE]: (spans: IOtelSpan[]) => ({ spans }), + [actionTypes.DETAIL_LOG_ITEM_TOGGLE]: (spanID: string, logItem: IEvent) => ({ logItem, spanID }), + [actionTypes.DETAIL_LOGS_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.EXPAND_ALL]: () => ({}), + [actionTypes.EXPAND_ONE]: (spans: IOtelSpan[]) => ({ spans }), + [actionTypes.DETAIL_PROCESS_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.DETAIL_WARNINGS_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.DETAIL_REFERENCES_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.DETAIL_TAGS_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.DETAIL_TOGGLE]: (spanID: string) => ({ spanID }), + [actionTypes.FOCUS_UI_FIND_MATCHES]: (trace: IOtelTrace, uiFind: string | TNil, allowHide?: boolean) => ({ + trace, + uiFind, + allowHide, + }), + [actionTypes.REMOVE_HOVER_INDENT_GUIDE_ID]: (spanID: string) => ({ spanID }), + [actionTypes.SET_SPAN_NAME_COLUMN_WIDTH]: (width: number) => ({ width }), + [actionTypes.SET_TRACE]: (trace: IOtelTrace, uiFind: string | TNil) => ({ trace, uiFind }), +}); + +export const actions = (fullActions as any).jaegerUi.traceTimelineViewer as TTimelineViewerActions; + +function calculateFocusedFindRowStates(uiFind: string, spans: ReadonlyArray, allowHide = true) { + const spansMap = new Map(); + const childrenHiddenIDs: Set = new Set(); + const detailStates: Map = new Map(); + let shouldScrollToFirstUiFindMatch = false; + + spans.forEach(span => { + spansMap.set(span.spanID, span); + if (allowHide) { + childrenHiddenIDs.add(span.spanID); + } + }); + const matchedSpanIds = filterSpans(uiFind, spans); + if (matchedSpanIds && matchedSpanIds.size) { + matchedSpanIds.forEach(spanID => { + const span = spansMap.get(spanID); + detailStates.set(spanID, new DetailState()); + spanAncestorIds(span).forEach(ancestorID => childrenHiddenIDs.delete(ancestorID)); + }); + shouldScrollToFirstUiFindMatch = true; + } + return { + childrenHiddenIDs, + detailStates, + shouldScrollToFirstUiFindMatch, + }; +} + +function focusUiFindMatches(state: TTraceTimeline, { uiFind, trace, allowHide }: TTraceUiFindValue) { + if (!uiFind) return state; + return { + ...state, + ...calculateFocusedFindRowStates(uiFind, trace.spans, allowHide), + }; +} + +function clearShouldScrollToFirstUiFindMatch(state: TTraceTimeline) { + if (state.shouldScrollToFirstUiFindMatch) { + return { ...state, shouldScrollToFirstUiFindMatch: false }; + } + return state; +} + +function setTrace(state: TTraceTimeline, { uiFind, trace }: TTraceUiFindValue) { + const { traceID, spans } = trace; + if (traceID === state.traceID) { + return state; + } + const { spanNameColumnWidth } = state; + + return Object.assign( + { ...newInitialState(), spanNameColumnWidth, traceID }, + uiFind ? calculateFocusedFindRowStates(uiFind, spans) : null + ); +} + +function setColumnWidth(state: TTraceTimeline, { width }: TWidthValue): TTraceTimeline { + localStorage.setItem('spanNameColumnWidth', width.toString()); + return { ...state, spanNameColumnWidth: width }; +} + +function childrenToggle(state: TTraceTimeline, { spanID }: TSpanIdValue): TTraceTimeline { + const childrenHiddenIDs = new Set(state.childrenHiddenIDs); + if (childrenHiddenIDs.has(spanID)) { + childrenHiddenIDs.delete(spanID); + } else { + childrenHiddenIDs.add(spanID); + } + return { ...state, childrenHiddenIDs }; +} + +export function expandAll(state: TTraceTimeline): TTraceTimeline { + const childrenHiddenIDs = new Set(); + return { ...state, childrenHiddenIDs }; +} + +export function collapseAll(state: TTraceTimeline, { spans }: TSpansValue) { + if (shouldDisableCollapse(spans, state.childrenHiddenIDs)) { + return state; + } + const childrenHiddenIDs = spans.reduce((res, s) => { + if (s.hasChildren) { + res.add(s.spanID); + } + return res; + }, new Set()); + return { ...state, childrenHiddenIDs }; +} + +export function collapseOne(state: TTraceTimeline, { spans }: TSpansValue) { + if (shouldDisableCollapse(spans, state.childrenHiddenIDs)) { + return state; + } + let nearestCollapsedAncestor: IOtelSpan | undefined; + const childrenHiddenIDs = spans.reduce((res, curSpan) => { + if (nearestCollapsedAncestor && curSpan.depth <= nearestCollapsedAncestor.depth) { + res.add(nearestCollapsedAncestor.spanID); + if (curSpan.hasChildren) { + nearestCollapsedAncestor = curSpan; + } + } else if (curSpan.hasChildren && !res.has(curSpan.spanID)) { + nearestCollapsedAncestor = curSpan; + } + return res; + }, new Set(state.childrenHiddenIDs)); + // The last one + if (nearestCollapsedAncestor) { + childrenHiddenIDs.add(nearestCollapsedAncestor.spanID); + } + return { ...state, childrenHiddenIDs }; +} + +export function expandOne(state: TTraceTimeline, { spans }: TSpansValue) { + if (state.childrenHiddenIDs.size === 0) { + return state; + } + let prevExpandedDepth = -1; + let expandNextHiddenSpan = true; + const childrenHiddenIDs = spans.reduce((res, s) => { + if (s.depth <= prevExpandedDepth) { + expandNextHiddenSpan = true; + } + if (expandNextHiddenSpan && res.has(s.spanID)) { + res.delete(s.spanID); + expandNextHiddenSpan = false; + prevExpandedDepth = s.depth; + } + return res; + }, new Set(state.childrenHiddenIDs)); + return { ...state, childrenHiddenIDs }; +} + +function detailToggle(state: TTraceTimeline, { spanID }: TSpanIdValue) { + const detailStates = new Map(state.detailStates); + if (detailStates.has(spanID)) { + detailStates.delete(spanID); + } else { + detailStates.set(spanID, new DetailState()); + } + return { ...state, detailStates }; +} + +function detailSubsectionToggle( + subSection: 'tags' | 'process' | 'logs' | 'warnings' | 'references', + state: TTraceTimeline, + { spanID }: TSpanIdValue +) { + const old = state.detailStates.get(spanID); + if (!old) { + return state; + } + let detailState; + if (subSection === 'tags') { + detailState = old.toggleTags(); + } else if (subSection === 'process') { + detailState = old.toggleProcess(); + } else if (subSection === 'warnings') { + detailState = old.toggleWarnings(); + } else if (subSection === 'references') { + detailState = old.toggleReferences(); + } else { + detailState = old.toggleLogs(); + } + const detailStates = new Map(state.detailStates); + detailStates.set(spanID, detailState); + return { ...state, detailStates }; +} + +const detailTagsToggle = detailSubsectionToggle.bind(null, 'tags'); +const detailProcessToggle = detailSubsectionToggle.bind(null, 'process'); +const detailLogsToggle = detailSubsectionToggle.bind(null, 'logs'); +const detailWarningsToggle = detailSubsectionToggle.bind(null, 'warnings'); +const detailReferencesToggle = detailSubsectionToggle.bind(null, 'references'); + +function detailLogItemToggle(state: TTraceTimeline, { spanID, logItem }: TSpanIdLogValue) { + const old = state.detailStates.get(spanID); + if (!old) { + return state; + } + const detailState = old.toggleLogItem(logItem); + const detailStates = new Map(state.detailStates); + detailStates.set(spanID, detailState); + return { ...state, detailStates }; +} + +function addHoverIndentGuideId(state: TTraceTimeline, { spanID }: TSpanIdValue) { + const newHoverIndentGuideIds = new Set(state.hoverIndentGuideIds); + newHoverIndentGuideIds.add(spanID); + + return { ...state, hoverIndentGuideIds: newHoverIndentGuideIds }; +} + +function removeHoverIndentGuideId(state: TTraceTimeline, { spanID }: TSpanIdValue) { + const newHoverIndentGuideIds = new Set(state.hoverIndentGuideIds); + newHoverIndentGuideIds.delete(spanID); + + return { ...state, hoverIndentGuideIds: newHoverIndentGuideIds }; +} + +export default handleActions( + { + [actionTypes.ADD_HOVER_INDENT_GUIDE_ID]: guardReducer(addHoverIndentGuideId), + [actionTypes.CHILDREN_TOGGLE]: guardReducer(childrenToggle), + [actionTypes.CLEAR_SHOULD_SCROLL_TO_FIRST_UI_FIND_MATCH]: guardReducer( + clearShouldScrollToFirstUiFindMatch + ), + [actionTypes.COLLAPSE_ALL]: guardReducer(collapseAll), + [actionTypes.COLLAPSE_ONE]: guardReducer(collapseOne), + [actionTypes.DETAIL_LOGS_TOGGLE]: guardReducer(detailLogsToggle), + [actionTypes.DETAIL_LOG_ITEM_TOGGLE]: guardReducer(detailLogItemToggle), + [actionTypes.DETAIL_PROCESS_TOGGLE]: guardReducer(detailProcessToggle), + [actionTypes.DETAIL_WARNINGS_TOGGLE]: guardReducer(detailWarningsToggle), + [actionTypes.DETAIL_REFERENCES_TOGGLE]: guardReducer(detailReferencesToggle), + [actionTypes.DETAIL_TAGS_TOGGLE]: guardReducer(detailTagsToggle), + [actionTypes.DETAIL_TOGGLE]: guardReducer(detailToggle), + [actionTypes.EXPAND_ALL]: guardReducer(expandAll), + [actionTypes.EXPAND_ONE]: guardReducer(expandOne), + [actionTypes.FOCUS_UI_FIND_MATCHES]: guardReducer(focusUiFindMatches), + [actionTypes.REMOVE_HOVER_INDENT_GUIDE_ID]: guardReducer(removeHoverIndentGuideId), + [actionTypes.SET_SPAN_NAME_COLUMN_WIDTH]: guardReducer(setColumnWidth), + [actionTypes.SET_TRACE]: guardReducer(setTrace), + }, + newInitialState() +); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/grid.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/grid.css new file mode 100644 index 0000000000..b90477f1be --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/grid.css @@ -0,0 +1,810 @@ +/* Modified from https://github.com/kristoferjoseph/flexboxgrid */ +.container, +.container-fluid { + margin-right: auto; + margin-left: auto; +} +.container-fluid { + padding-right: 2rem; + padding-left: 2rem; +} +.row { + box-sizing: border-box; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 0; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + -webkit-box-orient: horizontal; + -webkit-box-direction: normal; + -ms-flex-direction: row; + flex-direction: row; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -0.5rem; + margin-left: -0.5rem; +} +.row.reverse { + -webkit-box-orient: horizontal; + -webkit-box-direction: reverse; + -ms-flex-direction: row-reverse; + flex-direction: row-reverse; +} +.col.reverse { + -webkit-box-orient: vertical; + -webkit-box-direction: reverse; + -ms-flex-direction: column-reverse; + flex-direction: column-reverse; +} +.col-xs, +.col-xs-1, +.col-xs-10, +.col-xs-11, +.col-xs-12, +.col-xs-2, +.col-xs-3, +.col-xs-4, +.col-xs-5, +.col-xs-6, +.col-xs-7, +.col-xs-8, +.col-xs-9, +.col-xs-offset-0, +.col-xs-offset-1, +.col-xs-offset-10, +.col-xs-offset-11, +.col-xs-offset-12, +.col-xs-offset-2, +.col-xs-offset-3, +.col-xs-offset-4, +.col-xs-offset-5, +.col-xs-offset-6, +.col-xs-offset-7, +.col-xs-offset-8, +.col-xs-offset-9 { + box-sizing: border-box; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + padding-right: 0; + padding-left: 0; +} +.col-xs { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; +} +.col-xs-1 { + -ms-flex-preferred-size: 8.33333333%; + flex-basis: 8.33333333%; + max-width: 8.33333333%; +} +.col-xs-2 { + -ms-flex-preferred-size: 16.66666667%; + flex-basis: 16.66666667%; + max-width: 16.66666667%; +} +.col-xs-3 { + -ms-flex-preferred-size: 25%; + flex-basis: 25%; + max-width: 25%; +} +.col-xs-4 { + -ms-flex-preferred-size: 33.33333333%; + flex-basis: 33.33333333%; + max-width: 33.33333333%; +} +.col-xs-5 { + -ms-flex-preferred-size: 41.66666667%; + flex-basis: 41.66666667%; + max-width: 41.66666667%; +} +.col-xs-6 { + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + max-width: 50%; +} +.col-xs-7 { + -ms-flex-preferred-size: 58.33333333%; + flex-basis: 58.33333333%; + max-width: 58.33333333%; +} +.col-xs-8 { + -ms-flex-preferred-size: 66.66666667%; + flex-basis: 66.66666667%; + max-width: 66.66666667%; +} +.col-xs-9 { + -ms-flex-preferred-size: 75%; + flex-basis: 75%; + max-width: 75%; +} +.col-xs-10 { + -ms-flex-preferred-size: 83.33333333%; + flex-basis: 83.33333333%; + max-width: 83.33333333%; +} +.col-xs-11 { + -ms-flex-preferred-size: 91.66666667%; + flex-basis: 91.66666667%; + max-width: 91.66666667%; +} +.col-xs-12 { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + max-width: 100%; +} +.col-xs-offset-0 { + margin-left: 0; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.start-xs { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: start; +} +.center-xs { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; +} +.end-xs { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: end; +} +.top-xs { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; +} +.middle-xs { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; +} +.bottom-xs { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; +} +.around-xs { + -ms-flex-pack: distribute; + justify-content: space-around; +} +.between-xs { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; +} +.first-xs { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; +} +.last-xs { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; +} +@media only screen and (min-width: 48em) { + .container { + width: 49rem; + } + .col-sm, + .col-sm-1, + .col-sm-10, + .col-sm-11, + .col-sm-12, + .col-sm-2, + .col-sm-3, + .col-sm-4, + .col-sm-5, + .col-sm-6, + .col-sm-7, + .col-sm-8, + .col-sm-9, + .col-sm-offset-0, + .col-sm-offset-1, + .col-sm-offset-10, + .col-sm-offset-11, + .col-sm-offset-12, + .col-sm-offset-2, + .col-sm-offset-3, + .col-sm-offset-4, + .col-sm-offset-5, + .col-sm-offset-6, + .col-sm-offset-7, + .col-sm-offset-8, + .col-sm-offset-9 { + box-sizing: border-box; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + padding-right: 0; + padding-left: 0; + } + .col-sm { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; + } + .col-sm-1 { + -ms-flex-preferred-size: 8.33333333%; + flex-basis: 8.33333333%; + max-width: 8.33333333%; + } + .col-sm-2 { + -ms-flex-preferred-size: 16.66666667%; + flex-basis: 16.66666667%; + max-width: 16.66666667%; + } + .col-sm-3 { + -ms-flex-preferred-size: 25%; + flex-basis: 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex-preferred-size: 33.33333333%; + flex-basis: 33.33333333%; + max-width: 33.33333333%; + } + .col-sm-5 { + -ms-flex-preferred-size: 41.66666667%; + flex-basis: 41.66666667%; + max-width: 41.66666667%; + } + .col-sm-6 { + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex-preferred-size: 58.33333333%; + flex-basis: 58.33333333%; + max-width: 58.33333333%; + } + .col-sm-8 { + -ms-flex-preferred-size: 66.66666667%; + flex-basis: 66.66666667%; + max-width: 66.66666667%; + } + .col-sm-9 { + -ms-flex-preferred-size: 75%; + flex-basis: 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex-preferred-size: 83.33333333%; + flex-basis: 83.33333333%; + max-width: 83.33333333%; + } + .col-sm-11 { + -ms-flex-preferred-size: 91.66666667%; + flex-basis: 91.66666667%; + max-width: 91.66666667%; + } + .col-sm-12 { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + max-width: 100%; + } + .col-sm-offset-0 { + margin-left: 0; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .start-sm { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: start; + } + .center-sm { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; + } + .end-sm { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: end; + } + .top-sm { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + } + .middle-sm { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + } + .bottom-sm { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + } + .around-sm { + -ms-flex-pack: distribute; + justify-content: space-around; + } + .between-sm { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + } + .first-sm { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .last-sm { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } +} +@media only screen and (min-width: 64em) { + .container { + width: 65rem; + } + .col-md, + .col-md-1, + .col-md-10, + .col-md-11, + .col-md-12, + .col-md-2, + .col-md-3, + .col-md-4, + .col-md-5, + .col-md-6, + .col-md-7, + .col-md-8, + .col-md-9, + .col-md-offset-0, + .col-md-offset-1, + .col-md-offset-10, + .col-md-offset-11, + .col-md-offset-12, + .col-md-offset-2, + .col-md-offset-3, + .col-md-offset-4, + .col-md-offset-5, + .col-md-offset-6, + .col-md-offset-7, + .col-md-offset-8, + .col-md-offset-9 { + box-sizing: border-box; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + padding-right: 0; + padding-left: 0; + } + .col-md { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; + } + .col-md-1 { + -ms-flex-preferred-size: 8.33333333%; + flex-basis: 8.33333333%; + max-width: 8.33333333%; + } + .col-md-2 { + -ms-flex-preferred-size: 16.66666667%; + flex-basis: 16.66666667%; + max-width: 16.66666667%; + } + .col-md-3 { + -ms-flex-preferred-size: 25%; + flex-basis: 25%; + max-width: 25%; + } + .col-md-4 { + -ms-flex-preferred-size: 33.33333333%; + flex-basis: 33.33333333%; + max-width: 33.33333333%; + } + .col-md-5 { + -ms-flex-preferred-size: 41.66666667%; + flex-basis: 41.66666667%; + max-width: 41.66666667%; + } + .col-md-6 { + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex-preferred-size: 58.33333333%; + flex-basis: 58.33333333%; + max-width: 58.33333333%; + } + .col-md-8 { + -ms-flex-preferred-size: 66.66666667%; + flex-basis: 66.66666667%; + max-width: 66.66666667%; + } + .col-md-9 { + -ms-flex-preferred-size: 75%; + flex-basis: 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex-preferred-size: 83.33333333%; + flex-basis: 83.33333333%; + max-width: 83.33333333%; + } + .col-md-11 { + -ms-flex-preferred-size: 91.66666667%; + flex-basis: 91.66666667%; + max-width: 91.66666667%; + } + .col-md-12 { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + max-width: 100%; + } + .col-md-offset-0 { + margin-left: 0; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .start-md { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: start; + } + .center-md { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; + } + .end-md { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: end; + } + .top-md { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + } + .middle-md { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + } + .bottom-md { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + } + .around-md { + -ms-flex-pack: distribute; + justify-content: space-around; + } + .between-md { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + } + .first-md { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .last-md { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } +} +@media only screen and (min-width: 75em) { + .container { + width: 76rem; + } + .col-lg, + .col-lg-1, + .col-lg-10, + .col-lg-11, + .col-lg-12, + .col-lg-2, + .col-lg-3, + .col-lg-4, + .col-lg-5, + .col-lg-6, + .col-lg-7, + .col-lg-8, + .col-lg-9, + .col-lg-offset-0, + .col-lg-offset-1, + .col-lg-offset-10, + .col-lg-offset-11, + .col-lg-offset-12, + .col-lg-offset-2, + .col-lg-offset-3, + .col-lg-offset-4, + .col-lg-offset-5, + .col-lg-offset-6, + .col-lg-offset-7, + .col-lg-offset-8, + .col-lg-offset-9 { + box-sizing: border-box; + -webkit-box-flex: 0; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + padding-right: 0; + padding-left: 0; + } + .col-lg { + -webkit-box-flex: 1; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-preferred-size: 0; + flex-basis: 0; + max-width: 100%; + } + .col-lg-1 { + -ms-flex-preferred-size: 8.33333333%; + flex-basis: 8.33333333%; + max-width: 8.33333333%; + } + .col-lg-2 { + -ms-flex-preferred-size: 16.66666667%; + flex-basis: 16.66666667%; + max-width: 16.66666667%; + } + .col-lg-3 { + -ms-flex-preferred-size: 25%; + flex-basis: 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex-preferred-size: 33.33333333%; + flex-basis: 33.33333333%; + max-width: 33.33333333%; + } + .col-lg-5 { + -ms-flex-preferred-size: 41.66666667%; + flex-basis: 41.66666667%; + max-width: 41.66666667%; + } + .col-lg-6 { + -ms-flex-preferred-size: 50%; + flex-basis: 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex-preferred-size: 58.33333333%; + flex-basis: 58.33333333%; + max-width: 58.33333333%; + } + .col-lg-8 { + -ms-flex-preferred-size: 66.66666667%; + flex-basis: 66.66666667%; + max-width: 66.66666667%; + } + .col-lg-9 { + -ms-flex-preferred-size: 75%; + flex-basis: 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex-preferred-size: 83.33333333%; + flex-basis: 83.33333333%; + max-width: 83.33333333%; + } + .col-lg-11 { + -ms-flex-preferred-size: 91.66666667%; + flex-basis: 91.66666667%; + max-width: 91.66666667%; + } + .col-lg-12 { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + max-width: 100%; + } + .col-lg-offset-0 { + margin-left: 0; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .start-lg { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start; + text-align: start; + } + .center-lg { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + text-align: center; + } + .end-lg { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end; + text-align: end; + } + .top-lg { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start; + } + .middle-lg { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + } + .bottom-lg { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end; + } + .around-lg { + -ms-flex-pack: distribute; + justify-content: space-around; + } + .between-lg { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between; + } + .first-lg { + -webkit-box-ordinal-group: 0; + -ms-flex-order: -1; + order: -1; + } + .last-lg { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.css new file mode 100644 index 0000000000..fcecfbd058 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.css @@ -0,0 +1,77 @@ +.TraceTimelineViewer { + /* border-bottom: 1px solid #bbb; */ +} + +.json-markup { + line-height: 17px; + font-size: 13px; + font-family: monospace; + + white-space: pre-wrap; + + word-break: break-all; + word-wrap: break-word; +} + +.json-markup-key { + font-weight: bold; + margin-right: 0.5rem; +} + +.json-markup-bool { + color: var(--syntax-bool); +} + +.json-markup-string { + color: var(--syntax-string); +} + +.json-markup-null, +.json-markup-undefined { + color: var(--syntax-null); +} + +.json-markup-number { + color: var(--syntax-number); +} + +.json-markup-other { + color: lightgrey; +} + +.json-markup-icon-expand { + margin-right: 4px; +} + +.json-markup-icon-expand::after { + content: '\25B6'; + color: var(--text-primary); + font-size: 12px; +} + +.json-markup-icon-collapse { + margin-right: 4px; +} + +.json-markup-icon-collapse::after { + content: '\25BC'; + color: var(--text-primary); + font-size: 12px; +} + +.json-markup-collapse-content::after { + content: '\2026'; + color: var(--text-primary); + font-size: 18px; + margin-right: 4px; +} + +.json-markup-child { + margin: 0.25rem; + width: 100%; + padding: 0; +} + +.json-markup-puncuation { + font-weight: bold; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.tsx new file mode 100644 index 0000000000..39918f432a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/index.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useEffect } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; + +import { actions } from './duck'; +import TimelineHeaderRow from './TimelineHeaderRow'; +import VirtualizedTraceView from './VirtualizedTraceView'; +import { Accessors } from '../ScrollManager'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate } from '../types'; +import { TNil, ReduxState } from '../../../types'; +import { IOtelSpan, IOtelTrace } from '../../../types/otel'; +import { CriticalPathSection } from '../../../types/critical_path'; + +import './index.css'; + +type TDispatchProps = { + setSpanNameColumnWidth: (width: number) => void; + collapseAll: (spans: ReadonlyArray) => void; + collapseOne: (spans: ReadonlyArray) => void; + expandAll: () => void; + expandOne: (spans: ReadonlyArray) => void; +}; + +type TProps = TDispatchProps & { + registerAccessors: (accessors: Accessors) => void; + findMatchesIDs: Set | TNil; + scrollToFirstVisibleSpan: () => void; + spanNameColumnWidth: number; + trace: IOtelTrace; + criticalPath: CriticalPathSection[]; + updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; + updateViewRangeTime: TUpdateViewRangeTimeFunction; + viewRange: IViewRange; + useOtelTerms: boolean; +}; + +const NUM_TICKS = 5; + +/** + * `TraceTimelineViewer` now renders the header row because it is sensitive to + * `props.viewRange.time.cursor`. If `VirtualizedTraceView` renders it, it will + * re-render the ListView every time the cursor is moved on the trace minimap + * or `TimelineHeaderRow`. + */ + +export const TraceTimelineViewerImpl = (props: TProps) => { + const collapseAll = useCallback(() => { + props.collapseAll(props.trace.spans); + }, [props.collapseAll, props.trace.spans]); + + const collapseOne = useCallback(() => { + props.collapseOne(props.trace.spans); + }, [props.collapseOne, props.trace.spans]); + + const expandAll = useCallback(() => { + props.expandAll(); + }, [props.expandAll]); + + const expandOne = useCallback(() => { + props.expandOne(props.trace.spans); + }, [props.expandOne, props.trace.spans]); + + const { setSpanNameColumnWidth, updateNextViewRangeTime, updateViewRangeTime, viewRange, ...rest } = props; + const { spanNameColumnWidth, trace } = rest; + + return ( +
+ + +
+ ); +}; + +function mapStateToProps(state: ReduxState) { + const spanNameColumnWidth = state.traceTimeline.spanNameColumnWidth; + return { spanNameColumnWidth }; +} + +function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { + const { setSpanNameColumnWidth, expandAll, expandOne, collapseAll, collapseOne } = bindActionCreators( + actions, + dispatch + ); + return { setSpanNameColumnWidth, expandAll, expandOne, collapseAll, collapseOne }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(TraceTimelineViewerImpl); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/utils.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/utils.ts new file mode 100644 index 0000000000..d2c261b6ae --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/TraceTimelineViewer/utils.ts @@ -0,0 +1,98 @@ +import { IOtelSpan, SpanKind, StatusCode } from '../../../types/otel'; + +export type ViewedBoundsFunctionType = (start: number, end: number) => { start: number; end: number }; +/** + * Given a range (`min`, `max`) and factoring in a zoom (`viewStart`, `viewEnd`) + * a function is created that will find the position of a sub-range (`start`, `end`). + * The calling the generated method will return the result as a `{ start, end }` + * object with values ranging in [0, 1]. + * + * @param {number} min The start of the outer range. + * @param {number} max The end of the outer range. + * @param {number} viewStart The start of the zoom, on a range of [0, 1], + * relative to the `min`, `max`. + * @param {number} viewEnd The end of the zoom, on a range of [0, 1], + * relative to the `min`, `max`. + * @returns {(number, number) => Object} Created view bounds function + */ +export function createViewedBoundsFunc(viewRange: { + min: number; + max: number; + viewStart: number; + viewEnd: number; +}) { + const { min, max, viewStart, viewEnd } = viewRange; + const duration = max - min; + const viewMin = min + viewStart * duration; + const viewMax = max - (1 - viewEnd) * duration; + const viewWindow = viewMax - viewMin; + + /** + * View bounds function + * @param {number} start The start of the sub-range. + * @param {number} end The end of the sub-range. + * @return {Object} The resultant range. + */ + return (start: number, end: number) => ({ + start: (start - viewMin) / viewWindow, + end: (end - viewMin) / viewWindow, + }); +} + +/** + * Returns `true` if the span has an error status. + * + * @param {IOtelSpan} span The OTEL span to check. + * @return {boolean} True if the span has an error status. + */ +export function isErrorSpan(span: IOtelSpan): boolean { + return span.status.code === StatusCode.ERROR; +} + +/** + * Returns `true` if at least one of the descendants of the `parentSpanIndex` + * span contains an error status. + * + * @param {IOtelSpan[]} spans The OTEL spans for a trace - should be + * sorted with children following parents. + * @param {number} parentSpanIndex The index of the parent span - only + * subsequent spans with depth less than + * the parent span will be checked. + * @return {boolean} Returns `true` if a descendant contains an error status. + */ +export function spanContainsErredSpan(spans: ReadonlyArray, parentSpanIndex: number): boolean { + const { depth } = spans[parentSpanIndex]; + let i = parentSpanIndex + 1; + for (; i < spans.length && spans[i].depth > depth; i++) { + if (isErrorSpan(spans[i])) { + return true; + } + } + return false; +} + +/** + * Expects the first span to be the parent span. + * Returns the first direct child span that is a SERVER span. + */ +export function findServerChildSpan(spans: ReadonlyArray): IOtelSpan | null | false { + if (spans.length <= 1 || spans[0].kind !== SpanKind.CLIENT) { + return false; + } + const span = spans[0]; + const spanChildDepth = span.depth + 1; + let i = 1; + while (i < spans.length && spans[i].depth === spanChildDepth) { + if (spans[i].kind === SpanKind.SERVER) { + return spans[i]; + } + i++; + } + return null; +} + +export const isKindClient = (span: IOtelSpan): boolean => span.kind === SpanKind.CLIENT; + +export const isKindProducer = (span: IOtelSpan): boolean => span.kind === SpanKind.PRODUCER; + +export { formatDuration } from '../../../utils/date'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Tween.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Tween.ts new file mode 100644 index 0000000000..4215fa511d --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/Tween.ts @@ -0,0 +1,100 @@ +import ease from 'tween-functions'; + +import { TNil } from '../../types'; + +interface ITweenState { + done: boolean; + value: number; +} + +type TTweenCallback = (state: ITweenState) => void; + +type TTweenOptions = { + delay?: number; + duration: number; + from: number; + onComplete?: TTweenCallback; + onUpdate?: TTweenCallback; + to: number; +}; + +export default class Tween { + callbackComplete: TTweenCallback | TNil; + callbackUpdate: TTweenCallback | TNil; + delay: number | TNil; + duration: number; + from: number; + requestID: number | TNil; + startTime: number; + timeoutID: number | TNil; + to: number; + + constructor({ duration, from, to, delay, onUpdate, onComplete }: TTweenOptions) { + this.startTime = Date.now() + (delay || 0); + this.duration = duration; + this.from = from; + this.to = to; + if (!onUpdate && !onComplete) { + this.callbackComplete = undefined; + this.callbackUpdate = undefined; + this.timeoutID = undefined; + this.requestID = undefined; + } else { + this.callbackComplete = onComplete; + this.callbackUpdate = onUpdate; + if (delay) { + // setTimeout from @types/node returns NodeJS.Timeout, so prefix with `window.` + this.timeoutID = window.setTimeout(this._frameCallback, delay); + this.requestID = undefined; + } else { + this.requestID = window.requestAnimationFrame(this._frameCallback); + this.timeoutID = undefined; + } + } + } + + _frameCallback = () => { + this.timeoutID = undefined; + this.requestID = undefined; + const current = Object.freeze(this.getCurrent()); + if (this.callbackUpdate) { + this.callbackUpdate(current); + } + if (this.callbackComplete && current.done) { + this.callbackComplete(current); + } + if (current.done) { + this.callbackComplete = undefined; + this.callbackUpdate = undefined; + } else { + this.requestID = window.requestAnimationFrame(this._frameCallback); + } + }; + + cancel() { + if (this.timeoutID != null) { + clearTimeout(this.timeoutID); + this.timeoutID = undefined; + } + if (this.requestID != null) { + window.cancelAnimationFrame(this.requestID); + this.requestID = undefined; + } + this.callbackComplete = undefined; + this.callbackUpdate = undefined; + } + + getCurrent(): ITweenState { + const t = Date.now() - this.startTime; + if (t <= 0) { + // still in the delay period + return { done: false, value: this.from }; + } + if (t >= this.duration) { + // after the expiration + return { done: true, value: this.to }; + } + // mid-tween + return { done: false, value: ease.easeOutQuint(t, this.from, this.to, this.duration) }; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.css b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.css new file mode 100644 index 0000000000..8b43a1b450 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.css @@ -0,0 +1,6 @@ +.Tracepage--headerSection { + background: var(--surface-primary); + position: relative !important; + width: 100%; + z-index: 3; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.tsx new file mode 100644 index 0000000000..1d41a7fc45 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/index.tsx @@ -0,0 +1,614 @@ +// TraceSearchPage.tsx +import * as React from 'react'; +import { Input, Button, Typography, Divider } from 'antd'; +import type { InputRef } from 'antd'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; +import memoizeOne from 'memoize-one'; +import store from 'store'; + +import SearchForm from './SearchForm'; +import { getUrlState, isSameQuery } from '../common/url'; +import * as jaegerApiActions from '../../actions/jaeger-api'; +import * as orderBy from '../../model/order-by'; +import { sortTraces } from '../../model/search'; +import LoadingIndicator from '../common/LoadingIndicator'; +import ErrorMessage from '../common/ErrorMessage'; +import { fetchedState } from '../../constants'; + +import withRouteProps from '../../utils/withRouteProps'; + +import type { ReduxState } from '../../types'; +import type { SearchQuery } from '../../types/search'; +import type { Trace } from '../../types/trace'; +import type { IOtelTrace } from '../../types/otel'; +import type { TUrlState } from '../common/url'; + +import './index.css' + +// TODO: adjust import path to your project structure +import { + TracePageImpl, + mapStateToProps as traceMapState, + mapDispatchToProps as traceMapDispatch, +} from './Trace'; + +const stripTraceKeys = (q: any) => { + if (!q) return q; + const { traceId, traceID, span, spanLinks, ...rest } = q; + return rest; +}; + +const { Text } = Typography; + +interface IServiceWithOperations { + name: string; + operations: string[]; +} + +interface IQueryOfResults extends Partial { + service?: string; + limit?: string | number; +} + +interface IEmbeddedConfig { + searchHideGraph?: boolean; +} + +type TOwnProps = { + history: any; + location: any; +}; + +type TStateProps = { + embedded?: IEmbeddedConfig; + + loadingServices: boolean; + loadingTraces: boolean; + services: IServiceWithOperations[] | null; + + // search results (from reducer) + traces: Trace[]; + traceResultsToDownload: unknown[]; + queryOfResults: IQueryOfResults | null; + urlQueryParams: TUrlState | null; + maxTraceDuration: number; + errors: Array<{ message: string }> | null; + + sortedTracesXformer: (traces: Trace[], sortBy: string) => IOtelTrace[]; + + isHomepage?: boolean; +}; + +type TDispatchProps = { + fetchServiceOperations: (service: string) => void; + fetchServices: () => void; + searchTraces: (query: TUrlState) => void; + + // for traceId direct open + fetchTrace: (id: string) => void; +}; + +type TProps = TOwnProps & TStateProps & TDispatchProps; + +type TViewMode = 'search' | 'trace'; + +type TState = { + sortBy: string; + traceIdInput: string; + viewMode: TViewMode; + selectedTraceId: string | null; +}; + +function normalizeTraceId(id: string) { + return (id || '').trim(); +} + +function guessTraceIdFromOtelTrace(t: IOtelTrace): string | null { + return ( + (t as any).traceID || + (t as any).traceId || + (t as any).traceIdLower || + (t as any).id || + null + ); +} + +/** ===== URL helpers for shareable state ===== */ +function getTraceIdFromSearch(search: string): string | null { + const sp = new URLSearchParams(search || ''); + const v = sp.get('traceId'); + return v ? v.trim() : null; +} + +function setTraceIdToSearch(search: string, traceId: string | null): string { + const sp = new URLSearchParams(search || ''); + if (traceId && traceId.trim()) sp.set('traceId', traceId.trim()); + else sp.delete('traceId'); + const next = sp.toString(); + return next ? `?${next}` : ''; +} + +/** + * Lightweight inline results list. + * Avoid reusing SearchResults if it navigates to /trace/:id internally. + */ +function SearchResultsInline(props: { + loading: boolean; + errors: Array<{ message: string }> | null; + traces: IOtelTrace[]; + onPickTrace: (traceId: string) => void; +}) { + const { loading, errors, traces, onPickTrace } = props; + + if (loading) return ; + + if (errors && errors.length) { + return ( +
+

There was an error loading traces:

+ {errors.map(err => ( + + ))} +
+ ); + } + + if (!traces.length) { + return ( +
+ No results. +
+ ); + } + + return ( +
+
+ Results + ({traces.length}) +
+ + {traces.map((t, idx) => { + const id = guessTraceIdFromOtelTrace(t); + const duration = (t as any).duration; + const startTime = (t as any).startTime; + const serviceName = (t as any).processes?.[0]?.serviceName || (t as any).rootServiceName; + + return ( +
+
+
+ {id || ''} +
+
+ {serviceName ? service: {serviceName} : null} + {typeof duration === 'number' ? duration: {duration} : null} + {startTime ? start: {String(startTime)} : null} +
+
+
+ +
+
+ ); + })} +
+ ); +} + +export class TraceSearchPageImpl extends React.PureComponent { + state: TState = { + sortBy: orderBy.MOST_RECENT, + traceIdInput: '', + viewMode: 'search', + selectedTraceId: null, + }; + + private _traceIdInputRef = React.createRef(); + + componentDidMount() { + const { fetchServiceOperations, fetchServices, queryOfResults, searchTraces, urlQueryParams, history } = this.props; + + // keep original SearchTracePage behavior: if url has query and differs from current results, search by url + // if (urlQueryParams && queryOfResults && !isSameQuery(urlQueryParams as any, queryOfResults as any)) { + // searchTraces(urlQueryParams); + // } + if (urlQueryParams && !isSameQuery(urlQueryParams as any, queryOfResults as any)) { + searchTraces(urlQueryParams); + } + + fetchServices(); + + // lastSearch -> fetchServiceOperations + let { service } = (store.get('lastSearch') as { service?: string } | undefined) || {}; + if (urlQueryParams && urlQueryParams.service) { + const urlService = urlQueryParams.service; + if (typeof urlService === 'string') service = urlService; + else if (Array.isArray(urlService)) service = urlService[0]; + } + if (service && service !== '-') { + fetchServiceOperations(service); + } + + // ✅ NEW: URL shareable trace state + const traceIdFromUrl = getTraceIdFromSearch(this.props.location.search); + if (traceIdFromUrl) { + this.setState({ + viewMode: 'trace', + selectedTraceId: traceIdFromUrl, + traceIdInput: traceIdFromUrl, + }); + this.props.fetchTrace(traceIdFromUrl); + } + } + + componentDidUpdate(prevProps: TProps) { + // Only react to URL search changes + if (prevProps.location.search === this.props.location.search) return; + + const nextSearchStr = this.props.location.search; + const prevSearchStr = prevProps.location.search; + + const nextTraceId = getTraceIdFromSearch(nextSearchStr); + const prevTraceId = getTraceIdFromSearch(prevSearchStr); + + const nextUrlState = getUrlState(nextSearchStr); + const prevUrlState = getUrlState(prevSearchStr); + + // Remove trace-related keys and only keep search-form-related params + const nextSearchState = stripTraceKeys(nextUrlState); + const prevSearchState = stripTraceKeys(prevUrlState); + + /** + * Determine whether the URL contains any "effective" search parameters. + * This avoids treating default-only params (e.g. lookback, limit) + * as an intentional search. + */ + const hasEffectiveSearchParam = (() => { + if (!nextSearchState) return false; + + // Service is considered effective if it is present and not "-" + const svc = (nextSearchState as any).service; + const service = + typeof svc === 'string' ? svc : Array.isArray(svc) ? svc[0] : undefined; + if (service && service !== '-') return true; + + // Any other meaningful search fields also count as effective + const keys = ['operation', 'tags', 'minDuration', 'maxDuration', 'start', 'end']; + return keys.some(k => { + const v = (nextSearchState as any)[k]; + // `true` may come from query-string when a key has no explicit value + return v !== undefined && v !== null && v !== '' && v !== true; + }); + })(); + + /** + * Priority rules: + * + * 1) If URL contains `traceId`, always switch to trace view and fetch the trace. + * 2) If no `traceId` but URL contains effective search params, trigger a search. + * 3) If neither exists, restore the pure initial (homepage) state. + */ + + // ===== 1) Trace view synchronization (highest priority) ===== + if (prevTraceId !== nextTraceId) { + if (nextTraceId) { + // Enter trace view + if ( + this.state.viewMode !== 'trace' || + this.state.selectedTraceId !== nextTraceId || + this.state.traceIdInput !== nextTraceId + ) { + this.setState({ + viewMode: 'trace', + selectedTraceId: nextTraceId, + traceIdInput: nextTraceId, + }); + } + this.props.fetchTrace(nextTraceId); + } else { + // traceId was removed from URL + // Do not decide the next view mode here; + // let the search-param logic below handle it. + if (this.state.viewMode === 'trace' || this.state.selectedTraceId) { + this.setState({ viewMode: 'search', selectedTraceId: null }); + } + } + } + + // If traceId exists, do not allow search logic to override trace view + if (nextTraceId) return; + + // ===== 2) Search query synchronization (only when no traceId) ===== + if (hasEffectiveSearchParam) { + const currentComparable = stripTraceKeys(this.props.queryOfResults); + + // Trigger search only when URL search state differs from current results + if ( + !currentComparable || + !isSameQuery(nextSearchState as any, currentComparable as any) + ) { + this.props.searchTraces(nextSearchState); + } + + // Ensure we are in search view + if (this.state.viewMode !== 'search') { + this.setState({ viewMode: 'search', selectedTraceId: null }); + } + return; + } + + // ===== 3) Pure initial (homepage) state ===== + // No traceId and no effective search params: + // restore the clean initial view without dispatching any search action + if (this.state.viewMode !== 'search' || this.state.selectedTraceId) { + this.setState({ viewMode: 'search', selectedTraceId: null }); + } + } + + + private pickTrace = (traceIdRaw: string) => { + const traceId = normalizeTraceId(traceIdRaw); + if (!traceId) return; + + // 1) write to URL for shareable state (keep other search params) + const nextSearch = setTraceIdToSearch('', traceId); + if (nextSearch !== this.props.location.search) { + this.props.history.push({ ...this.props.location, search: nextSearch }); + setTimeout(() => console.log('[pickTrace] after push', window.location.href), 0); + } + + // 2) local switch + this.setState({ selectedTraceId: traceId, viewMode: 'trace', traceIdInput: traceId }); + + // 3) fetch trace + this.props.fetchTrace(traceId); + }; + + private onTraceIdEnter = () => { + const traceId = normalizeTraceId(this.state.traceIdInput); + if (!traceId) return; + this.pickTrace(traceId); + }; + + private renderTraceIdBar() { + return ( +
+ {/* Label */} + + TraceId + + + {/* Input */} + this.setState({ traceIdInput: e.target.value })} + onPressEnter={this.onTraceIdEnter} + allowClear + style={{ + width: '36ch', // enough for 32-hex traceId (+ a bit) + fontFamily: 'monospace', + }} + /> + + {/* Action */} + +
+ ); + } + + + private renderTopBar() { + const { loadingServices, services } = this.props; + + return ( +
+ {/* Left: SearchForm fixed */} +
+ {!loadingServices && services ? : } +
+ +
+ + {/* Right: traceId direct open */} +
+ {this.renderTraceIdBar()} +
+
+ ); + } + + private renderBottom() { + const { sortedTracesXformer, traces, loadingTraces, errors } = this.props; + const { sortBy, viewMode, selectedTraceId } = this.state; + + if (viewMode === 'trace' && selectedTraceId) { + const TraceInline = ConnectedTraceInline as any; + return ( +
+ +
+ ); + } + + const traceResults = sortedTracesXformer(traces, sortBy); + + return ( + + ); + } + + render() { + return ( +
+ {this.renderTopBar()} + + {this.renderBottom()} +
+ ); + } +} + +/** ===== selectors / xformers (copied from SearchTracePage, minimal change) ===== */ + +const stateTraceXformer = memoizeOne((stateTrace: ReduxState['trace']) => { + const { traces: traceMap, search } = stateTrace; + const { query, results, state, error: traceError } = search; + + const loadingTraces = state === fetchedState.LOADING; + const traces = results.map(id => traceMap[id].data).filter((t): t is Trace => t !== undefined); + const rawTraces = (stateTrace as any).rawTraces || []; + const maxDuration = Math.max.apply( + null, + traces.map(tr => tr.duration) + ); + return { traces, rawTraces, maxDuration, traceError, loadingTraces, query }; +}); + +const sortedTracesXformer = memoizeOne((traces: Trace[], sortBy: string) => { + const traceResults = traces.slice(); + sortTraces(traceResults, sortBy); + return traceResults.map(t => t.asOtelTrace()); +}); + +const stateServicesXformer = memoizeOne((stateServices: ReduxState['services']) => { + const { loading: loadingServices, services: serviceList, operationsForService: opsBySvc, error: serviceError } = + stateServices; + + const selectedService = store.get?.('lastSearch')?.service; + if ( + selectedService && + serviceList && + serviceList.includes(selectedService) && + (!opsBySvc || !opsBySvc[selectedService] || opsBySvc[selectedService].length === 0) + ) { + return { loadingServices: true, services: serviceList, serviceError }; + } + + const services = + serviceList && + serviceList.map(name => ({ + name, + operations: (opsBySvc && opsBySvc[name]) || [], + })); + + return { loadingServices, services, serviceError }; +}); + +export function mapStateToProps(state: ReduxState): TStateProps { + const { embedded, router, services: stServices, config } = state; + + const query = getUrlState(router.location.search); + const { + query: queryOfResults, + traces, + rawTraces, + maxDuration, + traceError, + loadingTraces, + } = stateTraceXformer(state.trace); + + const traceIdFromUrl = getTraceIdFromSearch(router.location.search); + const searchPart = stripTraceKeys(query); + const hasSearchParams = Object.keys(searchPart).length > 0; + const isHomepage = !traceIdFromUrl && !hasSearchParams; + + const { loadingServices, services, serviceError } = stateServicesXformer(stServices); + + const errors: Array<{ message: string }> = []; + if (traceError && typeof traceError === 'object' && 'message' in traceError) { + errors.push({ message: (traceError as any).message }); + } + if (serviceError) { + if (typeof serviceError === 'string') errors.push({ message: serviceError }); + else if (typeof serviceError === 'object' && 'message' in serviceError) errors.push({ message: (serviceError as any).message }); + } + + if (isHomepage) { + return { + queryOfResults: null, + embedded, + loadingServices, + loadingTraces: false, + services: (services || null) as any, + traces: [], + traceResultsToDownload: [], + errors: null, + maxTraceDuration: 0, + sortedTracesXformer, + urlQueryParams: null, + isHomepage: true, + }; + } + + + return { + queryOfResults: (queryOfResults as any) || null, + embedded, + loadingServices, + loadingTraces, + services: (services || null) as any, + traces, + traceResultsToDownload: rawTraces, + errors: errors.length ? errors : null, + maxTraceDuration: maxDuration, + sortedTracesXformer, + urlQueryParams: Object.keys(query).length > 0 ? (query as any) : null, + isHomepage, + }; +} + +export function mapDispatchToProps(dispatch: Dispatch): TDispatchProps { + const { fetchServiceOperations, fetchServices, searchTraces, fetchTrace } = bindActionCreators( + jaegerApiActions as any, + dispatch + ); + return { + fetchServiceOperations, + fetchServices, + searchTraces, + fetchTrace, + }; +} + +const connector = connect(mapStateToProps, mapDispatchToProps); +export default withRouteProps(connector(TraceSearchPageImpl)); + +/** ===== connect TracePageImpl for inline render ===== */ +const ConnectedTraceInline = connect(traceMapState as any, traceMapDispatch as any)(TracePageImpl as any); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/scroll-page.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/scroll-page.ts new file mode 100644 index 0000000000..edc5ad695c --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/scroll-page.ts @@ -0,0 +1,41 @@ +import Tween from './Tween'; + +const DURATION_MS = 350; + +let lastTween: Tween | void; + +// TODO(joe): this util can be modified a bit to be generalized (e.g. take in +// an element as a parameter and use scrollTop instead of window.scrollTo) + +function _onTweenUpdate({ done, value }: { done: boolean; value: number }) { + window.scrollTo(window.scrollX, value); + if (done) { + lastTween = undefined; + } +} + +export function scrollBy(yDelta: number, appendToLast = false) { + const { scrollY } = window; + let targetFrom = scrollY; + if (appendToLast && lastTween) { + const currentDirection = lastTween.to < scrollY ? 'up' : 'down'; + const nextDirection = yDelta < 0 ? 'up' : 'down'; + if (currentDirection === nextDirection) { + targetFrom = lastTween.to; + } + } + const to = targetFrom + yDelta; + lastTween = new Tween({ to, duration: DURATION_MS, from: scrollY, onUpdate: _onTweenUpdate }); +} + +export function scrollTo(y: number) { + const { scrollY } = window; + lastTween = new Tween({ duration: DURATION_MS, from: scrollY, to: y, onUpdate: _onTweenUpdate }); +} + +export function cancel() { + if (lastTween) { + lastTween.cancel(); + lastTween = undefined; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/types.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/types.ts new file mode 100644 index 0000000000..22a19654e4 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/types.ts @@ -0,0 +1,51 @@ +import { TNil } from '../../types'; + +interface ITimeCursorUpdate { + cursor: number | TNil; +} + +interface ITimeReframeUpdate { + reframe: { + anchor: number; + shift: number; + }; +} + +interface ITimeShiftEndUpdate { + shiftEnd: number; +} + +interface ITimeShiftStartUpdate { + shiftStart: number; +} + +export type TUpdateViewRangeTimeFunction = (start: number, end: number, trackSrc?: string) => void; + +export type ViewRangeTimeUpdate = + | ITimeCursorUpdate + | ITimeReframeUpdate + | ITimeShiftEndUpdate + | ITimeShiftStartUpdate; + +export interface IViewRangeTime { + current: [number, number]; + cursor?: number | TNil; + reframe?: { + anchor: number; + shift: number; + }; + shiftEnd?: number; + shiftStart?: number; +} + +export interface IViewRange { + time: IViewRangeTime; +} + +export enum ETraceViewType { + TraceTimelineViewer = 'TraceTimelineViewer', + TraceGraph = 'TraceGraph', + TraceStatistics = 'TraceStatistics', + TraceSpansView = 'TraceSpansView', + TraceFlamegraph = 'TraceFlamegraph', +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/ReferenceLink.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/ReferenceLink.tsx new file mode 100644 index 0000000000..7b04f030c1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/ReferenceLink.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { ILink } from '../../../types/otel'; +import { getUrl } from '.'; + +type ReferenceLinkProps = { + link: ILink; + children: React.ReactNode; + className?: string; + focusSpan: (spanID: string) => void; + onClick?: () => void; +}; + +export default function ReferenceLink(props: ReferenceLinkProps) { + const { link, children, className, focusSpan, ...otherProps } = props; + delete otherProps.onClick; + + // link within the trace should have link.span defined + const isSameTrace = link.span !== undefined; + + if (isSameTrace) { + return ( + focusSpan(link.spanID)} className={className} {...otherProps}> + {children} + + ); + } + + return ( + + {children} + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/index.ts b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/index.ts new file mode 100644 index 0000000000..ae9011fcff --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/TraceSearchPage/url/index.ts @@ -0,0 +1,53 @@ +import queryString from 'query-string'; + +import prefixUrl from '../../../utils/prefix-url'; + +import { TNil } from '../../../types'; +import { TUrlState } from '../../SearchTracePage/url'; + +export const MAX_LENGTH = 7000; + +export const ROUTE_PATH = prefixUrl('/search-trace'); + +export function getUrl(id: string, uiFind?: string): string { + const traceUrl = prefixUrl(`/search-trace/${id}`); + if (!uiFind) return traceUrl; + + return `${traceUrl}?${queryString.stringify({ uiFind })}`; +} + +export function getSearchUrl(query?: TUrlState) { + const searchUrl = prefixUrl(`/search-trace`); + if (!query) return searchUrl; + + const { traceID, spanLinks, ...rest } = query; + let ids = traceID; + if (spanLinks && traceID) { + ids = (Array.isArray(traceID) ? traceID : [traceID]).filter((id: string) => !spanLinks[id]); + } + const stringifyArg = { + ...rest, + span: + spanLinks && + Object.keys(spanLinks).reduce((res: string[], trace: string) => { + return [...res, `${spanLinks[trace]}@${trace}`]; + }, []), + traceID: ids && ids.length ? ids : undefined, + }; + + const fullUrl = `${searchUrl}?${queryString.stringify(stringifyArg)}`; + if (fullUrl.length <= MAX_LENGTH) return fullUrl; + + const truncated = fullUrl.slice(0, MAX_LENGTH + 1); + if (truncated[MAX_LENGTH] === '&') return truncated.slice(0, -1); + + return truncated.slice(0, truncated.lastIndexOf('&')); +} + +export function getLocation(id: string, state: Record | TNil, uiFind?: string) { + return { + state, + pathname: getUrl(id), + search: uiFind && queryString.stringify({ uiFind }), + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.css new file mode 100644 index 0000000000..4fd6e6fca9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.css @@ -0,0 +1,32 @@ +.NodeContent--actionsItemIconWrapper { + flex: none; + height: 16px; + width: 16px; +} + +.NodeContent--actionsItemIconWrapper > * { + height: 16px; + position: absolute; + width: 16px; +} + +.NodeContent--actionsItemIconWrapper > * > .ant-checkbox { + vertical-align: unset; + top: unset; +} + +.NodeContent--actionsItemText { + font-size: 0.9em; + line-height: normal; + margin-left: 0.5em; +} + +.NodeContent--actionsItem { + align-items: center; + display: flex; + padding: 0.5em; +} + +.NodeContent--actionsItem:not(:hover) { + color: inherit; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.tsx new file mode 100644 index 0000000000..f7e2fad020 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ActionMenu/ActionsMenu.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; +import { Checkbox } from 'antd'; +import './ActionsMenu.css'; + +export interface IActionMenuItem { + id: string; + label: string; + icon: React.ReactNode; + href?: string; + onClick?: () => void; + isVisible?: boolean; + checkboxProps?: { + checked: boolean; + indeterminate?: boolean; + }; +} + +interface IActionsMenuProps { + items?: IActionMenuItem[]; + className?: string; + style?: React.CSSProperties; +} + +export const ActionsMenu: React.FC = ({ items = [], className, style }) => { + const handleKeyDown = (item: IActionMenuItem) => (event: React.KeyboardEvent) => { + if (event.key === 'Enter' || event.key === ' ') { + event.preventDefault(); + item.onClick?.(); + } + }; + + return ( +
+ {items.map(item => { + if (item.isVisible === false) return null; + + const content = ( + <> + + {item.checkboxProps ? ( + + ) : ( + item.icon + )} + + {item.label} + + ); + + if (item.href) { + return ( + + {content} + + ); + } + + return ( + + {content} + + ); + })} +
+ ); +}; + +export default ActionsMenu; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.css new file mode 100644 index 0000000000..3f7e69b4f4 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.css @@ -0,0 +1,4 @@ +.BreakableText { + display: inline-block; + white-space: pre; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.tsx new file mode 100644 index 0000000000..8969b1d483 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/BreakableText.tsx @@ -0,0 +1,30 @@ +import * as React from 'react'; + +import './BreakableText.css'; + +const WORD_RX = /\W*\w+\W*/g; + +type Props = { + text: string; + className?: string; + wordRegexp?: RegExp; +}; + +export default function BreakableText({ className = 'BreakableText', text, wordRegexp = WORD_RX }: Props) { + if (!text) { + return typeof text === 'string' ? text : null; + } + const spans = []; + wordRegexp.exec(''); + // if the given text has no words, set the first match to the entire text + let match: RegExpExecArray | string[] | null = wordRegexp.exec(text) || [text]; + while (match) { + spans.push( + + {match[0]} + + ); + match = wordRegexp.exec(text); + } + return spans; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ClickToCopy.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/ClickToCopy.tsx new file mode 100644 index 0000000000..83632c4328 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ClickToCopy.tsx @@ -0,0 +1,70 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Tooltip } from 'antd'; + +type Props = { + text: string; + className?: string; + children: React.ReactNode; +}; + +function copy(text: string) { + const textArea = document.createElement('textarea'); + textArea.value = text; + document.body.appendChild(textArea); + textArea.select(); + document.execCommand('copy'); + document.body.removeChild(textArea); +} + +function ClickToCopy({ text, className = '', children }: Props) { + const [isCopied, setIsCopied] = useState(false); + const [previousClick, setPreviousClick] = useState(0); + const timeoutRef = useRef(null); + + useEffect(() => { + if (isCopied) { + const checkDeadline = () => { + if (Date.now() >= previousClick + 1800) { + setIsCopied(false); + } else { + timeoutRef.current = setTimeout(checkDeadline, 100); + } + }; + timeoutRef.current = setTimeout(checkDeadline, 100); + } + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [isCopied, previousClick]); + + const whenClicked = () => { + copy(text); + setIsCopied(true); + setPreviousClick(Date.now()); + }; + + return ( + + { + if (e.key === 'Enter' || e.key === ' ') { + whenClicked(); + e.preventDefault(); + } + }} + role="button" + tabIndex={0} + aria-label={isCopied ? 'Copied to clipboard' : 'Copy to clipboard'} + > + {children} + + + ); +} + +export default ClickToCopy; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.css new file mode 100644 index 0000000000..9173c63e63 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.css @@ -0,0 +1,11 @@ +.CopyIcon, +.CopyIcon:hover { + background-color: transparent; + border: none; + color: inherit; + height: 100%; + overflow: hidden; + padding: 1px; + font-size: 13px; + margin-left: 5px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.tsx new file mode 100644 index 0000000000..51d0003852 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/CopyIcon.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { useState } from 'react'; + +import { Button, Tooltip } from 'antd'; +import { TooltipPlacement } from 'antd/es/tooltip/index'; +import cx from 'classnames'; +import copy from 'copy-to-clipboard'; + +import './CopyIcon.css'; +import { IoCopyOutline } from 'react-icons/io5'; + +type PropsType = { + className?: string; + copyText: string; + icon?: React.ReactNode; + placement?: TooltipPlacement; + tooltipTitle: string; + buttonText: string; +}; + +const CopyIcon: React.FC = ({ + className, + copyText, + icon = , + placement = 'top', + tooltipTitle, + buttonText, +}) => { + const [hasCopied, setHasCopied] = useState(false); + + const handleClick = () => { + setHasCopied(true); + copy(copyText); + }; + + const handleTooltipVisibilityChange = (visible: boolean) => { + if (!visible && hasCopied) { + setHasCopied(false); + } + }; + + return ( + + + + ); +}; + +export default CopyIcon; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailList.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailList.tsx new file mode 100644 index 0000000000..ece3396d4f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailList.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { List } from 'antd'; + +const { Item } = List; + +export default function DetailList({ details }: { details: string[] }) { + return ( + ( + + {s} + + )} + /> + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTable.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTable.tsx new file mode 100644 index 0000000000..892d9c4faa --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTable.tsx @@ -0,0 +1,163 @@ +import * as React from 'react'; +import { Table } from 'antd'; +import { IoFunnel, IoFunnelOutline } from 'react-icons/io5'; +import _isEmpty from 'lodash/isEmpty'; + +import ExamplesLink, { TExample } from '../ExamplesLink'; +import DetailTableDropdown from './DetailTableDropdown'; + +import { TColumnDef, TColumnDefs, TFilterDropdownProps, TRow, TStyledValue } from './types'; + +// exported for tests +export const _makeFilterDropdown = + (dataIndex: string, options: Set) => (props: TFilterDropdownProps) => { + return ; + }; + +// exported for tests +export const _onCell = (dataIndex: string) => (row: TRow) => { + const cellData = row[dataIndex]; + if (!cellData || typeof cellData !== 'object' || Array.isArray(cellData)) return {}; + const { styling } = cellData; + if (_isEmpty(styling)) return {}; + return { + style: styling, + }; +}; + +// exported for tests +export const _onFilter = (dataIndex: string) => (value: React.Key | boolean, row: TRow) => { + const data = row[dataIndex]; + if (typeof data === 'object' && !Array.isArray(data) && typeof data.value === 'string') { + return data.value === value; + } + return data === value; +}; + +// exported for tests +export const _renderCell = (cellData: undefined | string | TStyledValue) => { + if (!cellData || typeof cellData !== 'object') return cellData; + if (Array.isArray(cellData)) return null; + if (!cellData.linkTo) return cellData.value; + return ( + + {cellData.value} + + ); +}; + +// exported for tests +export const _sort = (dataIndex: string) => (a: TRow, b: TRow) => { + const aData = a[dataIndex]; + let aValue; + if (Array.isArray(aData)) aValue = aData.length; + else if (typeof aData === 'object' && typeof aData.value === 'string') aValue = aData.value; + else aValue = aData; + + const bData = b[dataIndex]; + let bValue; + if (Array.isArray(bData)) bValue = bData.length; + else if (typeof bData === 'object' && typeof bData.value === 'string') bValue = bData.value; + else bValue = bData; + + if (aValue < bValue) return -1; + return bValue < aValue ? 1 : 0; +}; + +// exported for tests +export const _makeColumns = ({ defs, rows }: { defs: TColumnDefs; rows: TRow[] }) => + defs.map((def: TColumnDef | string) => { + let dataIndex: string; + let key: string; + let sortable = true; + let style: React.CSSProperties | undefined; + let title: string; + if (typeof def === 'string') { + key = title = dataIndex = def; + } else { + key = title = dataIndex = def.key; + if (def.label) title = def.label; + if (def.styling) style = def.styling; + if (def.preventSort) sortable = false; + } + + const options = new Set(); + rows.forEach(row => { + const value = row[dataIndex]; + if (typeof value === 'string' && value) options.add(value); + else if (typeof value === 'object' && !Array.isArray(value) && typeof value.value === 'string') { + options.add(value.value); + } + }); + + return { + dataIndex, + key, + title, + filterDropdown: Boolean(options.size) && _makeFilterDropdown(dataIndex, options), + filterIcon: (filtered: boolean) => { + if (filtered) return ; + return ; + }, + onCell: _onCell(dataIndex), + onHeaderCell: () => ({ + style, + }), + onFilter: _onFilter(dataIndex), + render: _renderCell, + sorter: sortable && _sort(dataIndex), + }; + }); + +// exported for tests +export const _rowKey = (row: TRow) => + JSON.stringify( + row, + function replacer(key: string, value: TRow | undefined | string | number | TStyledValue | TExample[]) { + function isRow(v: typeof value): v is TRow { + return v === row; + } + if (isRow(value)) return value; + if (Array.isArray(value)) return JSON.stringify(value); + if (typeof value === 'object') { + if (typeof value.value === 'string') return JSON.stringify(value); + return value.value.key || 'Unknown'; + } + return value; + } + ); + +export default function DetailTable({ + columnDefs: _columnDefs, + details, +}: { + columnDefs?: TColumnDefs; + details: TRow[]; +}) { + const columnDefs: TColumnDefs = _columnDefs ? _columnDefs.slice() : []; + const knownColumns = new Set( + columnDefs.map(keyOrObj => { + if (typeof keyOrObj === 'string') return keyOrObj; + return keyOrObj.key; + }) + ); + details.forEach(row => { + Object.keys(row).forEach((col: string) => { + if (!knownColumns.has(col)) { + knownColumns.add(col); + columnDefs.push(col); + } + }); + }); + + return ( + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.css new file mode 100644 index 0000000000..8279a7e4cb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.css @@ -0,0 +1,49 @@ +.DetailTableDropdown--Footer { + background: white; + border: 1px solid #ccc; + display: flex; + justify-content: space-between; + padding: 0.3em; +} + +.DetailTableDropdown--Footer--CancelConfirm { + display: flex; +} + +.DetailTableDropdown--Btn { + align-items: center; + border-radius: 0; + color: #ddd; + display: flex; + padding: 0.3em 0.7em 0.3em 0.6em; +} + +.DetailTableDropdown--Btn ~ .DetailTableDropdown--Btn { + margin-left: 0.3em; +} + +.DetailTableDropdown--Btn > *:not(:first-child) { + margin-left: 0.3em; +} + +.DetailTableDropdown--Btn.Apply { + background: #4c21ce; +} + +.DetailTableDropdown--Btn.Cancel { + background: #007272; +} + +.DetailTableDropdown--Btn.Clear { + background: #ff2626; +} + +.DetailTableDropdown--Tooltip { + max-width: unset; + white-space: nowrap; +} + +.DetailTableDropdown--Tooltip--Body { + display: flex; + flex-direction: column; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.tsx new file mode 100644 index 0000000000..ecea756f40 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/DetailTableDropdown.tsx @@ -0,0 +1,119 @@ +import React, { Key, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Tooltip } from 'antd'; +import { IoTrash, IoBan, IoCheckmark } from 'react-icons/io5'; + +import FilteredList from '../FilteredList'; + +import { TFilterDropdownProps } from './types'; + +import './DetailTableDropdown.css'; + +type TProps = TFilterDropdownProps & { + options: Set; +}; + +const DetailTableDropdown: React.FC = props => { + const { clearFilters = () => {}, confirm, options, selectedKeys, setSelectedKeys } = props; + const confirmedSelectionRef = useRef(selectedKeys); + const [isCancelled, setIsCancelled] = useState(false); + const prevSelectedKeysRef = useRef([]); + + useEffect(() => { + const prevKeys = prevSelectedKeysRef.current; + if (prevKeys && selectedKeys.length === prevKeys.length) { + const prevKeysSet = new Set(prevKeys); + if (selectedKeys.every(key => prevKeysSet.has(key))) { + confirmedSelectionRef.current = selectedKeys; + } + } + prevSelectedKeysRef.current = selectedKeys; + + if (isCancelled) { + confirm(); + setIsCancelled(false); + } + }, [selectedKeys, isCancelled, confirm]); + + const cancel = useCallback(() => { + setSelectedKeys(confirmedSelectionRef.current); + setIsCancelled(true); + }, [setSelectedKeys]); + + const value = useMemo(() => { + const valueSet = new Set(); + selectedKeys.forEach(selected => { + if (typeof selected === 'string') valueSet.add(selected); + }); + return valueSet; + }, [selectedKeys]); + + const addValues = useCallback( + (values: string[]) => { + setSelectedKeys([...selectedKeys, ...values]); + }, + [selectedKeys, setSelectedKeys] + ); + + const removeValues = useCallback( + (values: string[]) => { + const remove = new Set(values); + setSelectedKeys(selectedKeys.filter(key => !remove.has(key))); + }, + [selectedKeys, setSelectedKeys] + ); + + const setValue = useCallback( + (v: string) => { + setSelectedKeys([v]); + }, + [setSelectedKeys] + ); + + return ( +
+ +
+ + + +
+ + + + + Apply changes to this column's filter + Same effect as clicking outside the dropdown +
+ } + > + + +
+
+ + ); +}; + +export default React.memo(DetailTableDropdown); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.css new file mode 100644 index 0000000000..0b8a7e9399 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.css @@ -0,0 +1,57 @@ +.DetailsCard { + display: flex; + flex-direction: column; + padding: 0.5em; +} + +.DetailsCard--ButtonHeaderWrapper { + display: flex; +} + +.DetailsCard--Collapser { + border: none; + background: transparent; + cursor: pointer; + flex: 0; + font-size: 1.5rem; + transition: transform 0.25s ease-out; +} + +.DetailsCard--Collapser:focus { + outline: none; +} + +.DetailsCard--Collapser.is-collapsed { + transform: rotate(-90deg); +} + +.DetailsCard--HeaderWrapper { + flex-grow: 1; +} + +.DetailsCard--Header { + border-bottom: solid 1px #ddd; + box-shadow: 0px 5px 5px -5px rgba(0, 0, 0, 0.3); + font-size: 1.5em; + padding: 0.1em 0.3em; +} + +.DetailsCard--Description { + margin-bottom: 0; + max-width: 90%; +} + +.DetailsCard--DetailsWrapper { + overflow: scroll; + margin-top: 0.5em; + transition: max-height 0.25s ease-out; + max-height: 60vh; +} + +.DetailsCard--DetailsWrapper.is-collapsed { + max-height: 0px; +} + +.DetailsCard--DetailsWrapper th { + min-width: 65px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.tsx new file mode 100644 index 0000000000..4bce0ed003 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/index.tsx @@ -0,0 +1,62 @@ +import * as React from 'react'; +import cx from 'classnames'; +import { IoChevronDown } from 'react-icons/io5'; + +import { TColumnDefs, TDetails, TRow } from './types'; +import DetailList from './DetailList'; +import DetailTable from './DetailTable'; + +import './index.css'; + +type TProps = { + className?: string; + collapsible?: boolean; + columnDefs?: TColumnDefs; + description?: string; + details: TDetails; + header: string; +}; + +function isList(arr: string[] | TRow[]): arr is string[] { + return typeof arr[0] === 'string'; +} + +function DetailsCard({ className, collapsible = false, description, header, columnDefs, details }: TProps) { + const [isCollapsed, setIsCollapsed] = React.useState(Boolean(collapsible)); + + const renderDetails = () => { + if (Array.isArray(details)) { + if (details.length === 0) return null; + if (isList(details)) return ; + return ; + } + return {details}; + }; + + const toggleCollapsed = React.useCallback(() => setIsCollapsed(!isCollapsed), [isCollapsed]); + + return ( +
+
+ {collapsible && ( + + )} +
+ {header} + {description &&

{description}

} +
+
+
+ {renderDetails()} +
+
+ ); +} + +export default DetailsCard; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/types.ts b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/types.ts new file mode 100644 index 0000000000..eb063c3827 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/DetailsCard/types.ts @@ -0,0 +1,28 @@ +import type React from 'react'; +import { TExample } from '../ExamplesLink'; + +export type TStyledValue = { + linkTo?: string; + styling?: React.CSSProperties; + value: string | React.ReactElement; +}; + +export type TColumnDef = { + key: string; + label?: string; + preventSort?: boolean; + styling?: React.CSSProperties; +}; + +export type TColumnDefs = (string | TColumnDef)[]; + +export type TRow = Record; + +export type TDetails = string | string[] | TRow[]; + +export type TFilterDropdownProps = { + clearFilters?: () => void; + confirm: () => void; + selectedKeys: React.Key[]; + setSelectedKeys: (selectedKeys: React.Key[]) => void; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.css new file mode 100644 index 0000000000..68746412ac --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.css @@ -0,0 +1,23 @@ +.EmphasizedNode { + stroke: var(--trace-emphasis-highlight); +} + +.EmphasizedNode.is-non-scaling { + stroke-width: 10; +} + +.EmphasizedNode.is-scaling { + stroke-width: 34; +} + +.EmphasizedNode--contrast { + stroke: var(--border-component-subtle); +} + +.EmphasizedNode--contrast.is-non-scaling { + stroke-width: 12; +} + +.EmphasizedNode--contrast.is-scaling { + stroke-width: 36; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.tsx new file mode 100644 index 0000000000..9c0d74aa77 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/EmphasizedNode.tsx @@ -0,0 +1,31 @@ +import * as React from 'react'; + +import './EmphasizedNode.css'; + +type EmphasizedNodeProps = { + height: number; + width: number; +}; + +function EmphasizedNode({ height, width }: EmphasizedNodeProps) { + return ( + + + + + + + ); +} + +export default EmphasizedNode; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.css new file mode 100644 index 0000000000..b47d870ae1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.css @@ -0,0 +1,48 @@ +.ErrorMessage { + position: relative; + word-break: break-word; + word-wrap: break-word; +} + +.ErrorMessage--msg { + color: var(--feedback-error); + margin: 0; + padding-bottom: 1rem; +} + +.ErrorMessage--details { + background: var(--surface-secondary); + border: 1px solid var(--border-default); + overflow: auto; +} + +.ErrorMessage--detailsTable { + min-width: 100%; +} + +.ErrorMessage--detailItem:nth-child(2n) > .ErrorMessage--attr, +.ErrorMessage--detailItem:nth-child(2n) > .ErrorMessage--value { + background-color: var(--surface-tertiary); + border-bottom: 1px solid var(--border-default); + border-top: 1px solid var(--border-default); +} + +/* dont have extra borders on the bottom row */ +.ErrorMessage--detailItem:last-child > .ErrorMessage--attr, +.ErrorMessage--detailItem:last-child > .ErrorMessage--value { + border-bottom: none; +} + +.ErrorMessage--attr { + color: var(--text-muted); + padding: 0.5em; + vertical-align: top; + white-space: nowrap; +} + +.ErrorMessage--value { + color: var(--text-primary); + font-family: monospace; + padding: 0.5em; + white-space: pre; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.tsx new file mode 100644 index 0000000000..49b1776e90 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ErrorMessage.tsx @@ -0,0 +1,113 @@ +import * as React from 'react'; + +import { ApiError } from '../../types/api-error'; + +import './ErrorMessage.css'; + +type ErrorMessageProps = { + className?: string; + detailClassName?: string; + messageClassName?: string; + error: ApiError; +}; + +type SubPartProps = { + className?: string; + error: ApiError; + wrap?: boolean; + wrapperClassName?: string; +}; + +type ErrorAttrProps = { + name: string; + value: string | number; +}; + +export const MAX_DETAIL_LENGTH = 1024; + +function ErrorAttr({ name, value }: ErrorAttrProps) { + return ( +
+ + + + ); +} + +export function Message({ className, error, wrap = false, wrapperClassName }: SubPartProps) { + const cssClass = `ErrorMessage--msg ${className || ''}`; + + const msg = + typeof error === 'string' ? ( +

{error}

+ ) : ( +

{error.message}

+ ); + + if (wrap) { + return
{msg}
; + } + + return msg; +} + +export function Details({ className, error, wrap = false, wrapperClassName }: SubPartProps) { + if (typeof error === 'string') { + return null; + } + + const { httpStatus, httpStatusText, httpUrl, httpQuery, httpBody } = error; + const bodyExcerpt = + httpBody && httpBody.length > MAX_DETAIL_LENGTH + ? `${httpBody.slice(0, MAX_DETAIL_LENGTH - 3).trim()}...` + : httpBody; + + const details = ( +
+
{name}{value}
+ + {httpStatus && } + {httpStatusText && } + {httpUrl && } + {httpQuery && } + {bodyExcerpt && } + +
+
+ ); + + if (wrap) { + return ( +
+ {details} +
+ ); + } + + return details; +} + +export default function ErrorMessage({ + className, + detailClassName, + error, + messageClassName, +}: ErrorMessageProps) { + if (!error) { + return null; + } + + if (typeof error === 'string') { + return ; + } + + return ( +
+ +
+
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/ExternalLinks.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/ExternalLinks.tsx new file mode 100644 index 0000000000..39cc733fc7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/ExternalLinks.tsx @@ -0,0 +1,57 @@ +import { Dropdown } from 'antd'; +import * as React from 'react'; +import { Hyperlink } from '../../types/hyperlink'; +import NewWindowIcon from './NewWindowIcon'; + +type ExternalLinksProps = { + links: Hyperlink[]; +}; + +const LinkValue = (props: { + href: string; + title: string; + children?: React.ReactNode; + className?: string; +}) => ( + + {props.children} + +); + +// export for testing +export const linkValueList = (links: Hyperlink[]) => { + const dropdownItems = links.map(({ text, url }, index) => ({ + label: ( + + {text} + + ), + key: `${url}-${index}`, + })); + + return dropdownItems; +}; + +// ExternalLinks are displayed as a menu at the trace level. +// Example: https://github.com/jaegertracing/jaeger-ui/assets/94157520/7f0d84bc-c2fb-488c-9e50-1ec9484ea1e6 +export default function ExternalLinks(props: ExternalLinksProps) { + const { links } = props; + + if (links.length === 1) { + return ; + } + + return ( + + + + + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.css new file mode 100644 index 0000000000..b402fa76f5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.css @@ -0,0 +1,50 @@ +.FilteredList--ListItem { + align-items: center; + cursor: pointer; + display: flex; + padding: 0.5em 5em 0.5em 0.5em; + white-space: nowrap; + min-width: 100%; +} + +.FilteredList--ListItem.is-striped { + background: linear-gradient(to right, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0)); +} + +.FilteredList--ListItem.is-striped:hover, +.FilteredList--ListItem:hover { + background: linear-gradient( + to right, + rgba(135, 232, 222, 0.1), + rgba(135, 232, 222, 0.2) 70%, + rgba(135, 232, 222, 0) + ); + color: var(--text-primary); +} + +.FilteredList--ListItem.is-selected { + background: linear-gradient(to right, #87e8de, #87e8deaa 70%, #87e8de00); + color: var(--text-primary); +} + +.FilteredList--ListItem.is-striped.is-focused, +.FilteredList--ListItem.is-focused { + background: linear-gradient( + to right, + rgba(135, 232, 222, 0.2), + rgba(17, 153, 153, 0.2) 70%, + rgba(17, 153, 153, 0) + ); + color: var(--text-primary); +} + +.FilteredList--ListItem mark { + background: none; + color: #eb2f96; + font-weight: 500; + padding: 0; +} + +.FilteredList--ListItem--Checkbox { + margin-right: 0.5em; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.tsx new file mode 100644 index 0000000000..fffda9729b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/ListItem.tsx @@ -0,0 +1,72 @@ +import React, { useCallback, useMemo } from 'react'; +import { Checkbox } from 'antd'; +import cx from 'classnames'; + +import highlightMatches from './highlightMatches'; + +import './ListItem.css'; + +interface IListItemProps { + index: number; + style?: React.CSSProperties; + data: { + addValues?: (values: string[]) => void; + focusedIndex: number | null; + highlightQuery: string; + multi?: boolean; + options: string[]; + removeValues?: (values: string[]) => void; + selectedValue: Set | string | null; + setValue: (value: string) => void; + }; +} + +const ListItem: React.FC = React.memo(props => { + const { data, index, style: styleOrig } = props; + const { addValues, focusedIndex, highlightQuery, multi, options, removeValues, selectedValue, setValue } = + data; + + const value = options[index]; + + const isSelected = useMemo(() => { + if (typeof selectedValue === 'string' || !selectedValue) { + return value === selectedValue; + } + return selectedValue.has(value); + }, [value, selectedValue]); + + const onClicked = useCallback(() => { + if (multi && addValues && removeValues) { + if (isSelected) { + removeValues([value]); + } else { + addValues([value]); + } + } else { + setValue(value); + } + }, [multi, addValues, removeValues, isSelected, value, setValue]); + + const { width: _, ...style } = styleOrig || {}; + + const cls = cx('FilteredList--ListItem', { + 'is-focused': index === focusedIndex, + 'is-selected': isSelected, + 'is-striped': index % 2, + }); + + return ( +
+ {multi && } + {highlightMatches(highlightQuery, value)} +
+ ); +}); + +export default ListItem; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/highlightMatches.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/highlightMatches.tsx new file mode 100644 index 0000000000..f9527cd897 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/highlightMatches.tsx @@ -0,0 +1,127 @@ +import * as React from 'react'; +import memoize from 'lru-memoize'; + +type TIsHighlightGroupFn = (index: number) => boolean; + +type THighlightMatchFn = (match: RegExpMatchArray) => React.ReactNode; + +function toHighlights(isHighlightGroup: TIsHighlightGroupFn, match: RegExpMatchArray) { + const texts = match + .map((tx: string, i: number) => { + if (i === 0 || !tx) { + // the first group is the full match, not a capturing group + return null; + } + if (isHighlightGroup(i)) { + return {tx}; + } + return tx; + }) + .filter(Boolean); + return {texts}; +} + +// In the matches from the regular expressions created in `MatchHighlighter`, +// text that should be highlighted is interleaved with text that shouldn't be +// highlighted. E.g. alternating capturing groups should be highlighted, +// regardless of how many capturing groups there are in the match. Therefore, we +// need to establish if the first capturing group (i.e. match[1]) should be +// highlighted or if it should start at match[2]. Then, we alternate, from there. +// This results in either highlighting even groups (starting at match[2]) or odd +// groups (starting at match[1]). However, we don't actually need to highlight +// odd groups, but just the first group (match[1]), so we can simplify that test. +const highlightEvenGroups = toHighlights.bind(null, (index: number) => index % 2 === 0); +const highlightFirstGroup = toHighlights.bind(null, (index: number) => index === 1); + +const ANY_LAZY = '(.*?)'; +const ANY_GREEDY = '(.*)'; + +function wordBreak(lowerLetter: string) { + return `(\\b${lowerLetter}|${lowerLetter.toUpperCase()})`; +} + +function letterClass(lowerLetter: string) { + return `[${lowerLetter}${lowerLetter.toUpperCase()}]`; +} + +/** + * Create regular expressions used for highlighting matches between the query + * and the text. This is derived from the match-sorter ranking system description: + * https://github.com/kentcdodds/match-sorter#this-solution + * + * Several regular expressions are created from the filterText. These roughly + * align with the ranking system described in the link. They are used to divide + * input text into emphasized and non-emphasized sections. + */ +class MatchHighlighter { + public static make(query: string) { + return new MatchHighlighter(query); + } + + private readonly matchers: [RegExp, THighlightMatchFn][]; + + constructor(readonly query: string) { + // istanbul ignore next + if (!query) { + this.matchers = []; + return; + } + const tx = query.toLowerCase(); + const initialCap = `${tx[0].toUpperCase()}${tx.slice(1)}`; + const letters = tx.split(''); + const startsWith = `^(${tx})`; + const wordStartsWith = `(\\b${letters.map(letterClass).join('')}|${initialCap})`; + const acronymLetters = letters.map(letter => wordBreak(letter)).join(ANY_LAZY); + const constains = `(${tx})`; + const anyLetters = letters.map(letter => `(${letter})`).join(ANY_LAZY); + + // given the query "eg", the following regular expressions will be created: + this.matchers = [ + // case-insensitive match at start of text, would match "ego" but not "lego" + // /^(eg)(.*)/i + [new RegExp(`${startsWith}${ANY_GREEDY}`, 'i'), highlightFirstGroup], + + // case-insensitive match at start of word, with boundaries in camelCase counting as words + // would match "theEgg" and "the-egg" + // /(.*?)(\b[eE][gG]|Eg)(.*)/ + [new RegExp(`${ANY_LAZY}${wordStartsWith}${ANY_GREEDY}`), highlightEvenGroups], + + // match an acronym, e.g. "easy-going" and "easyGoing" would both match "eg" + // /(.*?)(\be|E)(.*?)(\bg|G)(.*)/ + [new RegExp(`${ANY_LAZY}${acronymLetters}${ANY_GREEDY}`), highlightEvenGroups], + + // case insensitive contains + // /(.*?)(eg)(.*)/i + [new RegExp(`${ANY_LAZY}${constains}${ANY_GREEDY}`, 'i'), highlightEvenGroups], + + // contains each letter, in sequence + // /(.*?)(e)(.*?)(g)(.*)/i + [new RegExp(`${ANY_LAZY}${anyLetters}${ANY_GREEDY}`, 'i'), highlightEvenGroups], + ]; + } + + highlightMatches(text: string) { + if (!text) { + return text; + } + for (let i = 0; i < this.matchers.length; i++) { + const [matcher, highlightMatch] = this.matchers[i]; + const match = text.match(matcher); + if (match) { + return highlightMatch(match); + } + } + return text; + } +} + +const getHighlighter = memoize(30)(MatchHighlighter.make); + +function highlightMatchesImpl(query: string, text: string) { + if (!query) { + return text; + } + return getHighlighter(query).highlightMatches(text); +} + +export default memoize(200)(highlightMatchesImpl); diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.css new file mode 100644 index 0000000000..59d012a0fe --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.css @@ -0,0 +1,49 @@ +.FilteredList--list { + background: var(--surface-tertiary); +} + +.FilteredList--filterCheckbox { + margin: 0 0.5em; +} + +.FilteredList--filterWrapper { + align-items: center; + background: var(--surface-primary); + box-shadow: var(--shadow-sm); + display: flex; + position: relative; + z-index: 10; +} + +.FilteredList--inputWrapper { + align-items: center; + background: var(--surface-primary); + box-shadow: var(--shadow-sm); + display: flex; + flex-grow: 1; + position: relative; + z-index: 10; +} + +.FilteredList--filterIcon { + font-size: 1.3em; + margin: 0 0.25em 0 0.65em; + position: absolute; +} + +.FilteredList--filterIcon.isMulti { + margin-left: 2em; +} + +.FilteredList--filterInput { + border: none; + flex: 1; + height: auto; + padding: 0.5em 0.3em 0.5em 2.5em; +} + +.FilteredList--filterInput:focus { + box-shadow: inset 0px 0px 3px 1px #6aa; + outline: 1px solid #199; + outline-offset: -1px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.tsx new file mode 100644 index 0000000000..e7c96cc899 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/FilteredList/index.tsx @@ -0,0 +1,255 @@ +import * as React from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState, forwardRef, useImperativeHandle } from 'react'; +import { Checkbox, Tooltip } from 'antd'; +import _debounce from 'lodash/debounce'; +import { matchSorter } from 'match-sorter'; +import { IoSearch } from 'react-icons/io5'; +import { useVirtualizer } from '@tanstack/react-virtual'; +import { KeyboardKey as EKey } from '../../../constants/keyboard-keys'; + +import ListItem from './ListItem'; + +import './index.css'; + +const ITEM_HEIGHT = 35; +const MAX_HEIGHT = 375; + +type TProps = { + addValues?: (values: string[]) => void; + cancel?: () => void; + multi?: boolean; + options: string[]; + removeValues?: (values: string[]) => void; + setValue: (value: string) => void; + value: Set | string | null; +}; + +export interface IFilteredListRef { + focusInput: () => void; +} + +const FilteredList = forwardRef( + ({ addValues, cancel, multi, options, removeValues, setValue, value }, ref) => { + const inputRef = useRef(null); + const wrapperRef = useRef(null); + const listRef = useRef(null); + + const [filterText, setFilterText] = useState(''); + const [focusedIndex, setFocusedIndex] = useState(null); + + useImperativeHandle(ref, () => ({ + focusInput: () => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, + })); + + const hasMountedRef = useRef(false); + useEffect(() => { + if (hasMountedRef.current && inputRef.current) { + inputRef.current.focus(); + } + hasMountedRef.current = true; + }); + + const filteredOptions = useMemo(() => { + return filterText ? matchSorter(options, filterText) : options; + }, [options, filterText]); + + const virtualizer = useVirtualizer({ + count: filteredOptions.length, + getScrollElement: () => listRef.current, + estimateSize: () => ITEM_HEIGHT, + overscan: 25, + }); + + const visibleItems = virtualizer.getVirtualItems(); + const [visibleStartIndex, visibleStopIndex] = useMemo(() => { + if (visibleItems.length === 0) return [0, 0]; + return [visibleItems[0].index, visibleItems[visibleItems.length - 1].index]; + }, [visibleItems]); + + const getFilteredCheckbox = useCallback( + (filtered: string[]) => { + if (!addValues || !removeValues) return null; + + const valueSet = typeof value === 'string' || !value ? new Set([value]) : value; + let checkedCount = 0; + let indeterminate = false; + for (let i = 0; i < filtered.length; i++) { + const match = valueSet.has(filtered[i]); + if (match) checkedCount++; + if (checkedCount && checkedCount <= i) { + indeterminate = true; + break; + } + } + const checked = Boolean(checkedCount) && checkedCount === filtered.length; + const title = `Click to ${checked ? 'unselect' : 'select'} all ${ + filtered.length < options.length ? 'filtered ' : '' + }options`; + + return ( + + { + if (newCheckedState) addValues(filtered.filter(f => !valueSet.has(f))); + else removeValues(filtered); + }} + indeterminate={indeterminate} + /> + + ); + }, + [addValues, removeValues, value, options.length] + ); + + const handleSetValue = useCallback( + (newValue: string) => { + setValue(newValue); + setFilterText(''); + setFocusedIndex(null); + }, + [setValue] + ); + + const onKeyDown = useCallback( + (event: React.KeyboardEvent) => { + switch (event.key) { + case EKey.Escape: { + setFilterText(''); + setFocusedIndex(null); + if (cancel) cancel(); + break; + } + case EKey.ArrowUp: + case EKey.ArrowDown: { + let newFocusedIndex: number | null; + if (focusedIndex == null) { + newFocusedIndex = event.key === EKey.ArrowDown ? visibleStartIndex : visibleStopIndex; + setFocusedIndex(newFocusedIndex); + } else { + const offset = event.key === EKey.ArrowDown ? 1 : -1; + const i = focusedIndex + offset; + newFocusedIndex = i > -1 ? i % filteredOptions.length : filteredOptions.length + i; + setFocusedIndex(newFocusedIndex); + } + if ( + newFocusedIndex !== null && + (newFocusedIndex < visibleStartIndex + 1 || newFocusedIndex > visibleStopIndex - 1) + ) { + virtualizer.scrollToIndex(newFocusedIndex); + } + break; + } + case EKey.Enter: { + if (focusedIndex !== null) handleSetValue(filteredOptions[focusedIndex]); + else if (filteredOptions.length === 1) handleSetValue(filteredOptions[0]); + break; + } + default: // no-op + } + }, + [ + focusedIndex, + visibleStartIndex, + visibleStopIndex, + filteredOptions, + cancel, + handleSetValue, + virtualizer, + ] + ); + + const onListScrolled = useMemo( + () => + _debounce(() => { + setFocusedIndex(null); + }, 80), + [] + ); + + const onFilterChanged = useCallback((event: React.ChangeEvent) => { + setFilterText(event.target.value); + setFocusedIndex(null); + }, []); + + const filteredCheckbox = multi && getFilteredCheckbox(filteredOptions); + const data = { + addValues, + focusedIndex, + highlightQuery: filterText, + multi, + options: filteredOptions, + removeValues, + selectedValue: value, + setValue: handleSetValue, + }; + return ( +
+
+ {filteredCheckbox} + +
+
+
+ {visibleItems.map(virtualItem => ( +
+ +
+ ))} +
+
+
+ ); + } +); + +FilteredList.displayName = 'FilteredList'; +export default FilteredList; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.css new file mode 100644 index 0000000000..bb364a019a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.css @@ -0,0 +1,15 @@ +.LabeledList { + list-style: none; + margin: 0; + padding: 0; +} + +.LabeledList--item { + display: inline-block; + margin-right: 0.5rem; +} + +.LabeledList--label { + color: var(--text-muted); + margin-right: 0.25rem; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.tsx new file mode 100644 index 0000000000..f4138f6c10 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/LabeledList.tsx @@ -0,0 +1,34 @@ +import * as React from 'react'; +import { Divider } from 'antd'; + +import './LabeledList.css'; + +type LabeledListProps = { + className?: string; + dividerClassName?: string; + items: { key: string; label: React.ReactNode; value: React.ReactNode }[]; +}; + +export default function LabeledList(props: LabeledListProps) { + const { className, dividerClassName, items } = props; + return ( +
    + {items.map(({ key, label, value }, i) => { + const divider = i < items.length - 1 && ( +
  • + +
  • + ); + return ( + +
  • + {label} + {value} +
  • + {divider} +
    + ); + })} +
+ ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.css new file mode 100644 index 0000000000..84a2e5e092 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.css @@ -0,0 +1,45 @@ +@keyframes LoadingIndicator--colorAnim { + from { + color: var(--interactive-primary-light); + } + to { + color: var(--interactive-primary); + } +} + +@keyframes LoadingIndicator--spin { + 100% { + transform: rotate(360deg); + } +} + +.LoadingIndicator { + animation: + LoadingIndicator--colorAnim 1s infinite alternate, + LoadingIndicator--spin 1.2s infinite linear; + /* outline / stroke the loading indicator */ + text-shadow: + -0.5px 0 var(--interactive-primary-opaque), + 0 0.5px var(--interactive-primary-opaque), + 0.5px 0 var(--interactive-primary-opaque), + 0 -0.5px var(--interactive-primary-opaque); + + width: 42px; + height: 42px; +} + +.LoadingIndicator.is-centered { + display: block; + margin-left: auto; + margin-right: auto; +} + +.LoadingIndicator.is-vcentered { + display: block; + margin: auto; +} + +.LoadingIndicator.is-small { + width: 32px; + height: 32px; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.tsx new file mode 100644 index 0000000000..ad258e6d2d --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/LoadingIndicator.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { LuLoaderCircle } from 'react-icons/lu'; + +import './LoadingIndicator.css'; + +export default function LoadingIndicator({ + centered = false, + vcentered, + className = '', + small = false, + style, + ...rest +}: { + centered?: boolean; + vcentered?: boolean; + className?: string; + small?: boolean; + style?: React.CSSProperties; +}) { + const cls = ` + LoadingIndicator + ${centered ? 'is-centered' : ''} + ${vcentered ? 'is-vcentered' : ''} + ${small ? 'is-small' : ''} + ${className} + `; + + return ; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.css new file mode 100644 index 0000000000..59e01d42f7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.css @@ -0,0 +1,8 @@ +.NewWindowIcon { + font-size: 1em; + margin-bottom: -3px; +} + +.NewWindowIcon.is-large { + font-size: 1.5em; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.tsx new file mode 100644 index 0000000000..6c96dd392b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/NewWindowIcon.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import cx from 'classnames'; +import { IoOpenOutline } from 'react-icons/io5'; + +import './NewWindowIcon.css'; + +type Props = { + isLarge?: boolean; +}; + +export default function NewWindowIcon({ isLarge = false, ...rest }: Props) { + const cls = cx('NewWindowIcon', { 'is-large': isLarge }); + return ; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/SearchableSelect.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/SearchableSelect.tsx new file mode 100644 index 0000000000..c92ebfa03b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/SearchableSelect.tsx @@ -0,0 +1,61 @@ +import React, { FunctionComponent } from 'react'; +import { Select, SelectProps } from 'antd'; +import { DefaultOptionType } from 'antd/es/select'; +import { matchSorter } from 'match-sorter'; + +/** + * Case-insensitive substring filter for Select options. + */ +export const filterOptionsByLabel = (input: string, option?: DefaultOptionType) => { + return (option?.children?.toString() ?? '').toLowerCase().includes(input.toLowerCase()); +}; + +/** + * Fuzzy match filter using match-sorter library for more forgiving search. + * Note: This creates a single-element array for each option during filtering, + * which is acceptable for typical use cases. For very large lists, consider + * using Ant Design's filterSort prop with a custom sorting function. + */ +export const filterOptionsFuzzy = (input: string, option?: DefaultOptionType) => { + if (!input) return true; + const label = option?.children?.toString() ?? ''; + return matchSorter([label], input).length > 0; +}; + +export type SearchableSelectProps = SelectProps & { + /** + * Enable fuzzy matching instead of simple substring matching. + * Uses match-sorter library for more forgiving search. + */ + fuzzy?: boolean; +}; + +/** + * SearchableSelect is a wrapper around Ant Design's Select component + * that adds search/filter functionality. + * + * Features: + * - Case-insensitive label matching (default) + * - Optional fuzzy matching via `fuzzy` prop (uses match-sorter) + * - Supports virtualization via Ant Design's `virtual` prop for large lists + * - All standard Ant Design Select props are supported + * + * Use this component for: + * - Standard form dropdowns that need search/filter capability + * - Large lists that need virtualization (pass `virtual` prop) + * - When fuzzy matching is preferred (pass `fuzzy` prop) + * + * @example + * // Basic usage + * + * + * @example + * // With fuzzy matching and virtualization + * + */ +const SearchableSelect: FunctionComponent = ({ fuzzy, ...props }) => { + const filterOption = fuzzy ? filterOptionsFuzzy : filterOptionsByLabel; + return + ); +}); + +UnconnectedUiFindInput.displayName = 'UnconnectedUiFindInput'; + +export function extractUiFindFromState(state: ReduxState): TExtractUiFindFromStateReturn { + const { uiFind: uiFindFromUrl } = parseQuery(state.router.location.search); + const uiFind = Array.isArray(uiFindFromUrl) ? uiFindFromUrl.join(' ') : uiFindFromUrl; + return { uiFind }; +} + +export default connect(extractUiFindFromState)(UnconnectedUiFindInput) as any; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.css new file mode 100644 index 0000000000..2f8de8f354 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.css @@ -0,0 +1,93 @@ +.VerticalResizer { + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.VerticalResizer.is-flipped { + transform: scaleX(-1); +} + +.VerticalResizer--wrapper { + bottom: 0; + position: absolute; + top: 0; +} + +.VerticalResizer--dragger { + border-left: 2px solid transparent; + cursor: col-resize; + height: calc(100vh - 430px); + margin-left: -1px; + position: absolute; + top: 0; + width: 1px; +} + +.VerticalResizer--dragger:hover { + border-left: 2px solid var(--border-strongest); +} + +.VerticalResizer.isDraggingLeft > .VerticalResizer--dragger, +.VerticalResizer.isDraggingRight > .VerticalResizer--dragger { + background: var(--resizer-drag-bg); + width: unset; +} + +.VerticalResizer.isDraggingLeft > .VerticalResizer--dragger { + border-left: 2px solid var(--interactive-primary); + border-right: 1px solid var(--text-muted); +} + +.VerticalResizer.isDraggingRight > .VerticalResizer--dragger { + border-left: 1px solid var(--text-muted); + border-right: 2px solid var(--interactive-primary); +} + +.VerticalResizer--dragger::before { + position: absolute; + top: 0; + bottom: 0; + left: -8px; + right: 0; + content: ' '; +} + +.VerticalResizer.isDraggingLeft > .VerticalResizer--dragger::before, +.VerticalResizer.isDraggingRight > .VerticalResizer--dragger::before { + left: -2000px; + right: -2000px; +} + +.VerticalResizer--gripIcon { + position: absolute; + top: 0; + bottom: 0; +} + +.VerticalResizer--gripIcon::before, +.VerticalResizer--gripIcon::after { + border-right: 1px solid var(--border-strong); + content: ' '; + height: 9px; + position: absolute; + right: 9px; + top: 25px; +} + +.VerticalResizer--gripIcon::after { + right: 5px; +} + +.VerticalResizer:hover .VerticalResizer--gripIcon::before, +.VerticalResizer:hover .VerticalResizer--gripIcon::after { + border-right-color: var(--border-strongest); +} + +.VerticalResizer.isDraggingLeft > .VerticalResizer--gripIcon::before, +.VerticalResizer.isDraggingRight > .VerticalResizer--gripIcon::before, +.VerticalResizer.isDraggingLeft > .VerticalResizer--gripIcon::after, +.VerticalResizer.isDraggingRight > .VerticalResizer--gripIcon::after { + border-right: 1px solid rgba(var(--interactive-primary-rgb), 0.5); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.tsx b/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.tsx new file mode 100644 index 0000000000..117f53ba32 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/VerticalResizer.tsx @@ -0,0 +1,123 @@ +import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react'; +import cx from 'classnames'; + +import { TNil } from '../../types'; +import DraggableManager, { DraggableBounds, DraggingUpdate } from '../../utils/DraggableManager'; + +import './VerticalResizer.css'; + +type VerticalResizerProps = { + max: number; + min: number; + onChange: (newSize: number) => void; + position: number; + rightSide?: boolean; +}; + +const VerticalResizer: React.FC = ({ max, min, onChange, position, rightSide }) => { + const [dragPosition, setDragPosition] = useState(null); + const rootElmRef = useRef(null); + + const getDraggingBounds = useCallback((): DraggableBounds => { + if (!rootElmRef.current) { + return { + clientXLeft: 0, + width: 0, + minValue: 0, + maxValue: 0, + }; + } + const { left: clientXLeft, width } = rootElmRef.current.getBoundingClientRect(); + + let adjustedMin = min; + let adjustedMax = max; + + if (rightSide) { + [adjustedMin, adjustedMax] = [1 - max, 1 - min]; + } + + return { + clientXLeft, + width, + minValue: adjustedMin, + maxValue: adjustedMax, + }; + }, [min, max, rightSide]); + + const handleDragUpdate = useCallback( + ({ value }: DraggingUpdate) => { + const newDragPosition = rightSide ? 1 - value : value; + setDragPosition(newDragPosition); + }, + [rightSide] + ); + + const handleDragEnd = useCallback( + ({ manager, value }: DraggingUpdate) => { + manager.resetBounds(); + setDragPosition(null); + const newDragPosition = rightSide ? 1 - value : value; + onChange(newDragPosition); + }, + [onChange, rightSide] + ); + + const dragManager = useMemo( + () => + new DraggableManager({ + getBounds: getDraggingBounds, + onDragEnd: handleDragEnd, + onDragMove: handleDragUpdate, + onDragStart: handleDragUpdate, + }), + [getDraggingBounds, handleDragEnd, handleDragUpdate] + ); + + useEffect(() => { + return () => { + dragManager.dispose(); + }; + }, [dragManager]); + + let draggerStyle: React.CSSProperties; + let isDraggingCls = ''; + const gripStyle: React.CSSProperties = { left: `${position * 100}%` }; + + if (dragManager.isDragging() && dragPosition != null) { + isDraggingCls = cx({ + isDraggingLeft: dragPosition < position, + isDraggingRight: dragPosition > position, + }); + + // Draw a highlight from the current dragged position back to the original + // position, e.g. highlight the change. Draw the highlight via `left` and + // `right` css styles (simpler than using `width`). + const draggerLeft = `${Math.min(position, dragPosition) * 100}%`; + // subtract 1px for draggerRight to deal with the right border being off + // by 1px when dragging left + const draggerRight = `calc(${(1 - Math.max(position, dragPosition)) * 100}% - 1px)`; + + draggerStyle = { left: draggerLeft, right: draggerRight }; + } else { + draggerStyle = gripStyle; + } + + return ( +
+
+
+
+ ); +}; + +export default VerticalResizer; diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/url.ts b/app/vmui/packages/jaeger-ui-lite/src/components/common/url.ts new file mode 100644 index 0000000000..0a2f583e1f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/url.ts @@ -0,0 +1,95 @@ +import memoizeOne from 'memoize-one'; +import queryString from 'query-string'; +import { matchPath } from 'react-router-dom'; + +import prefixUrl from '../../utils/prefix-url'; +export const MAX_LENGTH = 7000; + +import { SearchQuery } from '../../types/search'; +import parseQuery from '../../utils/parseQuery'; + +function eqEq(a: string | number | null | undefined, b: string | number | null | undefined) { + return (a == null && b == null) || String(a) === String(b); +} + +export const ROUTE_PATH = prefixUrl('/search'); + +const ROUTE_MATCHER = { path: ROUTE_PATH, strict: true, exact: true }; + +export function matches(path: string) { + return Boolean(matchPath(path, ROUTE_MATCHER)); +} + +type TUrlState = Record> & { + traceID?: string | string[]; + spanLinks?: Record; +}; + +export type { TUrlState }; + +export function getUrl(query?: TUrlState) { + const searchUrl = prefixUrl(`/search`); + if (!query) return searchUrl; + + const { traceID, spanLinks, ...rest } = query; + let ids = traceID; + if (spanLinks && traceID) { + ids = (Array.isArray(traceID) ? traceID : [traceID]).filter((id: string) => !spanLinks[id]); + } + const stringifyArg = { + ...rest, + span: + spanLinks && + Object.keys(spanLinks).reduce((res: string[], trace: string) => { + return [...res, `${spanLinks[trace]}@${trace}`]; + }, []), + traceID: ids && ids.length ? ids : undefined, + }; + + const fullUrl = `${searchUrl}?${queryString.stringify(stringifyArg)}`; + if (fullUrl.length <= MAX_LENGTH) return fullUrl; + + const truncated = fullUrl.slice(0, MAX_LENGTH + 1); + if (truncated[MAX_LENGTH] === '&') return truncated.slice(0, -1); + + return truncated.slice(0, truncated.lastIndexOf('&')); +} + +export const getUrlState: (search: string) => TUrlState = memoizeOne(function getUrlState( + search: string +): TUrlState { + const { traceID, span, ...rest } = parseQuery(search); + const rv: TUrlState = { ...rest }; + const traceIDs = new Set(!traceID || Array.isArray(traceID) ? traceID : [traceID]); + const spanLinks: Record = {}; + if (span && span.length) { + (Array.isArray(span) ? span : [span]).forEach(s => { + const [spansStr, trace] = s.split('@'); + traceIDs.add(trace); + if (spansStr) { + if (spanLinks[trace]) spanLinks[trace] = spanLinks[trace].concat(' ', spansStr); + else spanLinks[trace] = spansStr; + } + }); + rv.spanLinks = spanLinks; + } + if (traceIDs.size) rv.traceID = [...traceIDs]; + return rv; +}); + +export function isSameQuery(a: SearchQuery, b: SearchQuery) { + if (Boolean(a) !== Boolean(b)) { + return false; + } + return ( + eqEq(a.end, b.end) && + eqEq(a.limit, b.limit) && + eqEq(a.lookback, b.lookback) && + eqEq(a.maxDuration, b.maxDuration) && + eqEq(a.minDuration, b.minDuration) && + eqEq(a.operation, b.operation) && + eqEq(a.service, b.service) && + eqEq(a.start, b.start) && + eqEq(a.tags, b.tags) + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/utils.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/utils.css new file mode 100644 index 0000000000..30510cebe3 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/utils.css @@ -0,0 +1,85 @@ +.u-width-100 { + width: 100%; +} + +.u-flex-1 { + flex: 1; +} + +.u-mt-vast { + margin-top: 13rem; +} + +.u-cursor-pointer { + cursor: pointer; +} + +.u-tx-muted { + color: var(--text-muted); +} + +.u-tx-inherit { + color: inherit; +} + +.u-tx-ellipsis { + text-overflow: ellipsis; +} + +.u-align-icon { + margin: -0.2rem 0.25rem 0 0; +} + +.u-simple-card { + background-color: var(--surface-secondary); + border: 1px solid var(--border-default); + padding: 1rem; +} + +/* simple-scrollbars */ +.u-simple-scrollbars::-webkit-scrollbar { + -webkit-appearance: none; + width: 8px; + height: 8px; +} + +.u-simple-scrollbars::-webkit-scrollbar-track { + background: var(--surface-secondary); +} + +.u-simple-scrollbars::-webkit-scrollbar-track:vertical { + border-left: 1px solid var(--border-component-subtle); +} + +.u-simple-scrollbars::-webkit-scrollbar-track:horizontal { + border-top: 1px solid var(--border-component-subtle); +} + +.u-simple-scrollbars::-webkit-scrollbar-thumb { + background: var(--control-subtle-default); +} + +.u-simple-scrollbars:hover::-webkit-scrollbar-thumb { + background: var(--control-subtle-hover); +} + +.u-simple-scrollbars::-webkit-scrollbar-thumb:window-inactive { + background: var(--control-subtle-muted); +} + +/* Remove the padding around ant-design's popover, unfortunate but very handy */ +.u-rm-popover-title-padding .ant-popover-title, +.u-rm-popover-content-padding .ant-popover-inner-content { + padding: 0; +} + +/* minimap for plexus */ + +.u-miniMap { + align-items: flex-end; + bottom: 1rem; + display: flex; + left: 1rem; + position: absolute; + z-index: 1; +} \ No newline at end of file diff --git a/app/vmui/packages/jaeger-ui-lite/src/components/common/vars.css b/app/vmui/packages/jaeger-ui-lite/src/components/common/vars.css new file mode 100644 index 0000000000..3b86628b92 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/components/common/vars.css @@ -0,0 +1,267 @@ +/** + * Centralized CSS Variables for Theming + * + * This file contains all color and layout variables used throughout the application. + * New naming scheme follows semantic conventions for better maintainability. + */ + +:root { + /* ============================================ + SURFACE TOKENS (Backgrounds) + Based on audit of background/background-color properties + ============================================ */ + + --surface-primary: var(--ant-color-bg-container); + --surface-secondary: var(--ant-color-bg-layout); + --surface-tertiary: color-mix(in srgb, var(--ant-color-text-base) 6%, var(--ant-color-bg-container)); + + /* dedicated, specific UI element (like the Minimap, a Modal, or a complex panel) */ + --surface-component-background: var(--ant-color-fill-secondary); + --surface-component-background-hover: var(--ant-color-fill-content-hover); + + /* ============================================ + TEXT TOKENS + Based on audit of color property + ============================================ */ + + --text-primary: var(--ant-color-text); + --text-secondary: var(--ant-color-text-secondary); + --text-muted: color-mix(in srgb, var(--ant-color-text) 50%, transparent); + --text-link: var(--ant-color-link); + --text-link-hover: var(--ant-color-link-hover); + --text-inverse: var(--ant-color-white); + + /* ============================================ + BORDER TOKENS + Based on audit of border/border-color properties + ============================================ */ + + --border-default: var(--ant-color-border-secondary); + --border-strong: #ccc; + --border-strongest: #ccc; + --border-component-subtle: var(--ant-color-split); + + /* ============================================ + INTERACTIVE TOKENS (Buttons, Links, Form Controls) + Based on audit of interactive elements + ============================================ */ + + --interactive-primary: var(--ant-color-primary); + --interactive-primary-hover: var(--ant-color-primary-hover); + /* Lighter and RGB variants used by animations and shadow opacity */ + --interactive-primary-light: var(--ant-color-primary-bg); + --interactive-primary-rgb: 17, 147, 154; + --interactive-primary-opaque: rgba(var(--interactive-primary-rgb), 0.6); + + /* ============================================ + FEEDBACK TOKENS (Alerts, Notifications) + ============================================ */ + + --feedback-error: var(--ant-color-error); + --feedback-warning: var(--ant-color-warning); + --feedback-success: var(--ant-color-success); + --feedback-info: var(--ant-color-info); + + /* ============================================ + SHADOW TOKENS + Based on audit of box-shadow property + ============================================ */ + + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.12); + --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.14); + /* centered, strong glow effect on components */ + --shadow-component-focus: 0px 0px 5px rgba(0, 0, 0, 0.3); + + /* ============================================ + SPECIAL PURPOSE TOKENS + Based on unique component needs + ============================================ */ + + /* Layout Variables */ + --nav-height: 48px; + + /* + Span categorical colors (IBM Carbon Sequence) + Reference: https://www.carbondesignsystem.com/data-visualization/color-palettes/#categorical-palettes + */ + --span-color-1: #0072c3; + /* Cyan 60 */ + --span-color-2: #eb6200; + /* Orange 60 */ + --span-color-3: #8a3ffc; + /* Purple 60 */ + --span-color-4: #b28600; + /* Yellow 50 */ + --span-color-5: #005d5d; + /* Teal 70 */ + --span-color-6: #fa4d56; + /* Red 50 */ + --span-color-7: #198038; + /* Green 60 */ + --span-color-8: #9f1853; + /* Magenta 70 */ + --span-color-9: #002d9c; + /* Blue 80 */ + --span-color-10: #6f6f6f; + /* Gray 60 */ + --span-color-11: #00539c; + /* Blue 70 (Cyan-ish) */ + --span-color-12: #8a3800; + /* Orange 70 */ + --span-color-13: #6929c4; + /* Purple 70 */ + --span-color-14: #8e6a00; + /* Yellow 60 */ + --span-color-15: #002d2d; + /* Teal 90 */ + --span-color-16: #570408; + /* Red 90 */ + --span-color-17: #0e6027; + /* Green 70 */ + --span-color-18: #510224; + /* Magenta 90 */ + --span-color-19: #001141; + /* Blue 100 */ + --span-color-20: #491d8b; + /* Purple 80 */ + + /* ============================================ + HELPER & SMALL-SCOPE TOKENS + Tokens used by common components for subtle states + ============================================ */ + + /* Background used when dragging resizers (very subtle tint of interactive color) */ + /* TODO: this should be used in Scrubber, etc. */ + --resizer-drag-bg: rgba(var(--interactive-primary-rgb), 0.05); + + /* Scrollbar Thumb Tokens (Subtle/Utility Control) */ + --control-subtle-default: rgba(0, 0, 0, 0.2); + --control-subtle-hover: rgba(0, 0, 0, 0.35); + --control-subtle-muted: rgba(0, 0, 0, 0.15); + + /* Trace Graph */ + --trace-emphasis-highlight: #fff3d7; + + /* Critical Path */ + --critical-path-color: #000; + --critical-path-outline: #fff; + + /* ============================================ + TRACE TIMELINE TOKENS + Used by TracePageHeader, TimelineViewer, SpanGraph components + ============================================ */ + + /* Trace Timeline */ + --span-graph-inactive: rgba(216, 216, 216, 0.5); + --span-bar-log-marker: rgba(0, 0, 0, 0.5); + + /* ============================================ + SYNTAX HIGHLIGHTING TOKENS + Used for JSON/KeyValue displays + ============================================ */ + --syntax-string: #22863a; + --syntax-number: #005cc5; + --syntax-bool: #d73a49; + --syntax-null: #6a737d; +} + +[data-theme='dark'] { + /* ============================================ + TEXT TOKENS - Dark Mode + ============================================ */ + + --text-inverse: var(--ant-color-black); + + /* ============================================ + INTERACTIVE TOKENS - Dark Mode + ============================================ */ + + --interactive-primary-rgb: 77, 184, 196; + + /* ============================================ + SHADOW TOKENS - Dark Mode + ============================================ */ + + --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3); + --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5); + /* centered, strong glow effect on components */ + --shadow-component-focus: 0px 0px 5px rgba(255, 255, 255, 0.3); + + /* ============================================ + SPECIAL PURPOSE TOKENS - Dark Mode + ============================================ */ + + /* + Span categorical colors (IBM Carbon Sequence) - Dark Mode + Reference: https://www.carbondesignsystem.com/data-visualization/color-palettes/#categorical-palettes + */ + --span-color-1: #1192e8; + /* Cyan 50 */ + --span-color-2: #ff832b; + /* Orange 50 */ + --span-color-3: #a56eff; + /* Purple 50 */ + --span-color-4: #f1c21b; + /* Yellow 40 */ + --span-color-5: #009d9a; + /* Teal 50 */ + --span-color-6: #da1e28; + /* Red 60 */ + --span-color-7: #24a148; + /* Green 50 */ + --span-color-8: #ee538b; + /* Magenta 50 */ + --span-color-9: #00539c; + /* Blue 70 */ + --span-color-10: #8d8d8d; + /* Gray 50 */ + --span-color-11: #0072c3; + /* Cyan 60 */ + --span-color-12: #ba4e00; + /* Orange 60 */ + --span-color-13: #8a3ffc; + /* Purple 60 */ + --span-color-14: #b28600; + /* Yellow 50 */ + --span-color-15: #005d5d; + /* Teal 70 */ + --span-color-16: #a2191f; + /* Red 70 */ + --span-color-17: #198038; + /* Green 60 */ + --span-color-18: #9f1853; + /* Magenta 70 */ + --span-color-19: #002d9c; + /* Blue 80 */ + --span-color-20: #6929c4; + /* Purple 70 */ + + /* ============================================ + HELPER & SMALL-SCOPE TOKENS - Dark Mode + ============================================ */ + + /* Trace Graph */ + --trace-emphasis-highlight: #3d3520; + + /* Critical Path */ + --critical-path-color: #000; + --critical-path-outline: #fff; + + /* ============================================ + TRACE TIMELINE TOKENS - Dark Mode + ============================================ */ + + /* Trace Timeline - Dark Mode */ + --span-graph-inactive: rgba(200, 200, 200, 0.25); + --span-bar-log-marker: rgba(255, 255, 255, 0.5); + + /* ============================================ + SYNTAX HIGHLIGHTING TOKENS - Dark Mode + ============================================ */ + --syntax-string: #79c0ff; + --syntax-number: #d2a8ff; + --syntax-bool: #ff7b72; + --syntax-null: #8b949e; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/default-config.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/default-config.ts new file mode 100644 index 0000000000..b813677955 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/default-config.ts @@ -0,0 +1,134 @@ +import deepFreeze from 'deep-freeze'; + +import { FALLBACK_DAG_MAX_NUM_SERVICES } from './index'; +import getVersion from '../utils/version/get-version'; + +import { version } from '../../package.json'; +import { Config } from '../types/config'; + +const defaultConfig: Config = { + archiveEnabled: true, + criticalPathEnabled: true, + dependencies: { + dagMaxNumServices: FALLBACK_DAG_MAX_NUM_SERVICES, + menuEnabled: true, + }, + menu: [ + { + label: 'About Jaeger', + items: [ + { + label: 'Website / Docs', + url: 'https://www.jaegertracing.io/', + }, + { + label: 'Blog', + url: 'https://medium.com/jaegertracing/', + }, + { + label: 'Twitter', + url: 'https://twitter.com/JaegerTracing', + }, + { + label: 'Discussions / Q&A', + url: 'https://github.com/orgs/jaegertracing/discussions', + }, + { + label: 'Online Chat', + url: 'https://cloud-native.slack.com/archives/CGG7NFUJ3', + }, + { + label: 'GitHub', + url: 'https://github.com/jaegertracing/', + }, + { + label: `Jaeger ${getVersion().gitVersion}`, + }, + { + label: `Commit ${getVersion().gitCommit.substring(0, 7)}`, + }, + { + label: `Build ${getVersion().buildDate}`, + }, + { + label: `Jaeger UI v${version}`, + }, + ], + }, + ], + search: { + maxLookback: { + label: '2 Days', + value: '2d', + }, + maxLimit: 1500, + }, + traceIdDisplayLength: 7, + storageCapabilities: { + archiveStorage: false, + }, + tracking: { + gaID: null, + trackErrors: true, + customWebAnalytics: null, + }, + linkPatterns: [], + monitor: { + menuEnabled: true, + emptyState: { + mainTitle: 'Get started with Service Performance Monitoring', + subTitle: + 'A high-level monitoring dashboard that helps you cut down the time to identify and resolve anomalies and issues.', + description: + 'Service Performance Monitoring aggregates tracing data into RED metrics and visualizes them in service and operation level dashboards.', + button: { + text: 'Read the Documentation', + onClick: () => window.open('https://www.jaegertracing.io/docs/latest/spm/'), + }, + alert: { + message: 'Service Performance Monitoring requires a Prometheus-compatible time series database.', + type: 'info', + }, + }, + docsLink: 'https://www.jaegertracing.io/docs/latest/spm/', + }, + disableFileUploadControl: false, + disableJsonView: false, + forbidNewPage: false, + traceGraph: { + layoutManagerMemory: undefined, + }, + + deepDependencies: { + menuEnabled: false, + }, + qualityMetrics: { + menuEnabled: false, + menuLabel: 'Trace Quality', + apiEndpoint: '/api/quality-metrics', + }, + traceDiff: { + helpLink: 'https://medium.com/jaegertracing/trace-comparisons-arrive-in-jaeger-1-7-a97ad5e2d05d', + }, + themes: { + enabled: true, + }, + useOpenTelemetryTerms: false, +}; + +// Fields that should be merged with user-supplied config values rather than overwritten. +type TMergeField = 'dependencies' | 'search' | 'tracking'; +export const mergeFields: readonly TMergeField[] = ['dependencies', 'search', 'tracking']; + +export default deepFreeze(defaultConfig); + +export const deprecations = [ + { + formerKey: 'dependenciesMenuEnabled', + currentKey: 'dependencies.menuEnabled', + }, + { + formerKey: 'gaTrackingID', + currentKey: 'tracking.gaID', + }, +]; diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/default-version.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/default-version.ts new file mode 100644 index 0000000000..47d8d70ffb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/default-version.ts @@ -0,0 +1,5 @@ +export default Object.freeze({ + gitCommit: '', + gitVersion: '', + buildDate: '', +}); diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/index.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/index.ts new file mode 100644 index 0000000000..d6d7f648d3 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/index.ts @@ -0,0 +1,19 @@ +export const TOP_NAV_HEIGHT = 46; + +// Max number of services that "Hierarchical Layout" with "dot" layout engine can can render +// Can be configured with "dependencies.dagMaxNumServices" +export const FALLBACK_DAG_MAX_NUM_SERVICES = 500; + +// Max number of services that layouts can render without selecting a focal service +export const DAG_MAX_NUM_SERVICES = 1200; +export const FALLBACK_TRACE_NAME = ''; + +export const FETCH_DONE = 'FETCH_DONE'; +export const FETCH_ERROR = 'FETCH_ERROR'; +export const FETCH_LOADING = 'FETCH_LOADING'; + +export const fetchedState = { + DONE: FETCH_DONE, + ERROR: FETCH_ERROR, + LOADING: FETCH_LOADING, +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/keyboard-keys.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/keyboard-keys.ts new file mode 100644 index 0000000000..ac087f8ec6 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/keyboard-keys.ts @@ -0,0 +1,11 @@ +/** + * Keyboard key constants for use with KeyboardEvent.key. + * Previously imported from ts-key-enum, but v3 uses const enum which + * doesn't work with Vite/esbuild (no runtime JavaScript representation). + */ +export const KeyboardKey = { + Escape: 'Escape', + ArrowUp: 'ArrowUp', + ArrowDown: 'ArrowDown', + Enter: 'Enter', +} as const; diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/search-form.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/search-form.ts new file mode 100644 index 0000000000..5644fcec8f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/search-form.ts @@ -0,0 +1,5 @@ +export const DEFAULT_OPERATION = 'all'; +export const DEFAULT_LOOKBACK = '1h'; +export const DEFAULT_LIMIT = 20; + +export const CHANGE_SERVICE_ACTION_TYPE = '@@redux/searchSideBar/CHANGE_SERVICE'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/constants/tag-keys.ts b/app/vmui/packages/jaeger-ui-lite/src/constants/tag-keys.ts new file mode 100644 index 0000000000..014947634a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/constants/tag-keys.ts @@ -0,0 +1,3 @@ +export const HTTP_METHOD = 'http.method'; +export const PEER_SERVICE = 'peer.service'; +export const SPAN_KIND = 'span.kind'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/middlewares/index.ts b/app/vmui/packages/jaeger-ui-lite/src/middlewares/index.ts new file mode 100644 index 0000000000..d0a30f4413 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/middlewares/index.ts @@ -0,0 +1,30 @@ +import promiseMiddleware from 'redux-promise-middleware'; +import { replace } from 'redux-first-history'; +import { Middleware } from 'redux'; + +import { searchTraces, fetchServiceOperations } from '../actions/jaeger-api'; +// import { getUrl as getSearchUrl } from '../components/SearchTracePage/url'; +import { getSearchUrl } from '../components/TraceSearchPage/url'; +import { CHANGE_SERVICE_ACTION_TYPE } from '../constants/search-form'; +import { ReduxState } from '../types'; + +/** + * Middleware to load "operations" for a particular service. + */ +export const loadOperationsForServiceMiddleware: Middleware<{}, ReduxState> = + store => next => (action: any) => { + if (action.type === CHANGE_SERVICE_ACTION_TYPE && action.payload !== '-') { + store.dispatch(fetchServiceOperations(action.payload) as any); + } + return next(action); + }; + +export const historyUpdateMiddleware: Middleware<{}, ReduxState> = store => next => (action: any) => { + if (action.type === String(searchTraces)) { + const url = getSearchUrl(action.meta.query); + store.dispatch(replace(url)); + } + return next(action); +}; + +export const promise = promiseMiddleware; diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/OtelSpanFacade.ts b/app/vmui/packages/jaeger-ui-lite/src/model/OtelSpanFacade.ts new file mode 100644 index 0000000000..4876ba57b2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/OtelSpanFacade.ts @@ -0,0 +1,190 @@ +import { Span } from '../types/trace'; +import { + IOtelSpan, + IAttribute, + AttributeValue, + IEvent, + ILink, + IStatus, + StatusCode, + SpanKind, + IResource, + IScope, +} from '../types/otel'; + +export default class OtelSpanFacade implements IOtelSpan { + private legacySpan: Span; + private _kind: SpanKind; + private _parentSpanID: string | undefined; + private _attributes: IAttribute[]; + private _events: IEvent[]; + private _links: ILink[]; + private _status: IStatus; + private _resource: IResource; + private _inboundLinks: ILink[]; + private _childSpans: ReadonlyArray = []; + private _parentSpan?: IOtelSpan; + + constructor(legacySpan: Span) { + this.legacySpan = legacySpan; + + // Pre-compute expensive fields + const kindTag = this.legacySpan.tags.find(t => t.key === 'span.kind'); + this._kind = SpanKind.INTERNAL; + if (kindTag) { + const val = String(kindTag.value).toUpperCase(); + if (val in SpanKind) { + this._kind = SpanKind[val as keyof typeof SpanKind]; + } + } + + // Find parent span ID according to the following priority: + // 1. Earliest CHILD_OF reference with the same traceID + // 2. Otherwise, earliest FOLLOWS_FROM reference with the same traceID + // 3. If no reference with same traceID exists, parent is undefined + const { references, traceID } = this.legacySpan; + const parentSpanRef = + references.find(r => r.traceID === traceID && r.refType === 'CHILD_OF') ?? + references.find(r => r.traceID === traceID && r.refType === 'FOLLOWS_FROM'); + this._parentSpanID = parentSpanRef?.spanID; + + this._attributes = OtelSpanFacade.toOtelAttributes(this.legacySpan.tags); + + this._events = this.legacySpan.logs.map(log => ({ + timestamp: log.timestamp as IEvent['timestamp'], + name: (log.fields.find(f => f.key === 'event')?.value as string) || 'log', + attributes: OtelSpanFacade.toOtelAttributes(log.fields), + })); + + this._links = this.legacySpan.references + .filter(ref => ref !== parentSpanRef) + .map(ref => ({ + traceID: ref.traceID, + spanID: ref.spanID, + attributes: [], // Legacy references don't have attributes + })); + + const errorTag = this.legacySpan.tags.find(t => t.key === 'error'); + this._status = + errorTag && errorTag.value ? { code: StatusCode.ERROR, message: 'error' } : { code: StatusCode.OK }; + + const process = this.legacySpan.process; + this._resource = { + attributes: process ? OtelSpanFacade.toOtelAttributes(process.tags) : [], + serviceName: process ? process.serviceName : 'unknown-service', + }; + + this._inboundLinks = this.legacySpan.subsidiarilyReferencedBy.map(ref => ({ + traceID: ref.traceID, + spanID: ref.spanID, + attributes: [], + })); + } + + private static toOtelAttributes(tags: ReadonlyArray<{ key: string; value: any }>): IAttribute[] { + return tags + .filter(kv => kv.value !== null && kv.value !== undefined) + .map(kv => ({ + key: kv.key, + value: kv.value as AttributeValue, + })); + } + + get traceID(): string { + return this.legacySpan.traceID; + } + + get spanID(): string { + return this.legacySpan.spanID; + } + + get parentSpanID(): string | undefined { + return this._parentSpanID; + } + + get name(): string { + return this.legacySpan.operationName; + } + + get kind(): SpanKind { + return this._kind; + } + + get startTime(): IOtelSpan['startTime'] { + return this.legacySpan.startTime as IOtelSpan['startTime']; + } + + get endTime(): IOtelSpan['endTime'] { + return (this.legacySpan.startTime + this.legacySpan.duration) as IOtelSpan['endTime']; + } + + get duration(): IOtelSpan['duration'] { + return this.legacySpan.duration as IOtelSpan['duration']; + } + + get attributes(): IAttribute[] { + return this._attributes; + } + + get events(): IEvent[] { + return this._events; + } + + get links(): ILink[] { + return this._links; + } + + get status(): IStatus { + return this._status; + } + + get resource(): IResource { + return this._resource; + } + + get parentSpan(): IOtelSpan | undefined { + return this._parentSpan; + } + + set parentSpan(value: IOtelSpan | undefined) { + this._parentSpan = value; + } + + get instrumentationScope(): IScope { + // Legacy Jaeger doesn't have explicit instrumentation scope, + // but we can look for it in tags if it was mapped there by exporters. + const name = + (this.legacySpan.tags.find(t => t.key === 'otel.library.name')?.value as string) || 'unknown'; + const version = this.legacySpan.tags.find(t => t.key === 'otel.library.version')?.value as string; + return { name, version }; + } + + get depth(): number { + return this.legacySpan.depth; + } + + get hasChildren(): boolean { + return this._childSpans.length > 0; + } + + get childSpans(): ReadonlyArray { + return this._childSpans; + } + + set childSpans(value: ReadonlyArray) { + this._childSpans = value; + } + + get relativeStartTime(): IOtelSpan['relativeStartTime'] { + return this.legacySpan.relativeStartTime as IOtelSpan['relativeStartTime']; + } + + get inboundLinks(): ILink[] { + return this._inboundLinks; + } + + // Legacy Jaeger-specific properties for UI compatibility + get warnings(): ReadonlyArray | null { + return this.legacySpan.warnings; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/OtelTraceFacade.ts b/app/vmui/packages/jaeger-ui-lite/src/model/OtelTraceFacade.ts new file mode 100644 index 0000000000..d3689993a6 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/OtelTraceFacade.ts @@ -0,0 +1,115 @@ +import { Trace } from '../types/trace'; +import { IOtelTrace, IOtelSpan } from '../types/otel'; +import OtelSpanFacade from './OtelSpanFacade'; + +export default class OtelTraceFacade implements IOtelTrace { + private legacyTrace: Trace; + private _spans: IOtelSpan[]; + private _spanMap: Map; + private _rootSpans: IOtelSpan[]; + private _orphanSpanCount: number; + + constructor(legacyTrace: Trace) { + this.legacyTrace = legacyTrace; + + // Pre-compute spans + this._spans = this.legacyTrace.spans.map(s => new OtelSpanFacade(s)); + + // Build spanMap + this._spanMap = new Map(); + this._spans.forEach(span => { + this._spanMap.set(span.spanID, span); + }); + + // Build rootSpans from legacy trace rootSpans + this._rootSpans = this.legacyTrace.rootSpans.map(s => { + const otelSpan = this._spanMap.get(s.spanID); + if (!otelSpan) throw new Error(`Root span ${s.spanID} not found in spanMap`); + return otelSpan; + }); + + // Calculate orphan span count + // A span is orphaned if it has a parentSpanID but the parent is not in the trace + this._orphanSpanCount = this._spans.filter( + s => s.parentSpanID && !this._spanMap.has(s.parentSpanID) + ).length; + + // Wire up parentSpan, childSpans, and link span references + this._spans.forEach(span => { + const facade = span as OtelSpanFacade; + if (facade.parentSpanID) { + facade.parentSpan = this._spanMap.get(facade.parentSpanID); + } + + // Populate childSpans using legacySpan.childSpans + const legacySpan = (facade as any).legacySpan; + if (legacySpan && legacySpan.childSpans) { + facade.childSpans = legacySpan.childSpans + .map((s: any) => this._spanMap.get(s.spanID)) + .filter(Boolean); + } + + // Wire up links + facade.links.forEach(link => { + link.span = this._spanMap.get(link.spanID); + }); + + // Wire up inboundLinks + facade.inboundLinks.forEach(link => { + link.span = this._spanMap.get(link.spanID); + }); + }); + } + + get traceID(): string { + return this.legacyTrace.traceID; + } + + get spans(): IOtelSpan[] { + return this._spans; + } + + get spanMap(): Map { + return this._spanMap; + } + + get rootSpans(): IOtelSpan[] { + return this._rootSpans; + } + + get duration(): IOtelTrace['duration'] { + return this.legacyTrace.duration as IOtelTrace['duration']; + } + + get startTime(): IOtelTrace['startTime'] { + return this.legacyTrace.startTime as IOtelTrace['startTime']; + } + + get endTime(): IOtelTrace['endTime'] { + return this.legacyTrace.endTime as IOtelTrace['endTime']; + } + + get traceName(): string { + return this.legacyTrace.traceName; + } + + get tracePageTitle(): string { + return this.legacyTrace.tracePageTitle; + } + + get traceEmoji(): string { + return this.legacyTrace.traceEmoji; + } + + get services(): ReadonlyArray<{ name: string; numberOfSpans: number }> { + return this.legacyTrace.services; + } + + get orphanSpanCount(): number { + return this._orphanSpanCount; + } + + hasErrors(): boolean { + return this._spans.some(sp => sp.status.code === 'ERROR'); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/link-patterns.ts b/app/vmui/packages/jaeger-ui-lite/src/model/link-patterns.ts new file mode 100644 index 0000000000..6d5013467b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/link-patterns.ts @@ -0,0 +1,298 @@ +import _uniq from 'lodash/uniq'; +import memoize from 'lru-memoize'; + +import { getConfigValue } from '../utils/config/get-config'; +import { encodedStringSupplant, getParamNames } from '../utils/stringSupplant'; +import { getParameterAndFormatter } from '../utils/link-formatting'; +import { TNil } from '../types'; +import { Hyperlink } from '../types/hyperlink'; +import { IOtelSpan, IOtelTrace, IAttribute } from '../types/otel'; + +type ProcessedTemplate = { + parameters: string[]; + template: (template: { [key: string]: any }) => string; +}; + +const ENABLE_LEGACY_LINK_PATTERNS = true; + +type LinkPatternType = 'attributes' | 'resource' | 'events' | 'traces'; +type LegacyLinkPatternType = 'tags' | 'process' | 'logs'; + +const VALID_TRACE_KEYS = ['traceID', 'traceName', 'duration', 'startTime', 'endTime']; + +const VALID_SPAN_KEYS = ['spanID', 'operationName', 'duration', 'startTime']; + +type ProcessedLinkPattern = { + object: any; + type: (link: LinkPatternType | LegacyLinkPatternType) => boolean; + key: (link: string) => boolean; + value: (value: any) => boolean; + url: ProcessedTemplate; + text: ProcessedTemplate; + parameters: string[]; +}; + +export function processTemplate(template: any, encodeFn: (unencoded: any) => string): ProcessedTemplate { + if (typeof template !== 'string') { + if (template && Array.isArray(template.parameters) && typeof template.template === 'function') { + return template; + } + + throw new Error('Invalid template'); + } + return { + parameters: getParamNames(template), + template: encodedStringSupplant.bind(null, template, encodeFn), + }; +} + +export function createTestFunction(entry: any): (arg: any) => boolean { + if (typeof entry === 'string') { + return (arg: any) => arg === entry; + } + if (Array.isArray(entry)) { + return (arg: any) => entry.indexOf(arg) > -1; + } + if (entry instanceof RegExp) { + return (arg: any) => entry.test(arg); + } + if (typeof entry === 'function') { + return entry; + } + if (entry == null) { + return () => true; + } + throw new Error(`Invalid value: ${entry}`); +} + +const identity = (a: any): typeof a => a; + +export function processLinkPattern(pattern: any): ProcessedLinkPattern | TNil { + try { + const url = processTemplate(pattern.url, encodeURIComponent); + const text = processTemplate(pattern.text, identity); + return { + object: pattern, + type: createTestFunction(pattern.type), + key: createTestFunction(pattern.key), + value: createTestFunction(pattern.value), + url, + text, + parameters: _uniq(url.parameters.concat(text.parameters)), + }; + } catch (error) { + console.error(`Ignoring invalid link pattern: ${error}`, pattern); + return null; + } +} + +export function getParameterInArray(name: string, array: ReadonlyArray): IAttribute | undefined { + if (array) { + return array.find(entry => entry.key === name); + } + return undefined; +} + +export function getParameterInAncestor(name: string, span: IOtelSpan): IAttribute | undefined { + let currentSpan: IOtelSpan | undefined = span; + while (currentSpan) { + if (VALID_SPAN_KEYS.includes(name)) { + let value: any; + switch (name) { + case 'spanID': + value = currentSpan.spanID; + break; + case 'operationName': + value = currentSpan.name; + break; + case 'duration': + value = currentSpan.duration; + break; + case 'startTime': + value = currentSpan.startTime; + break; + default: + // If it's a valid span key but no value found, continue to check attributes + break; + } + if (value !== undefined) { + return { key: name, value }; + } + } + + const result = + getParameterInArray(name, currentSpan.attributes) || + getParameterInArray(name, currentSpan.resource.attributes); + if (result) { + return result; + } + currentSpan = currentSpan.parentSpan; + } + + return undefined; +} + +export function getParameterInTrace( + name: string, + trace: IOtelTrace +): { key: string; value: any } | undefined { + if (VALID_TRACE_KEYS.includes(name)) { + let value: any; + switch (name) { + case 'traceID': + value = trace.traceID; + break; + case 'traceName': + value = trace.traceName; + break; + case 'duration': + value = trace.duration; + break; + case 'startTime': + value = trace.startTime; + break; + case 'endTime': + value = trace.endTime; + break; + default: + return undefined; + } + return { key: name, value }; + } + + return undefined; +} + +function callTemplate(template: ProcessedTemplate, data: any): string { + return template.template(data); +} + +export function computeTraceLink(linkPatterns: ProcessedLinkPattern[], trace: IOtelTrace): Hyperlink[] { + const result: Hyperlink[] = []; + + linkPatterns + .filter(pattern => pattern.type('traces')) + .forEach(pattern => { + const parameterValues: Record = {}; + const allParameters = pattern.parameters.every(parameter => { + const { parameterName, formatFunction } = getParameterAndFormatter(parameter); + const traceKV = getParameterInTrace(parameterName, trace); + + if (traceKV) { + // At this point is safe to access to trace object using parameter variable because + // we validated parameter against validKeys, this implies that parameter a keyof IOtelTrace. + parameterValues[parameterName] = formatFunction ? formatFunction(traceKV.value) : traceKV.value; + + return true; + } + return false; + }); + + if (allParameters) { + result.push({ + url: callTemplate(pattern.url, parameterValues), + text: callTemplate(pattern.text, parameterValues), + }); + } + }); + + return result; +} + +// computeLinks generates {url, text} link pairs by applying link patterms +// to the element `itemIndex` of `items` array. The values for template +// variables used in the patterns are looked up first in `items`, then +// in `span.attributes` and `span.resource.attributes`, and then in ancestor spans +// recursively via `span.parentSpan`. +export function computeLinks( + linkPatterns: ProcessedLinkPattern[], + span: IOtelSpan, + items: ReadonlyArray, + itemIndex: number, + trace: IOtelTrace +): Hyperlink[] { + const item = items[itemIndex]; + let type: LinkPatternType = 'events'; + let legacyType: LegacyLinkPatternType = 'logs'; + + if (span.resource.attributes === items) { + type = 'resource'; + legacyType = 'process'; + } else if (span.attributes === items) { + type = 'attributes'; + legacyType = 'tags'; + } + + const result: Hyperlink[] = []; + linkPatterns.forEach(pattern => { + let typeMatches = pattern.type(type); + if (!typeMatches && ENABLE_LEGACY_LINK_PATTERNS) { + typeMatches = pattern.type(legacyType); + } + + if (typeMatches && pattern.key(item.key) && pattern.value(item.value)) { + const parameterValues: Record = {}; + const allParameters = pattern.parameters.every(parameter => { + let entry; + + if (parameter.startsWith('trace.')) { + entry = getParameterInTrace(parameter.split('trace.')[1], trace); + } else { + entry = getParameterInArray(parameter, items); + + if (!entry && type !== 'resource') { + // do not look in ancestors for resource attributes because the same object may appear in different places in the hierarchy + // and the cache in getLinks uses that object as a key + entry = getParameterInAncestor(parameter, span); + } + } + + if (entry) { + parameterValues[parameter] = entry.value; + return true; + } + + console.warn( + `Skipping link pattern, missing parameter ${parameter} for key ${item.key} in ${type}.`, + pattern.object + ); + return false; + }); + if (allParameters) { + result.push({ + url: callTemplate(pattern.url, parameterValues), + text: callTemplate(pattern.text, parameterValues), + }); + } + } + }); + return result; +} + +export function createGetLinks( + linkPatterns: ProcessedLinkPattern[], + cache: WeakMap +): (span: IOtelSpan, items: ReadonlyArray, itemIndex: number, trace: IOtelTrace) => Hyperlink[] { + return (span: IOtelSpan, items: ReadonlyArray, itemIndex: number, trace: IOtelTrace) => { + if (linkPatterns.length === 0) { + return []; + } + const item = items[itemIndex]; + let result = cache.get(item); + if (!result) { + result = computeLinks(linkPatterns, span, items, itemIndex, trace); + cache.set(item, result); + } + return result; + }; +} + +export const processedLinks: ProcessedLinkPattern[] = (getConfigValue('linkPatterns') || []) + .map(processLinkPattern) + .filter(Boolean); + +export const getTraceLinks: (trace: IOtelTrace) => Hyperlink[] = memoize(10)((trace: IOtelTrace) => { + return computeTraceLink(processedLinks, trace); +}); + +export default createGetLinks(processedLinks, new WeakMap()); diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/order-by.ts b/app/vmui/packages/jaeger-ui-lite/src/model/order-by.ts new file mode 100644 index 0000000000..49427fc844 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/order-by.ts @@ -0,0 +1,5 @@ +export const MOST_RECENT = 'MOST_RECENT'; +export const LONGEST_FIRST = 'LONGEST_FIRST'; +export const SHORTEST_FIRST = 'SHORTEST_FIRST'; +export const MOST_SPANS = 'MOST_SPANS'; +export const LEAST_SPANS = 'LEAST_SPANS'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/search.ts b/app/vmui/packages/jaeger-ui-lite/src/model/search.ts new file mode 100644 index 0000000000..e251ba208c --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/search.ts @@ -0,0 +1,23 @@ +import { LEAST_SPANS, LONGEST_FIRST, MOST_RECENT, MOST_SPANS, SHORTEST_FIRST } from './order-by'; + +import { Trace } from '../types/trace'; + +const comparators: Record number> = { + [MOST_RECENT]: (a, b) => +(b.startTime > a.startTime) || +(a.startTime === b.startTime) - 1, + [SHORTEST_FIRST]: (a, b) => +(a.duration > b.duration) || +(a.duration === b.duration) - 1, + [LONGEST_FIRST]: (a, b) => +(b.duration > a.duration) || +(a.duration === b.duration) - 1, + [MOST_SPANS]: (a, b) => +(b.spans.length > a.spans.length) || +(a.spans.length === b.spans.length) - 1, + [LEAST_SPANS]: (a, b) => +(a.spans.length > b.spans.length) || +(a.spans.length === b.spans.length) - 1, +}; + +/** + * Sorts `Trace[]`, in place. + * + * @param {Trace[]} traces The `Trace` array to sort. + * @param {string} sortBy A sort specification, see ./order-by.js. + */ + +export function sortTraces(traces: Trace[], sortBy: string) { + const comparator = comparators[sortBy] || comparators[LONGEST_FIRST]; + traces.sort(comparator); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/span.ts b/app/vmui/packages/jaeger-ui-lite/src/model/span.ts new file mode 100644 index 0000000000..49bfafd797 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/span.ts @@ -0,0 +1,12 @@ +import { Span } from '../types/trace'; + +/** + * Searches the span.references to find 'CHILD_OF' reference type or returns null. + * @param {Span} span The span whose parent is to be returned. + * @return {Span|null} The parent span if there is one, null otherwise. + */ + +export function getParent(span: Span) { + const parentRef = span.references ? span.references.find(ref => ref.refType === 'CHILD_OF') : null; + return parentRef ? parentRef.span : null; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/trace-viewer.ts b/app/vmui/packages/jaeger-ui-lite/src/model/trace-viewer.ts new file mode 100644 index 0000000000..db3f8735ea --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/trace-viewer.ts @@ -0,0 +1,93 @@ +import _memoize from 'lodash/memoize'; + +import { Span } from '../types/trace'; + +export type TracePageHeaderParts = { + serviceName: string; + operationName: string; +}; + +export function _getTracePageHeaderPartsImpl(spans: ReadonlyArray): TracePageHeaderParts | null { + // Use a span with no references to another span in given array + // prefering the span with the fewest references + // using start time as a tie breaker + let candidateSpan: Span | undefined; + const allIDs: Set = new Set(spans.map(({ spanID }) => spanID)); + + for (let i = 0; i < spans.length; i++) { + const hasInternalRef = + spans[i].references && + spans[i].references.some(({ traceID, spanID }) => traceID === spans[i].traceID && allIDs.has(spanID)); + if (hasInternalRef) continue; + + if (!candidateSpan) { + candidateSpan = spans[i]; + continue; + } + + const thisRefLength = (spans[i].references && spans[i].references.length) || 0; + const candidateRefLength = (candidateSpan.references && candidateSpan.references.length) || 0; + + if ( + thisRefLength < candidateRefLength || + (thisRefLength === candidateRefLength && spans[i].startTime < candidateSpan.startTime) + ) { + candidateSpan = spans[i]; + } + } + + if (!candidateSpan) { + return null; + } + + return { + serviceName: candidateSpan.process.serviceName, + operationName: candidateSpan.operationName, + }; +} + +export const getTracePageHeaderParts = _memoize( + _getTracePageHeaderPartsImpl, + (spans: ReadonlyArray) => { + if (!spans.length) return 0; + return spans[0].traceID; + } +); + +export function getTraceName(spans: ReadonlyArray): string { + const parts = getTracePageHeaderParts(spans); + + return parts ? `${parts.serviceName}: ${parts.operationName}` : ''; +} + +export function getTracePageTitle(spans: ReadonlyArray): string { + const parts = getTracePageHeaderParts(spans); + + return parts ? `${parts.operationName} (${parts.serviceName})` : ''; +} + +export function getTraceEmoji(spans: ReadonlyArray): string { + if (!spans.length) return ''; + + // prettier-ignore + const emojiSet = [ + '🐶', '🐱', '🐭', '🦊', '🐨', '🐮', '🐷', '🐸', '🐵', '🐔', '🐤', '🦆', + '🦉', '🐝', '🦋', '🐢', '🦀', '🐳', '🐊', '🦒', '🪶', '🦩', '🐉', '🍄', + '🌸', '🌜', '🔥', '🌪️', '💧', '🍏', '🍊', '🍉', '🍒', '🥦', '🌽', '🍠', + '🥐', '🥖', '🥚', '🧀', '🍗', '🍟', '🍕', '🍣', '🍤', '🍙', '🍪', '⚽️', + '🏀', '🥎', '🎹', '🎲', '🎮', '🧩', '🚗', '🚲', '🚂', '⛺️', '📞', '⏰', + '🔌', '💎', '🪚', '🧲', '🧬', '🎀', '📬', '📘', '🩷', '🎵', '🏴', '🚩', + ]; + + const traceID = spans[0].traceID; + let index = 0; + + if (traceID) { + for (let i = 0; i < traceID.length; i++) { + const hexChar = traceID.slice(i, i + 1); + index = (index * 16 + parseInt(hexChar, 16)) % emojiSet.length; + } + } + + return emojiSet[index]; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/model/transform-trace-data.ts b/app/vmui/packages/jaeger-ui-lite/src/model/transform-trace-data.ts new file mode 100644 index 0000000000..2845a9eade --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/model/transform-trace-data.ts @@ -0,0 +1,220 @@ +import _isEqual from 'lodash/isEqual'; + +import { getConfigValue } from '../utils/config/get-config'; +import { getTraceEmoji, getTraceName, getTracePageTitle } from './trace-viewer'; +import { KeyValuePair, Span, SpanData, SpanReference, Trace, TraceData } from '../types/trace'; +import { IOtelTrace } from '../types/otel'; + +import OtelTraceFacade from './OtelTraceFacade'; + +// exported for tests +export function deduplicateTags(spanTags: ReadonlyArray) { + const warningsHash: Map = new Map(); + const tags: KeyValuePair[] = spanTags.reduce((uniqueTags, tag) => { + if (!uniqueTags.some(t => t.key === tag.key && t.value === tag.value)) { + uniqueTags.push(tag); + } else { + warningsHash.set(`${tag.key}:${tag.value}`, `Duplicate tag "${tag.key}:${tag.value}"`); + } + return uniqueTags; + }, []); + const warnings = Array.from(warningsHash.values()); + return { tags, warnings }; +} + +// exported for tests +export function orderTags(spanTags: KeyValuePair[], topPrefixes?: string[]) { + const orderedTags: KeyValuePair[] = spanTags.slice(); + const tp = (topPrefixes || []).map((p: string) => p.toLowerCase()); + + orderedTags.sort((a, b) => { + const aKey = a.key.toLowerCase(); + const bKey = b.key.toLowerCase(); + + for (let i = 0; i < tp.length; i++) { + const p = tp[i]; + if (aKey.startsWith(p) && !bKey.startsWith(p)) { + return -1; + } + if (!aKey.startsWith(p) && bKey.startsWith(p)) { + return 1; + } + } + + if (aKey > bKey) { + return 1; + } + if (aKey < bKey) { + return -1; + } + return 0; + }); + + return orderedTags; +} + +/** + * NOTE: Mutates `data` - Transform the HTTP response data into the form the app + * generally requires. + */ +export default function transformTraceData(data: TraceData & { spans: SpanData[] }): Trace | null { + let { traceID } = data; + if (!traceID) { + return null; + } + traceID = traceID.toLowerCase(); + + let traceEndTime = 0; + let traceStartTime = Number.MAX_SAFE_INTEGER; + const spanIdCounts = new Map(); + const spanMap = new Map(); + + // Filter out spans with empty start times + data.spans = data.spans.filter(span => Boolean(span.startTime)); + + const numSpans = data.spans.length; + for (let i = 0; i < numSpans; i++) { + // Unsafe cast to avoid memory allocations. + // We populate/fix all properties below. + const span: Span = data.spans[i] as Span; + const { startTime, duration, processID } = span; + let spanID = span.spanID; + // make sure span IDs are unique + const idCount = spanIdCounts.get(spanID); + if (idCount != null) { + console.warn(`Dupe spanID, ${idCount + 1} x ${spanID}`, span, spanMap.get(spanID)); + if (_isEqual(span, spanMap.get(spanID))) { + console.warn('\t two spans with same ID have `isEqual(...) === true`'); + } + spanIdCounts.set(spanID, idCount + 1); + spanID = `${spanID}_${idCount}`; + span.spanID = spanID; + } else { + spanIdCounts.set(spanID, 1); + } + span.process = data.processes[processID] || { serviceName: 'unknown-service' }; + span.process.tags = span.process.tags || []; + span.tags = span.tags || []; + span.logs = span.logs || []; + span.logs.forEach(log => { + log.fields = log.fields || []; + }); + span.references = span.references || []; + span.childSpans = []; + span.subsidiarilyReferencedBy = []; + + const tagsInfo = deduplicateTags(span.tags); + span.tags = orderTags(tagsInfo.tags, getConfigValue('topTagPrefixes')); + span.warnings = span.warnings || []; + if (tagsInfo.warnings && tagsInfo.warnings.length > 0) { + (span.warnings as string[]).push(...tagsInfo.warnings); + } + + spanMap.set(spanID, span); + + // update trace's start / end time + if (startTime < traceStartTime) { + traceStartTime = startTime; + } + if (startTime + duration > traceEndTime) { + traceEndTime = startTime + duration; + } + } + + const rootSpans: Span[] = []; + let orphanSpanCount = 0; + + // Second pass: link parents/children and identify roots + for (const span of spanMap.values()) { + let parent: Span | undefined; + if (Array.isArray(span.references) && span.references.length > 0) { + // Find the first CHILD_OF or FOLLOWS_FROM reference that exists in the spanMap + for (const ref of span.references) { + if (ref.refType === 'CHILD_OF' || ref.refType === 'FOLLOWS_FROM') { + parent = spanMap.get(ref.spanID); + if (parent) { + break; + } + } + } + if (!parent) { + orphanSpanCount++; + } + } + + if (parent) { + // It's a child + (parent.childSpans as Span[]).push(span); + } else { + // It's a root + rootSpans.push(span); + } + } + + const spans: Span[] = []; + const svcCounts: Record = {}; + + // Depth-first traversal to order spans and populate flat array + const processSpan = (span: Span, depth: number) => { + span.depth = depth; + span.hasChildren = span.childSpans.length > 0; + span.relativeStartTime = span.startTime - traceStartTime; + + const { serviceName } = span.process; + svcCounts[serviceName] = (svcCounts[serviceName] || 0) + 1; + + span.references.forEach((ref, index) => { + const refSpan = spanMap.get(ref.spanID); + if (refSpan) { + ref.span = refSpan; + if (index > 0) { + // Don't take into account the parent, just other references. + refSpan.subsidiarilyReferencedBy = refSpan.subsidiarilyReferencedBy || []; + (refSpan.subsidiarilyReferencedBy as SpanReference[]).push({ + spanID: span.spanID, + traceID, + span, + refType: ref.refType, + }); + } + } + }); + + spans.push(span); + + // Sort children by startTime before processing them + (span.childSpans as Span[]).sort((a, b) => a.startTime - b.startTime); + span.childSpans.forEach(child => processSpan(child, depth + 1)); + }; + + rootSpans.sort((a, b) => a.startTime - b.startTime); + rootSpans.forEach(root => processSpan(root, 0)); + + const traceName = getTraceName(spans); + const tracePageTitle = getTracePageTitle(spans); + const traceEmoji = getTraceEmoji(spans); + const services = Object.keys(svcCounts).map(name => ({ name, numberOfSpans: svcCounts[name] })); + + return { + services, + spans, + traceID, + traceName, + tracePageTitle, + traceEmoji, + spanMap, + rootSpans, + processes: data.processes, + duration: (traceEndTime - traceStartTime) as IOtelTrace['duration'], + startTime: traceStartTime as IOtelTrace['startTime'], + endTime: traceEndTime as IOtelTrace['endTime'], + orphanSpanCount, + + asOtelTrace() { + if (!this._otelFacade) { + this._otelFacade = new OtelTraceFacade(this); + } + return this._otelFacade!; + }, + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/config.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/config.ts new file mode 100644 index 0000000000..8c129b0a35 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/config.ts @@ -0,0 +1,9 @@ +import getConfig from '../utils/config/get-config'; +import { Config } from '../types/config'; + +export default function reduceConfig(state?: Config): Config { + if (state === undefined) { + return getConfig(); + } + return state; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/dependencies.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/dependencies.ts new file mode 100644 index 0000000000..39745b6914 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/dependencies.ts @@ -0,0 +1,36 @@ +import { handleActions } from 'redux-actions'; + +import { fetchDependencies } from '../actions/jaeger-api'; + +type DependenciesState = { + dependencies: any[]; + loading: boolean; + error: any; +}; + +const initialState: DependenciesState = { + dependencies: [], + loading: false, + error: null, +}; + +function fetchStarted(state: DependenciesState): DependenciesState { + return { ...state, loading: true }; +} + +function fetchDepsDone(state: DependenciesState, { payload }: any): DependenciesState { + return { ...state, dependencies: payload.data, loading: false }; +} + +function fetchDepsErred(state: DependenciesState, { payload: error }: any): DependenciesState { + return { ...state, error, dependencies: [], loading: false }; +} + +export default handleActions( + { + [`${fetchDependencies}_PENDING`]: fetchStarted, + [`${fetchDependencies}_FULFILLED`]: fetchDepsDone, + [`${fetchDependencies}_REJECTED`]: fetchDepsErred, + }, + initialState +); diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/embedded.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/embedded.ts new file mode 100644 index 0000000000..cf45b49c73 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/embedded.ts @@ -0,0 +1,12 @@ +import _get from 'lodash/get'; + +import { EmbeddedState } from '../types/embedded'; +import { getEmbeddedState } from '../utils/embedded-url'; + +export default function embeddedConfig(state: EmbeddedState | undefined) { + if (state === undefined) { + const search = _get(window, 'location.search'); + return search ? getEmbeddedState(search) : null; + } + return state; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/index.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/index.ts new file mode 100644 index 0000000000..9a10244b9a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/index.ts @@ -0,0 +1,16 @@ +import { Reducer } from 'redux'; +import config from './config'; +import dependencies from './dependencies'; +import embedded from './embedded'; +import services from './services'; +import trace from './trace'; + +const reducers: Record> = { + config, + dependencies, + embedded, + services, + trace, +}; + +export default reducers; diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/services.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/services.ts new file mode 100644 index 0000000000..32b69cd07f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/services.ts @@ -0,0 +1,111 @@ +import { handleActions } from 'redux-actions'; + +import { + fetchServices, + fetchServiceOperations as fetchOps, + fetchServiceServerOps as fetchServerOps, +} from '../actions/jaeger-api'; +import { localeStringComparator } from '../utils/sort'; + +/** + * State for managing services and their operations. + * + * Loading strategy: + * - `services`: Loaded once when the page loads (null indicates not yet loaded) + * - `operationsForService`: Cached incrementally as users select services + * - `serverOpsForService`: Cached incrementally as users select services + * + * Operations are fetched on-demand when a service is selected and cached in the state. + * Once loaded, they remain available until the page is refreshed or state is reset. + */ +type ServicesState = { + error: any; + loading: boolean; + /** Cache of operations per service, keyed by service name */ + operationsForService: Record; + /** Cache of server operations per service, keyed by service name */ + serverOpsForService: Record; + /** List of all services (null = not yet loaded) */ + services: string[] | null; +}; + +const initialState: ServicesState = { + error: null, + loading: false, + operationsForService: {}, + serverOpsForService: {}, + // `services` initial value of `null` indicates they haven't yet been loaded + services: null, +}; + +function fetchStarted(state: ServicesState): ServicesState { + return { ...state, loading: true }; +} + +function fetchServicesDone(state: ServicesState, { payload }: any): ServicesState { + const services = payload.data || []; + services.sort(localeStringComparator); + return { ...state, services, error: null, loading: false }; +} + +function fetchServicesErred(state: ServicesState, { payload: error }: any): ServicesState { + return { ...state, error, loading: false, services: [] }; +} + +function fetchServerOpsStarted(state: ServicesState, { meta: { serviceName } }: any): ServicesState { + const serverOpsForService = { + ...state.operationsForService, + [serviceName]: [], + }; + return { ...state, serverOpsForService }; +} + +function fetchServerOpsDone( + state: ServicesState, + { meta: { serviceName }, payload: { data: serverOpStructs } }: any +): ServicesState { + if (!Array.isArray(serverOpStructs)) return state; + + const serverOpsForService = { + ...state.operationsForService, + [serviceName]: serverOpStructs.map(({ name }: any) => name).sort(localeStringComparator), + }; + return { ...state, serverOpsForService }; +} + +function fetchOpsStarted(state: ServicesState, { meta: { serviceName } }: any): ServicesState { + const operationsForService = { + ...state.operationsForService, + [serviceName]: [], + }; + return { ...state, operationsForService }; +} + +function fetchOpsDone(state: ServicesState, { meta, payload }: any): ServicesState { + const { data: operations } = payload; + if (Array.isArray(operations)) { + operations.sort(localeStringComparator); + } + const operationsForService = { + ...state.operationsForService, + [meta.serviceName]: operations || [], + }; + return { ...state, operationsForService }; +} + +// TODO(joe): fetchOpsErred + +export default handleActions( + { + [`${fetchServices}_PENDING`]: fetchStarted, + [`${fetchServices}_FULFILLED`]: fetchServicesDone, + [`${fetchServices}_REJECTED`]: fetchServicesErred, + + [`${fetchServerOps}_PENDING`]: fetchServerOpsStarted, + [`${fetchServerOps}_FULFILLED`]: fetchServerOpsDone, + + [`${fetchOps}_PENDING`]: fetchOpsStarted, + [`${fetchOps}_FULFILLED`]: fetchOpsDone, + }, + initialState +); diff --git a/app/vmui/packages/jaeger-ui-lite/src/reducers/trace.ts b/app/vmui/packages/jaeger-ui-lite/src/reducers/trace.ts new file mode 100644 index 0000000000..1c6c579db7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/reducers/trace.ts @@ -0,0 +1,170 @@ +import _isEqual from 'lodash/isEqual'; +import { handleActions } from 'redux-actions'; + +import { fetchTrace, fetchMultipleTraces, searchTraces } from '../actions/jaeger-api'; +import { fetchedState } from '../constants'; +import transformTraceData from '../model/transform-trace-data'; + +type TraceState = { + traces: Record; + search: { + query: any; + results: string[]; + state?: string; + error?: any; + }; + rawTraces?: any[]; +}; + +const initialState: TraceState = { + traces: {}, + search: { + query: null, + results: [], + }, +}; + +function fetchTraceStarted(state: TraceState, { meta }: any): TraceState { + const { id } = meta; + const traces = { ...state.traces, [id]: { id, state: fetchedState.LOADING } }; + return { ...state, traces }; +} + +function fetchTraceDone(state: TraceState, { meta, payload }: any): TraceState { + const { id } = meta; + const data = transformTraceData(payload.data[0]); + let trace: any; + if (!data) { + trace = { id, state: fetchedState.ERROR, error: new Error('Invalid trace data recieved.') }; + } else { + trace = { data, id, state: fetchedState.DONE }; + } + const traces = { ...state.traces, [id]: trace }; + return { ...state, traces }; +} + +function fetchTraceErred(state: TraceState, { meta, payload }: any): TraceState { + const { id } = meta; + const trace = { id, error: payload, state: fetchedState.ERROR }; + const traces = { ...state.traces, [id]: trace }; + return { ...state, traces }; +} + +function fetchMultipleTracesStarted(state: TraceState, { meta }: any): TraceState { + const { ids } = meta; + const traces = { ...state.traces }; + ids.forEach((id: string) => { + traces[id] = { id, state: fetchedState.LOADING }; + }); + return { ...state, traces }; +} + +function fetchMultipleTracesDone(state: TraceState, { payload }: any): TraceState { + const traces = { ...state.traces }; + payload.data.forEach((raw: any) => { + const data = transformTraceData(raw)!; + traces[data.traceID] = { data, id: data.traceID, state: fetchedState.DONE }; + }); + if (payload.errors) { + payload.errors.forEach((err: any) => { + const { msg, traceID } = err; + const error = new Error(`Error: ${msg} - ${traceID}`); + traces[traceID] = { error, id: traceID, state: fetchedState.ERROR }; + }); + } + return { ...state, traces }; +} + +function fetchMultipleTracesErred(state: TraceState, { meta, payload }: any): TraceState { + const { ids } = meta; + const traces = { ...state.traces }; + const error = payload; + ids.forEach((id: string) => { + traces[id] = { error, id, state: fetchedState.ERROR }; + }); + return { ...state, traces }; +} + +function fetchSearchStarted(state: TraceState, { meta }: any): TraceState { + const { query } = meta; + const search = { + query, + results: [], + state: fetchedState.LOADING, + }; + return { ...state, search }; +} + +function searchDone(state: TraceState, { meta, payload }: any): TraceState { + if (!_isEqual(state.search.query, meta.query)) { + return state; + } + const payloadData = payload.data; + const processed = payloadData.map(transformTraceData); + const resultTraces: Record = {}; + const results: string[] = []; + for (let i = 0; i < processed.length; i++) { + const data = processed[i]; + const id = data.traceID; + resultTraces[id] = { data, id, state: fetchedState.DONE }; + results.push(id); + } + const traces = { ...state.traces, ...resultTraces }; + const search = { ...state.search, results, state: fetchedState.DONE }; + return { ...state, search, traces, rawTraces: payloadData }; +} + +function searchErred(state: TraceState, { meta, payload }: any): TraceState { + if (!_isEqual(state.search.query, meta.query)) { + return state; + } + const search = { ...state.search, error: payload, results: [], state: fetchedState.ERROR }; + return { ...state, search }; +} + +function loadJsonStarted(state: TraceState): TraceState { + const { search } = state; + return { ...state, search: { ...search, state: fetchedState.LOADING } }; +} + +function loadJsonDone(state: TraceState, { payload }: any): TraceState { + try { + const processed = payload.data.map(transformTraceData); + const resultTraces: Record = {}; + const results = new Set(state.search.results); + for (let i = 0; i < processed.length; i++) { + const data = processed[i]; + const id = data.traceID; + resultTraces[id] = { data, id, state: fetchedState.DONE }; + results.add(id); + } + const traces = { ...state.traces, ...resultTraces }; + const search = { ...state.search, results: Array.from(results), state: fetchedState.DONE }; + return { ...state, search, traces }; + } catch (error) { + const search = { ...state.search, error, results: [], state: fetchedState.ERROR }; + return { ...state, search }; + } +} + +function loadJsonErred(state: TraceState, { payload }: any): TraceState { + const search = { ...state.search, error: payload, results: [], state: fetchedState.ERROR }; + return { ...state, search }; +} + +export default handleActions( + { + [`${fetchTrace}_PENDING`]: fetchTraceStarted, + [`${fetchTrace}_FULFILLED`]: fetchTraceDone, + [`${fetchTrace}_REJECTED`]: fetchTraceErred, + + [`${fetchMultipleTraces}_PENDING`]: fetchMultipleTracesStarted, + [`${fetchMultipleTraces}_FULFILLED`]: fetchMultipleTracesDone, + [`${fetchMultipleTraces}_REJECTED`]: fetchMultipleTracesErred, + + [`${searchTraces}_PENDING`]: fetchSearchStarted, + [`${searchTraces}_FULFILLED`]: searchDone, + [`${searchTraces}_REJECTED`]: searchErred, + }, + initialState +); diff --git a/app/vmui/packages/jaeger-ui-lite/src/site-prefix.ts b/app/vmui/packages/jaeger-ui-lite/src/site-prefix.ts new file mode 100644 index 0000000000..51cdba825a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/site-prefix.ts @@ -0,0 +1,22 @@ +// Per the resolution of https://github.com/jaegertracing/jaeger-ui/issues/42, +// package.json#homepage is set to "." and the document MUST have a +// element to define a usable base URL. +import { getAppEnvironment } from './utils/constants'; + +const baseNode = document.querySelector('base'); +// if (!baseNode && getAppEnvironment() !== 'test') { +// throw new Error(' element not found'); +// } + +if (!baseNode) { + console.warn('[jaeger-ui] not found, fallback to "/"'); +} + +const sitePrefix = baseNode ? baseNode.href : `${globalThis.location.origin}/`; + +// Configure the webpack publicPath to match the : +// https://webpack.js.org/guides/public-path/#on-the-fly + +window.__webpack_public_path__ = sitePrefix; + +export default sitePrefix; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/TNil.ts b/app/vmui/packages/jaeger-ui-lite/src/types/TNil.ts new file mode 100644 index 0000000000..7f46464094 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/TNil.ts @@ -0,0 +1,3 @@ +type TNil = null | undefined; + +export default TNil; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/TTraceDiffState.ts b/app/vmui/packages/jaeger-ui-lite/src/types/TTraceDiffState.ts new file mode 100644 index 0000000000..6014aa7394 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/TTraceDiffState.ts @@ -0,0 +1,9 @@ +import TNil from './TNil'; + +type TTraceDiffState = { + a?: string | TNil; + b?: string | TNil; + cohort: string[]; +}; + +export default TTraceDiffState; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/TTraceTimeline.ts b/app/vmui/packages/jaeger-ui-lite/src/types/TTraceTimeline.ts new file mode 100644 index 0000000000..d5ee48aa14 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/TTraceTimeline.ts @@ -0,0 +1,13 @@ +import DetailState from '../components/TracePage/TraceTimelineViewer/SpanDetail/DetailState'; +import TNil from './TNil'; + +type TTraceTimeline = { + childrenHiddenIDs: Set; + detailStates: Map; + hoverIndentGuideIds: Set; + shouldScrollToFirstUiFindMatch: boolean; + spanNameColumnWidth: number; + traceID: string | TNil; +}; + +export default TTraceTimeline; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/api-error.ts b/app/vmui/packages/jaeger-ui-lite/src/types/api-error.ts new file mode 100644 index 0000000000..d55947fc69 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/api-error.ts @@ -0,0 +1,10 @@ +export type ApiError = + | string + | { + message: string; + httpStatus?: number; + httpStatusText?: string; + httpUrl?: string; + httpQuery?: string; + httpBody?: string; + }; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/archive.ts b/app/vmui/packages/jaeger-ui-lite/src/types/archive.ts new file mode 100644 index 0000000000..3624a6f39f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/archive.ts @@ -0,0 +1,16 @@ +import { ApiError } from './api-error'; + +export type LoadingTraceArchive = { isLoading: true }; +export type SuccessfulTraceArchive = { isAcknowledged: false; isArchived: true }; +export type ErrorTraceArchive = { error: ApiError; isAcknowledged: false; isArchived: false; isError: true }; +export type AcknowledgedTraceArchive = Omit & { + isAcknowledged: true; +}; + +export type TraceArchive = + | LoadingTraceArchive + | SuccessfulTraceArchive + | ErrorTraceArchive + | AcknowledgedTraceArchive; + +export type TracesArchive = Record; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/config.ts b/app/vmui/packages/jaeger-ui-lite/src/types/config.ts new file mode 100644 index 0000000000..358d746cc3 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/config.ts @@ -0,0 +1,208 @@ +import { TPathAgnosticDecorationSchema } from '../model/path-agnostic-decorations/types'; +import { IWebAnalyticsFunc } from './tracking'; +import { TNil } from '.'; + +export type ConfigMenuItem = { + label: string; + url?: string; + anchorTarget?: '_self' | '_blank' | '_parent' | '_top'; +}; + +export type ConfigMenuGroup = { + label: string; + items: readonly ConfigMenuItem[]; +}; + +export type TScript = { + text: string; + type: 'inline'; +}; + +export type LinkPatternsConfig = { + // type defines the entity that the pattern applies to. + // 'traces' patterns apply to the whole trace, and have access to 'traceID' value. + // Other patterns apply to tags at different levels. They have access to the value + // of the respective tag, for example: + // "linkPatterns": [{ + // "type": "process", + // "key": "jaeger.version", + // "url": "https://github.com/jaegertracing/jaeger-client-java/releases/tag/#{jaeger.version}", + // "text": "Information about Jaeger SDK release #{jaeger.version}" + // }] + type: 'process' | 'tags' | 'logs' | 'traces'; + // key of the tag for tag-level patterns. + key?: string; + // url of the link, with variable extrapoliation, e.g. "#{jaeger.version}". + url: string; + // tooltip of the link, with variable extrapoliation, e.g. "#{jaeger.version}". + text: string; +}; + +export type MonitorEmptyStateConfig = { + mainTitle?: string; + subTitle?: string; + description?: string; + button?: { + text?: string; + onClick?: () => void; + }; + info?: string; + alert?: { + message?: string; + type?: 'success' | 'info' | 'warning' | 'error'; + }; +}; + +export type MonitorConfig = { + menuEnabled?: boolean; + emptyState?: MonitorEmptyStateConfig; + docsLink?: string; +}; + +export type TraceGraphConfig = { + // layoutManagerMemory controls the total memeory available for the GraphViz + // Emscripten module instance. The value should be a power of two. + // The default of 16MB should be sufficient for most cases — only consider + // using a larger number if you run into the error "Cannot enlarge memory arrays". + // See https://github.com/jaegertracing/jaeger-ui/issues/1249 for background + layoutManagerMemory?: number; +}; + +export type StorageCapabilities = { + // archiveStorage indicates whether the query service supports archive storage. + archiveStorage?: boolean; +}; + +// Default values are provided in packages/jaeger-ui/src/constants/default-config.tsx +export type Config = { + // + // archiveEnabled enables the Archive Trace button in the trace view. + // Requires Query Service to be configured with "archive" storage backend. + archiveEnabled?: boolean; + + // criticalPath enables to show the criticalPath of each span in a trace view. + criticalPathEnabled: boolean; + + // dependencies controls the behavior of System Architecture tab. + dependencies?: { + // menuEnabled enables or disables the System Architecture tab. + menuEnabled?: boolean; + + // dagMaxNumServices defines the maximum number of services allowed + // before the DAG dependency view is disabled. Too many services + // cause the DAG view to be non-responsive. + dagMaxNumServices?: number; + }; + + // menu controls the dropdown menu in the top-right corner of the UI. + // When populated, this element completely overrides the default menu. + menu: readonly (ConfigMenuGroup | ConfigMenuItem)[]; + + // search section controls some aspects of the Search panel. + search?: { + // maxLookback controls how far back in time the search may apply. + // By default the Lookback dropdown contains values from "last hour" + // to "last 2 days". Setting maxLookback to a shorter time range, + // such as "6h" disables the longer ranges. + maxLookback: { + // label to be displayed in the search form dropdown, e.g. "Last 2 days". + label: string; + + // The value submitted in the search query if the label is selected. + // Examples: "6h", "2d". + value: string; + }; + // maxLimit configures the "search depth" parameter. + // The interpretation of search depth varies between different backends. + maxLimit: number; + + // adjustEndTime shifts the search end time back by the specified duration. + // This helps avoid incomplete traces that may still be receiving spans. + // When set, the UI will show "(adjusted)" next to the lookback dropdown. + // Examples: "1m" for 1 minute, "30s" for 30 seconds. + // Default is undefined (no adjustment). + adjustEndTime?: string; + }; + + // scripts is an array of URLs of additional JavaScript files to be loaded. + // TODO when is it useful? + scripts?: readonly TScript[]; + + // traceIdDisplayLength controls the length of the trace ID displayed in the UI. + traceIdDisplayLength?: number; + + // storage capabilities given by the query service. + storageCapabilities?: StorageCapabilities; + + // topTagPrefixes defines a set of prefixes for span tag names that are considered + // "important" and cause the matching tags to appear higher in the list of tags. + // For example, topTagPrefixes=['http.'] would cause all span tags that begin with + // "http." to be shown above all other tags. + // See https://github.com/jaegertracing/jaeger-ui/issues/218 for background. + topTagPrefixes?: readonly string[]; + + // tracking section controls the collection of usage metrics as analytics events. + // By default, Jaeger uses Google Analytics for event tracking (if enabled). + tracking?: { + // gaID is the Google Analytics account ID. + gaID: string | TNil; + + // trackErrors enables the use of Raven SDK to capture rich details + // about the exceptions and report them as analytics events. + // See https://github.com/jaegertracing/jaeger-ui/issues/39 for background. + trackErrors: boolean | TNil; + + // customWebAnalytics allows using custom implementation of event reporting, + // as an alternative to Google Analytics. + // This only works when using a JavaScript-based configuration file, + // which allows passing functions to the configuration. + // See https://github.com/jaegertracing/jaeger-ui/issues/652 for background. + customWebAnalytics: IWebAnalyticsFunc | TNil; + }; + + // linkPatterns allow customizing the display of traces with external links. + // The patterns can apply either at trace level, or at individual tag level. + // When a matching pattern is found, a link is rendered, whose URL and tooltip + // strings support variable substitution. + // A trace level link is displayed as an icon at the top of the trace view. + // A tag-level link converts the tag value into a hyperlink. + linkPatterns?: readonly LinkPatternsConfig[]; + + // monitor section controls Service Performance Monitoring tab. + monitor?: MonitorConfig; + + // traceGraph controls the trace graph under trace page + traceGraph?: TraceGraphConfig; + + // Disables the file upload control. + disableFileUploadControl: boolean; + + // Disables the json view. + disableJsonView: boolean; + + // Alters all targets of links to empty or top to + // prevent the creation of a new page. + forbidNewPage: boolean; + + // The following features are experimental / undocumented. + + deepDependencies?: { + menuEnabled?: boolean; + }; + pathAgnosticDecorations?: readonly TPathAgnosticDecorationSchema[]; + qualityMetrics?: { + menuEnabled?: boolean; + menuLabel?: string; + apiEndpoint?: string; + }; + traceDiff?: { + helpLink: string; + }; + themes: { + enabled: boolean; + }; + // useOpenTelemetryTerms determines whether the UI uses legacy Jaeger terminology + // (tags, logs, process, operation name) or OpenTelemetry terminology + // (attributes, events, resource, name). + useOpenTelemetryTerms: boolean; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/critical_path.ts b/app/vmui/packages/jaeger-ui-lite/src/types/critical_path.ts new file mode 100644 index 0000000000..99180f6459 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/critical_path.ts @@ -0,0 +1,21 @@ +import { IOtelSpan } from './otel'; + +// A section of a span that lies on the critical path +export type CriticalPathSection = { + spanID: string; + sectionStart: IOtelSpan['startTime']; + sectionEnd: IOtelSpan['endTime']; +}; + +// Critical Path Span - a minimal span type used for critical path computation +// This type contains only the fields needed for critical path algorithms +// and ensures the original trace spans are not modified during computation +export type CPSpan = { + spanID: string; + parentSpanID?: string; + isBlocking: boolean; // is this span blocking the critical path of the parent? + startTime: IOtelSpan['startTime']; + endTime: IOtelSpan['endTime']; + duration: IOtelSpan['duration']; + childSpanIDs: string[]; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/embedded.ts b/app/vmui/packages/jaeger-ui-lite/src/types/embedded.ts new file mode 100644 index 0000000000..0ed175c8c9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/embedded.ts @@ -0,0 +1,11 @@ +type EmbeddedStateV0 = { + version: 'v0'; + searchHideGraph: boolean; + timeline: { + collapseTitle: boolean; + hideMinimap: boolean; + hideSummary: boolean; + }; +}; + +export type EmbeddedState = EmbeddedStateV0; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/external-modules.d.ts b/app/vmui/packages/jaeger-ui-lite/src/types/external-modules.d.ts new file mode 100644 index 0000000000..4435eaf880 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/external-modules.d.ts @@ -0,0 +1,17 @@ +// Type declarations for third-party modules without TypeScript definitions + +declare module 'logfmt/lib/logfmt_parser' { + export function parse(input: string): Record; +} + +declare module 'logfmt/lib/stringify' { + export function stringify(data: Record): string; +} + +declare module 'store' { + const store: { + get(key: string): any; + set(key: string, value: any): void; + }; + export default store; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/hyperlink.ts b/app/vmui/packages/jaeger-ui-lite/src/types/hyperlink.ts new file mode 100644 index 0000000000..def382a731 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/hyperlink.ts @@ -0,0 +1,4 @@ +export type Hyperlink = { + url: string; + text: string; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/index.ts b/app/vmui/packages/jaeger-ui-lite/src/types/index.ts new file mode 100644 index 0000000000..e130758ec5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/index.ts @@ -0,0 +1,65 @@ +import { Router } from 'react-router-dom'; +import { Location } from 'history'; + +import { ApiError } from './api-error'; +import { TracesArchive } from './archive'; +import { Config } from './config'; +import { EmbeddedState } from './embedded'; +import { SearchQuery } from './search'; +import tNil from './TNil'; +import iWebAnalytics from './tracking'; +import { Trace } from './trace'; +import TTraceDiffState from './TTraceDiffState'; +import TTraceTimeline from './TTraceTimeline'; +import { MetricsReduxState } from './metrics'; + +export type TNil = tNil; +export type IWebAnalytics = iWebAnalytics; + +export type FetchedState = 'FETCH_DONE' | 'FETCH_ERROR' | 'FETCH_LOADING'; + +export type FetchedTrace = { + data?: T; + error?: ApiError; + id: string; + state?: FetchedState; +}; + +export type LocationState = { + fromSearch?: string; +}; + +export type ReduxState = { + archive: TracesArchive; + type: string; + config: Config; + ddg: TDdgState; + dependencies: { + dependencies: { parent: string; child: string; callCount: number }[]; + loading: boolean; + error: ApiError | TNil; + }; + embedded: EmbeddedState; + router: Router & { + location: Location; + }; + services: { + services: string[] | TNil; + serverOpsForService: Record; + operationsForService: Record; + loading: boolean; + error: ApiError | TNil; + }; + trace: { + traces: Record; + search: { + error?: ApiError; + results: string[]; + state?: FetchedState; + query?: SearchQuery; + }; + }; + traceDiff: TTraceDiffState; + traceTimeline: TTraceTimeline; + metrics: MetricsReduxState; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/metrics.ts b/app/vmui/packages/jaeger-ui-lite/src/types/metrics.ts new file mode 100644 index 0000000000..b1cf1755dd --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/metrics.ts @@ -0,0 +1,143 @@ +import { ApiError } from './api-error'; + +export type MetricsType = 'latencies' | 'calls' | 'errors'; +export type AvailableServiceMetrics = 'service_call_rate' | 'service_latencies' | 'service_error_rate'; +export type AvailableOpsMetrics = + | 'service_operation_call_rate' + | 'service_operation_latencies' + | 'service_operation_error_rate'; + +export type spanKinds = 'unspecified' | 'internal' | 'server' | 'client' | 'producer' | 'consumer'; + +export type MetricsAPIQueryParams = { + quantile: number; + groupByOperation?: boolean; + endTs: number; + lookback: number; + step: number; + ratePer: number; + spanKind: spanKinds; +}; + +export type LableObject = { + name: string; + value: string; +}; + +export type MetricPointObject = { + gaugeValue: { + doubleValue: number; + }; + timestamp: string; +}; + +export type MetricObject = { + labels: LableObject[]; + metricPoints: MetricPointObject[]; +}; + +export type MetricsAPIServiceResponseData = { + name: T; + type: 'GAUGE'; + help: string; + metrics: MetricObject[]; + quantile: U; +}; + +export type MetricsAPIOpsResponseData = { + name: T; + type: 'GAUGE'; + help: string; + metrics: MetricObject[]; + quantile: number; +}; + +export type Points = { + x: number; + y: number | null; +}; + +export type DataAvg = { + service_operation_call_rate: null | number; + service_operation_error_rate: null | number; + service_operation_latencies: null | number; +}; + +export type OpsDataPoints = { + service_operation_call_rate: Points[]; + service_operation_error_rate: Points[]; + service_operation_latencies: Points[]; + avg: DataAvg; +}; + +export type ServiceOpsMetrics = { + dataPoints: OpsDataPoints; + errRates: number; + impact: number; + latency: number; + name: string; + requests: number; + key: number; +}; + +export type ServiceMetricsObject = { + serviceName: string; + quantile: number; + max: number; + metricPoints: Points[]; +}; + +export type ServiceMetrics = { + service_latencies: null | ServiceMetricsObject[]; + service_call_rate: null | ServiceMetricsObject; + service_error_rate: null | ServiceMetricsObject; +}; + +export type MetricsReduxState = { + serviceError: { + service_latencies_50: null | ApiError; + service_latencies_75: null | ApiError; + service_latencies_95: null | ApiError; + service_call_rate: null | ApiError; + service_error_rate: null | ApiError; + }; + opsError: { + opsLatencies: null | ApiError; + opsCalls: null | ApiError; + opsErrors: null | ApiError; + }; + isATMActivated: null | boolean; + loading: boolean; + operationMetricsLoading: undefined | boolean; + serviceMetrics: ServiceMetrics | null; + serviceOpsMetrics: ServiceOpsMetrics[] | undefined; +}; + +export enum PromiseStatus { + fulfilled = 'fulfilled', + rejected = 'rejected', +} + +export type PromiseFulfilledResult = { + status: PromiseStatus.fulfilled; + value: T; +}; + +export type PromiseRejectedResult = { + status: PromiseStatus.rejected; + reason: ApiError; +}; + +export type FetchedAllServiceMetricsResponse = [ + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, +]; + +export type FetchAggregatedServiceMetricsResponse = [ + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, + PromiseFulfilledResult> | PromiseRejectedResult, +]; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/otel.ts b/app/vmui/packages/jaeger-ui-lite/src/types/otel.ts new file mode 100644 index 0000000000..5e4fe51da5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/otel.ts @@ -0,0 +1,119 @@ +import { Microseconds } from './units'; + +export enum SpanKind { + INTERNAL = 'INTERNAL', + SERVER = 'SERVER', + CLIENT = 'CLIENT', + PRODUCER = 'PRODUCER', + CONSUMER = 'CONSUMER', +} + +export enum StatusCode { + UNSET = 'UNSET', + OK = 'OK', + ERROR = 'ERROR', +} + +export type AttributeValue = + | string + | number + | boolean + | Array + | { [key: string]: AttributeValue } + | Uint8Array; + +export interface IAttribute { + key: string; + value: AttributeValue; +} + +export interface IResource { + attributes: IAttribute[]; // includes service.name, etc. + serviceName: string; // convenience: attributes['service.name'] +} + +export interface IScope { + name: string; + version?: string; + attributes?: IAttribute[]; +} + +export interface IEvent { + timestamp: Microseconds; + name: string; + attributes: IAttribute[]; +} + +export interface ILink { + traceID: string; + spanID: string; + attributes: IAttribute[]; + span?: IOtelSpan; +} + +export interface IStatus { + code: StatusCode; + message?: string; +} + +export interface IOtelSpan { + // Identity + traceID: string; + spanID: string; + parentSpanID?: string; + parentSpan?: IOtelSpan; + + // Naming & Classification + name: string; + kind: SpanKind; + + // Timing + startTime: Microseconds; + endTime: Microseconds; + duration: Microseconds; + + // Core Data + attributes: IAttribute[]; + events: IEvent[]; + links: ILink[]; + status: IStatus; + + // Context + resource: IResource; + instrumentationScope: IScope; + + // Derived properties + depth: number; + hasChildren: boolean; + childSpans: ReadonlyArray; + relativeStartTime: Microseconds; // microseconds since trace start + + // Inverse links to spans that reference this span via their outbound Links + inboundLinks: ILink[]; + + warnings: ReadonlyArray | null; +} + +export interface IOtelTrace { + traceID: string; + spans: ReadonlyArray; + + // Some trace-level convenience properties + duration: Microseconds; + startTime: Microseconds; + endTime: Microseconds; + traceName: string; + tracePageTitle: string; + traceEmoji: string; + services: ReadonlyArray<{ name: string; numberOfSpans: number }>; + + // Optimized data structures - created once during trace transformation + spanMap: ReadonlyMap; + rootSpans: ReadonlyArray; + + // Number of orphan spans (spans with parent references to spans not in the trace) + orphanSpanCount: number; + + // Helper methods + hasErrors(): boolean; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/search.ts b/app/vmui/packages/jaeger-ui-lite/src/types/search.ts new file mode 100644 index 0000000000..644d40c0b2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/search.ts @@ -0,0 +1,13 @@ +import { TNil } from '.'; + +export type SearchQuery = { + end: number | string; + limit: number | string; + lookback: string; + maxDuration: null | string; + minDuration: null | string; + operation: string | TNil; + service: string; + start: number | string; + tags: string | TNil; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/trace.ts b/app/vmui/packages/jaeger-ui-lite/src/types/trace.ts new file mode 100644 index 0000000000..15dc199a82 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/trace.ts @@ -0,0 +1,82 @@ +/** + * All timestamps are in microseconds + */ + +import { IOtelTrace } from './otel'; + +export type KeyValuePair = { + key: string; + value: ValueType; +}; + +export type Log = { + timestamp: number; + fields: ReadonlyArray; +}; + +export type Process = { + serviceName: string; + tags: ReadonlyArray; +}; + +export type SpanReference = { + refType: 'CHILD_OF' | 'FOLLOWS_FROM'; + + span: Span | null | undefined; + spanID: string; + traceID: string; +}; + +export type SpanData = { + spanID: string; + traceID: string; + processID: string; + operationName: string; + startTime: number; + duration: number; + tags?: ReadonlyArray; + logs?: ReadonlyArray; + references?: ReadonlyArray; + warnings?: ReadonlyArray | null; +}; + +export type Span = SpanData & { + tags: NonNullable; + logs: NonNullable; + references: NonNullable; + warnings: NonNullable; + + depth: number; + relativeStartTime: number; + process: Process; + + hasChildren: boolean; + childSpans: ReadonlyArray; + subsidiarilyReferencedBy: ReadonlyArray; +}; + +export type TraceData = { + processes: Record; + traceID: string; +}; + +export type Trace = TraceData & { + duration: IOtelTrace['duration']; + endTime: IOtelTrace['endTime']; + spans: ReadonlyArray; + startTime: IOtelTrace['startTime']; + traceName: string; + tracePageTitle: string; + traceEmoji: string; + services: ReadonlyArray<{ name: string; numberOfSpans: number }>; + // Number of orphan spans (spans referencing parent spans that don't exist in the trace) + orphanSpanCount?: number; + + // Optimized data structures - created once during trace transformation + spanMap: ReadonlyMap; + rootSpans: ReadonlyArray; + + // OTEL facade - lazy-initialized and memoized + _otelFacade?: IOtelTrace; + asOtelTrace(): IOtelTrace; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/tracking.ts b/app/vmui/packages/jaeger-ui-lite/src/types/tracking.ts new file mode 100644 index 0000000000..3e6020caa2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/tracking.ts @@ -0,0 +1,21 @@ +import { BrowserClient } from '@sentry/browser'; +import { TNil } from '.'; +import { Config } from './config'; + +export interface IWebAnalyticsFunc { + (config: Config, versionShort: string, versionLong: string): IWebAnalytics; +} + +export default interface IWebAnalytics { + init: () => void; + context: boolean | typeof BrowserClient | null; + isEnabled: () => boolean; + trackPageView: (pathname: string, search: string | TNil) => void; + trackError: (description: string) => void; + trackEvent: ( + category: string, + action: string, + labelOrValue?: string | number | TNil, + value?: number | TNil + ) => void; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/types/units.ts b/app/vmui/packages/jaeger-ui-lite/src/types/units.ts new file mode 100644 index 0000000000..6893c757a0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/types/units.ts @@ -0,0 +1,5 @@ +/** + * A branded type representing time values in microseconds. + * This provides type safety to ensure time values are not confused with other numeric values. + */ +export type Microseconds = number & { readonly __brand: unique symbol }; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/DraggableManager.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/DraggableManager.ts new file mode 100644 index 0000000000..c2fbfbd6e9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/DraggableManager.ts @@ -0,0 +1,211 @@ +import _get from 'lodash/get'; +import type React from 'react'; + +import EUpdateTypes from './EUpdateTypes'; +import { DraggableBounds, DraggingUpdate } from './types'; +import { TNil } from '../../types'; + +const LEFT_MOUSE_BUTTON = 0; + +type DraggableManagerOptions = { + getBounds: (tag: string | TNil) => DraggableBounds; + onMouseEnter?: (update: DraggingUpdate) => void; + onMouseLeave?: (update: DraggingUpdate) => void; + onMouseMove?: (update: DraggingUpdate) => void; + onDragStart?: (update: DraggingUpdate) => void; + onDragMove?: (update: DraggingUpdate) => void; + onDragEnd?: (update: DraggingUpdate) => void; + resetBoundsOnResize?: boolean; + tag?: string; +}; + +export default class DraggableManager { + // cache the last known DraggableBounds (invalidate via `#resetBounds()) + _bounds: DraggableBounds | TNil; + _isDragging: boolean; + // optional callbacks for various dragging events + _onMouseEnter: ((update: DraggingUpdate) => void) | TNil; + _onMouseLeave: ((update: DraggingUpdate) => void) | TNil; + _onMouseMove: ((update: DraggingUpdate) => void) | TNil; + _onDragStart: ((update: DraggingUpdate) => void) | TNil; + _onDragMove: ((update: DraggingUpdate) => void) | TNil; + _onDragEnd: ((update: DraggingUpdate) => void) | TNil; + // whether to reset the bounds on window resize + _resetBoundsOnResize: boolean; + + /** + * Get the `DraggableBounds` for the current drag. The returned value is + * cached until either `#resetBounds()` is called or the window is resized + * (assuming `_resetBoundsOnResize` is `true`). The `DraggableBounds` defines + * the range the current drag can span to. It also establishes the left offset + * to adjust `clientX` by (from the `MouseEvent`s). + */ + getBounds: (tag: string | TNil) => DraggableBounds; + + // convenience data + tag: string | TNil; + + // handlers for integration with DOM elements + handleMouseEnter: (event: React.MouseEvent) => void; + handleMouseMove: (event: React.MouseEvent) => void; + handleMouseLeave: (event: React.MouseEvent) => void; + handleMouseDown: (event: React.MouseEvent) => void; + + constructor({ getBounds, tag, resetBoundsOnResize = true, ...rest }: DraggableManagerOptions) { + this.handleMouseDown = this._handleDragEvent; + this.handleMouseEnter = this._handleMinorMouseEvent; + this.handleMouseMove = this._handleMinorMouseEvent; + this.handleMouseLeave = this._handleMinorMouseEvent; + + this.getBounds = getBounds; + this.tag = tag; + this._isDragging = false; + this._bounds = undefined; + this._resetBoundsOnResize = Boolean(resetBoundsOnResize); + if (this._resetBoundsOnResize) { + window.addEventListener('resize', this.resetBounds); + } + this._onMouseEnter = rest.onMouseEnter; + this._onMouseLeave = rest.onMouseLeave; + this._onMouseMove = rest.onMouseMove; + this._onDragStart = rest.onDragStart; + this._onDragMove = rest.onDragMove; + this._onDragEnd = rest.onDragEnd; + } + + _getBounds(): DraggableBounds { + if (!this._bounds) { + this._bounds = this.getBounds(this.tag); + } + return this._bounds; + } + + _getPosition(clientX: number) { + const { clientXLeft, maxValue, minValue, width } = this._getBounds(); + let x = clientX - clientXLeft; + let value = x / width; + if (minValue != null && value < minValue) { + value = minValue; + x = minValue * width; + } else if (maxValue != null && value > maxValue) { + value = maxValue; + x = maxValue * width; + } + return { value, x }; + } + + _stopDragging() { + window.removeEventListener('mousemove', this._handleDragEvent); + window.removeEventListener('mouseup', this._handleDragEvent); + const style = _get(document, 'body.style'); + if (style) { + style.removeProperty('userSelect'); + } + this._isDragging = false; + } + + isDragging() { + return this._isDragging; + } + + dispose() { + if (this._isDragging) { + this._stopDragging(); + } + if (this._resetBoundsOnResize) { + window.removeEventListener('resize', this.resetBounds); + } + this._bounds = undefined; + this._onMouseEnter = undefined; + this._onMouseLeave = undefined; + this._onMouseMove = undefined; + this._onDragStart = undefined; + this._onDragMove = undefined; + this._onDragEnd = undefined; + } + + resetBounds = () => { + this._bounds = undefined; + }; + + _handleMinorMouseEvent = (event: React.MouseEvent) => { + const { button, clientX, type: eventType } = event; + if (this._isDragging || button !== LEFT_MOUSE_BUTTON) { + return; + } + let type: EUpdateTypes | null = null; + let handler: ((update: DraggingUpdate) => void) | TNil; + if (eventType === 'mouseenter') { + type = EUpdateTypes.MouseEnter; + handler = this._onMouseEnter; + } else if (eventType === 'mouseleave') { + type = EUpdateTypes.MouseLeave; + handler = this._onMouseLeave; + } else if (eventType === 'mousemove') { + type = EUpdateTypes.MouseMove; + handler = this._onMouseMove; + } else { + throw new Error(`invalid event type: ${eventType}`); + } + if (!handler) { + return; + } + const { value, x } = this._getPosition(clientX); + handler({ + event, + type, + value, + x, + manager: this, + tag: this.tag, + }); + }; + + _handleDragEvent = (event: MouseEvent | React.MouseEvent) => { + const { button, clientX, type: eventType } = event; + let type: EUpdateTypes | null = null; + let handler: ((update: DraggingUpdate) => void) | TNil; + if (eventType === 'mousedown') { + if (this._isDragging || button !== LEFT_MOUSE_BUTTON) { + return; + } + window.addEventListener('mousemove', this._handleDragEvent); + window.addEventListener('mouseup', this._handleDragEvent); + const style = _get(document, 'body.style'); + if (style) { + style.userSelect = 'none'; + } + this._isDragging = true; + + type = EUpdateTypes.DragStart; + handler = this._onDragStart; + } else if (eventType === 'mousemove') { + if (!this._isDragging) { + return; + } + type = EUpdateTypes.DragMove; + handler = this._onDragMove; + } else if (eventType === 'mouseup') { + if (!this._isDragging) { + return; + } + this._stopDragging(); + type = EUpdateTypes.DragEnd; + handler = this._onDragEnd; + } else { + throw new Error(`invalid event type: ${eventType}`); + } + if (!handler) { + return; + } + const { value, x } = this._getPosition(clientX); + handler({ + event, + type, + value, + x, + manager: this, + tag: this.tag, + }); + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/EUpdateTypes.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/EUpdateTypes.ts new file mode 100644 index 0000000000..3ae4033483 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/EUpdateTypes.ts @@ -0,0 +1,22 @@ +// // export default { +// const updateTypes = { +// DRAG_END: 'DRAG_END', +// DRAG_MOVE: 'DRAG_MOVE', +// DRAG_START: 'DRAG_START', +// MOUSE_ENTER: 'MOUSE_ENTER', +// MOUSE_LEAVE: 'MOUSE_LEAVE', +// MOUSE_MOVE: 'MOUSE_MOVE', +// }; + +// const typeUpdateTypes = updateTypes as { [K in keyof typeof updateTypes]: K }; + +enum EUpdateTypes { + DragEnd = 'DragEnd', + DragMove = 'DragMove', + DragStart = 'DragStart', + MouseEnter = 'MouseEnter', + MouseLeave = 'MouseLeave', + MouseMove = 'MouseMove', +} + +export default EUpdateTypes; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/README.md b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/README.md new file mode 100644 index 0000000000..e21236f56f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/README.md @@ -0,0 +1,287 @@ +# DraggbleManager Information and Demo + +In the `src/utils/DraggableManager/demo` folder there is a small project that demonstrates the use of the `DraggableManager` utility. + +The demo contains two components: + +- `DividerDemo`, which occupies the top half of the web page +- `RegionDemo`, which occupies the bottom half of the web page, as shown in the GIF, below + +![GIF of Demo](demo/demo-ux.gif) + +## Caveat + +This DraggableManager utility does not actually "drag" anything, it does not move or drag DOM elements, it just tells us where the mouse is while the mouse is down. Primarily, it listens for `mousedown` and subsequent `mousemove` and then finally `mouseup` events. (It listens to `window` for the `mousemove` and `mouseup` events.) + +What we do with that information is up to us. This is mentioned because you need to handle the DraggableManager callbacks _to create the illusion of dragging_. + +## In brief + +DraggableManager instances provide three (and a half) conveniences: + +- Handle mouse events related to dragging. +- Maps `MouseEvent.clientX` from the [client area](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) to the local context (yielding `x` (pixels) and `value` (0 -> 1, e.g, `x/width`)). +- Maintains a sense of state in terms of whether or not the subject DOM element is being dragged. For example, it fires `onMouseMove` callbacks when not being dragged and `onDragMove` when being dragged. +- Two other minor conveniences (relating to window events) + +And, DraggableManager instances have two (or three) primary requirements: + +- Mouse events need to be piped into it +- The `getBounds()` constructor parameter must be provided +- At least some of the callbacks need to be handled + +## Conveniences + +### Handles the mouse events related to dragging + +For the purposes of handling mouse events related to the intended dragging functionality, DraggableManager instances expose the following methods (among others): + +- `handleMouseEnter` +- `handleMouseMove` +- `handleMouseLeave` +- `handleMouseDown` + +To use a DraggableManager instance, relevant mouse events should be piped to the above handlers: + +```jsx +
+
+
+``` + +Note: Not all handlers are always necessary. See "Mouse events need to be piped into it" for more details. + +### Maps the `clientX` to `x` and `value` + +`MouseEvent` (and `SyntheticMouseEvent`) events provide the [`clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) property, which generally needs some adjustments before it's useful. For instance, in the following snippet we transform `clientX` to the `x` within the `
`. The `value` is simply the `x/width` ratio, which is pretty much the percent but divided by `100`. + +```jsx +
+
{ + const { clientX, target } = event; + const { left, width } = target.getBoundingClientRect(); + const localX = clientX - left; + console.log('within the client area, x:', clientX); + console.log('within the div, x: ', localX); + console.log('position along the width: ', localX / width); + }} + /> +
+``` + +In other words, DraggableManager instances convert the data to the relevant context. (The "relevant context" is, naturally, varies... see the `getBounds()` constructor parameter below). + +### Maintains a sense of state + +The callbacks for DraggableManager instances are: + +- onMouseEnter +- onMouseLeave +- onMouseMove +- onDragStart +- onDragMove +- onDragEnd + +Implicit in the breakdown of the callbacks is the notion that `onDrag*` callbacks are fired when dragging and `onMouse*` callbacks are issued, otherwise. + +Therefore, using the DraggableManager util relieves us of the necessity of keeping track of whether we are currently dragging or not. + +### Two other minor conveniences + +When dragging starts, the util then switches over to listening to window events (`mousemove` and `mouseup`). This prevents the dragging from having strange behavior if / when the user moves the mouse anywhere on the page. + +Last but not least... + +The util listens for window resize events and makes adjustments accordingly, preventing things from going crazy (due to miscalibration) if the user resizes the window. This primary relates to the `getBounds()` constructor option (see below). + +## Requirements + +### Mouse events need to be piped into it + +In my use, DraggbaleManager instances become the receiver of the relevant mouse events instead of handlers on the React component. + +For instance, if implementing a draggable divider (see `DividerDemo.js` and the top half of the gif), only `onMouseDown` needs to be handled: + +```jsx +
+
+
+``` + +But, if implementing the ability to drag a sub-range (see `RegionDemo.js` and the bottom of demo gif), you generally want to show a vertical line at the mouse cursor until the dragging starts (`onMouseDown`), then you want to draw the region being dragged. So, the `onMouseMove`, `onMouseLeave` and `onMouseDown` handlers are necessary: + +```jsx +
+ {/* Draw visuals for the currently dragged range, otherwise empty */} +
+``` + +### `getBounds()` constructor parameter + +The crux of the conversion from `clientX` to `x` and `value` is the `getBounds()` constructor parameter. + +The function is a required constructor parameter, and it must return a `DraggableBounds` object: + +``` +type DraggableBounds = { + clientXLeft: number, + maxValue?: number, + minValue?: number, + width: number, +}; +``` + +This generally amounts to calling [`Element#getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) on the DOM element that defines the valid dragging range. + +For instance, in the `DividerDemo`, the function used is `DivideDemo#_getDraggingBounds()`: + +```js +_getDraggingBounds = (): DraggableBounds => { + if (!this._realmElm) { + throw new Error('invalid state'); + } + const { left: clientXLeft, width } = this._realmElm.getBoundingClientRect(); + return { + clientXLeft, + width, + maxValue: 0.98, + minValue: 0.02, + }; +}; +``` + +In the snippet above, `this._realmElm` is the `
` that fills the green draggable region. + +On the other hand, if you need more flexibility, this function can ignore the DOM altogether and do something else entirely. It just needs to return an object with `clientXLeft` and `width` properties, at the minimum. + +`maxValue` and `minValue` are optional and will restrict the extent of the dragging. They are in terms of `value`, not `x`. + +### The callbacks need to be handled + +Last but not least, if the callbacks are ignored, nothing happens. + +In the `DividerDemo`, we're only interested in repositioning the divider when it is dragged. We don't care about mouse related callbacks. So, only the drag related callbacks are handled. And, all of the drag callbacks are handled in the same way: we update the position of the divider. Done. See `DividerDemo#_handleDragEvent()`. + +In the other scenario, `RegionDemo`, we care about showing the red vertical line for mouse-over. This sort of indicates to the user they can click and drag, and when they drag we want to show a region that spans the current drag. So, we handle the mousemove and mouseleave callbacks along with the drag callbacks. + +The `RegionDemo` is a bit more involved, so, to break down how we handle the callbacks... First, we store the following state (in the parent element, incidentally): + +- `regionCursor` is where we draw the cursor indicator (a red vertical line, in the demo). +- `regionDragging` represents the start (at index `0`) and current position (at index `1`) of the region currently being dragged. + +``` +{ + regionCursor: ?number, + regionDragging: ?[number, number], +} +``` + +Then, we handle the callbacks as follows: + +- `onMouseMove` + - Set `regionCursor` to `value` + - This allows us to draw the red vertical line at the cursor +- `onMouseLeave` + - Set `regionCursor` to `null` + - So we know not to draw the red vertical line +- `onDragStart` + - Set `regionDragging` to `[value, value]` + - This allows us to draw the dragging region +- `onDragMove` + - Set `regionDragging` to `[regionDragging[0], value]` + - Again, for drawing the dragging region. We keep `regionDragging[0]` as-is so we always know where the drag started +- `onDragEnd` + - Set `regionDragging` to `null`, set `regionCursor` to `value` + - Setting `regionDragging` to `null` lets us know not to draw the region, and setting `regionCursor` lets us know to draw the cursor right where the user left off + +This is a contrived demo, so `onDragEnd` is kind of boring... Usually we would do something more interesting with the final `x` or `value`. + +## API + +### Constants `updateTypes` + +Used as the `type` field on `DraggingUpdate` objects. + +``` +{ + DRAG_END: 'DRAG_END', + DRAG_MOVE: 'DRAG_MOVE', + DRAG_START: 'DRAG_START', + MOUSE_ENTER: 'MOUSE_ENTER', + MOUSE_LEAVE: 'MOUSE_LEAVE', + MOUSE_MOVE: 'MOUSE_MOVE', +}; +``` + +### Type `DraggingUpdate` + +The data type issued for all callbacks. + +``` +type DraggingUpdate = { + event: SyntheticMouseEvent, + manager: DraggableManager, + tag: ?string, + type: UpdateType, + value: number, + x: number, +}; +``` + +### Type `DraggableBounds` + +The type the `getBounds()` constructor parameter must return. + +``` +type DraggableBounds = { + clientXLeft: number, + maxValue?: number, + minValue?: number, + width: number, +}; +``` + +`clientXLeft` is used to convert [`MouseEvent.clientX`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) from the client area to the dragging area. + +`maxValue` and `minValue` are in terms of `value` on the updates, e.g. they are in the range from `[0, 1]` where `0` is the far left (e.g. style `left: 0;`) end of the draggable region and `1` is the far right end (style `right: 0`). If set, they will restrict the `x` and `value` issued by the callbacks. + +`width` is used to convert `x` to `value` and is also the span on which `minValue` and `maxValue` are mapped onto when calculating `x` and `value` for issuing callbacks. + +### Constructor parameters + +``` +type DraggableManagerOptions = { + getBounds: (?string) => DraggableBounds, + onMouseEnter?: DraggingUpdate => void, + onMouseLeave?: DraggingUpdate => void, + onMouseMove?: DraggingUpdate => void, + onDragStart?: DraggingUpdate => void, + onDragMove?: DraggingUpdate => void, + onDragEnd?: DraggingUpdate => void, + resetBoundsOnResize?: boolean, + tag?: string, +}; +``` + +`getBounds()` is used to map the `clientX` to whatever the dragging context is. **It is called lazily** and the returned value is cached, until either `DraggableManager#resetBounds()` is called, the window is resized (when `resetBoundsOnResize` is `true`) or `DraggableManager#dispose()` is called. + +The callbacks are all optional. The callbacks all present the same data (`DraggingUpdate`), with the `type` field being set based on which callback is firing (e.g. `type` is `'MOUSE_ENTER'` when `onMouseEnter` is fired), and the `x` and `value` representing the last know position of the mouse cursor. + +If `resetBoundsOnResize` is `true`, the instance resets the cached `DraggableBounds` when the window is resized. + +`tag` is an optional string parameter. It is a convenience field for distinguishing different `DraggableManager` instances. If set on the constructor, it is set on every `DraggingUpdate` that is issued. + +### `DraggableManager# isDragging()` + +Returns `true` when the instance is in a dragged state, e.g. after `onDragStart` is fired and before `onDragEnd` is fired. + +### `DraggableManager# dispose()` + +Removes any event listeners attached to `window` and sets all instance properties to `undefined`. diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/index.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/index.ts new file mode 100644 index 0000000000..9063196aab --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/index.ts @@ -0,0 +1,3 @@ +export * from './types'; +export { default as EUpdateTypes } from './EUpdateTypes'; +export { default } from './DraggableManager'; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/types.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/types.ts new file mode 100644 index 0000000000..e0ff80ae0c --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/DraggableManager/types.ts @@ -0,0 +1,21 @@ +import * as React from 'react'; + +import DraggableManager from './DraggableManager'; +import EUpdateTypes from './EUpdateTypes'; +import { TNil } from '../../types'; + +export type DraggableBounds = { + clientXLeft: number; + maxValue?: number; + minValue?: number; + width: number; +}; + +export type DraggingUpdate = { + event: React.MouseEvent | MouseEvent; + manager: DraggableManager; + tag: string | TNil; + type: EUpdateTypes; + value: number; + x: number; +}; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.css b/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.css new file mode 100644 index 0000000000..b466aca173 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.css @@ -0,0 +1,4 @@ +.value-is-invalid { + background: #fff9f8; + border: 1px solid #c00; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.tsx b/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.tsx new file mode 100644 index 0000000000..0cba49e7e9 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/ValidatedFormField.tsx @@ -0,0 +1,32 @@ +import React, { useState } from 'react'; +import { Input, Popover } from 'antd'; +import cx from 'classnames'; + +import './ValidatedFormField.css'; + +export default function ValidatedFormField(props: any) { + const [blur, setOnBlur] = useState(false); + const { onChange: handleChange, validate, ...rest } = props; + + const validationResult = validate(rest.value); + const isInvalid = blur && Boolean(validationResult); + + return ( + + { + setOnBlur(true); + }} + onFocus={() => { + setOnBlur(false); + }} + type="text" + {...rest} + /> + + ); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/color-generator.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/color-generator.ts new file mode 100644 index 0000000000..59a698e632 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/color-generator.ts @@ -0,0 +1,73 @@ +/** + * Span categorical colors are defined in vars.css using the Design Token Architecture. + */ +const SPAN_COLOR_VARS = Array.from({ length: 20 }, (_, i) => `--span-color-${i + 1}`); +const SPAN_COLORS = SPAN_COLOR_VARS.map(v => `var(${v})`); + +// TS needs the precise return type +export function strToRgb(s: string): [number, number, number] { + const trimmed = s.trim(); + if (trimmed.length !== 7) { + return [0, 0, 0]; + } + const r = trimmed.slice(1, 3); + const g = trimmed.slice(3, 5); + const b = trimmed.slice(5); + return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)]; +} + +export class ColorGenerator { + colors: string[]; + cache: Map; + currentIdx: number; + + constructor(colors: string[] = SPAN_COLORS) { + this.colors = colors; + this.cache = new Map(); + this.currentIdx = 0; + } + + _getColorIndex(key: string): number { + let i = this.cache.get(key); + if (i == null) { + i = this.currentIdx; + this.cache.set(key, this.currentIdx); + this.currentIdx = ++this.currentIdx % this.colors.length; + } + return i; + } + + /** + * Will assign a color to an arbitrary key. + * If the key has been used already, it will + * use the same color. + */ + getColorByKey(key: string) { + const i = this._getColorIndex(key); + return this.colors[i]; + } + + /** + * Retrieve the RGB values associated with a key. Adds the key and associates + * it with a color if the key is not recognized. + * @return {number[]} An array of three ints [0, 255] representing a color. + */ + getRgbColorByKey(key: string): [number, number, number] { + const i = this._getColorIndex(key); + if (typeof window !== 'undefined' && typeof window.getComputedStyle === 'function') { + const hex = window.getComputedStyle(document.documentElement).getPropertyValue(SPAN_COLOR_VARS[i]); + if (hex) { + return strToRgb(hex); + } + } + // Fallback or default if window is not available (e.g. tests or server-side) + return [0, 0, 0]; + } + + clear() { + this.cache.clear(); + this.currentIdx = 0; + } +} + +export default new ColorGenerator(); diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-config.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-config.ts new file mode 100644 index 0000000000..861416c3c4 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-config.ts @@ -0,0 +1,52 @@ +import _get from 'lodash/get'; +import memoizeOne from 'memoize-one'; + +import { Config } from '../../types/config'; +import processDeprecation from './process-deprecation'; +import defaultConfig, { deprecations, mergeFields } from '../../constants/default-config'; + +function getUiConfig() { + const getter = window.getJaegerUiConfig; + if (typeof getter !== 'function') { + console.warn('Embedded config not available'); + return { ...defaultConfig }; + } + return getter(); +} + +function getCapabilities() { + const getter = window.getJaegerStorageCapabilities; + const capabilities = typeof getter === 'function' ? getter() : null; + return capabilities ?? defaultConfig.storageCapabilities; +} + +/** + * Merge the embedded config from the query service (if present) with the + * default config from `../../constants/default-config`. + */ +const getConfig = memoizeOne(function getConfig(): Config { + const capabilities = getCapabilities(); + + const embedded = getUiConfig(); + if (!embedded) { + return { ...defaultConfig, storageCapabilities: capabilities }; + } + // check for deprecated config values + if (Array.isArray(deprecations)) { + deprecations.forEach(deprecation => processDeprecation(embedded, deprecation, true)); + } + const rv = { ...defaultConfig, ...embedded }; + // mergeFields config values should be merged instead of fully replaced + mergeFields.forEach(key => { + if (embedded && typeof embedded[key] === 'object' && embedded[key] !== null) { + rv[key] = { ...defaultConfig[key], ...embedded[key] }; + } + }); + return { ...rv, storageCapabilities: capabilities }; +}); + +export default getConfig; + +export function getConfigValue(path: string) { + return _get(getConfig(), path); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-target.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-target.ts new file mode 100644 index 0000000000..7cc685326f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/config/get-target.ts @@ -0,0 +1,9 @@ +import { getConfigValue } from './get-config'; + +export function getTargetEmptyOrBlank() { + return getConfigValue('forbidNewPage') ? '' : '_blank'; +} + +export function getTargetBlankOrTop() { + return getConfigValue('forbidNewPage') ? '_top' : '_blank'; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-deprecation.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-deprecation.ts new file mode 100644 index 0000000000..e3f740b179 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-deprecation.ts @@ -0,0 +1,52 @@ +import _get from 'lodash/get'; +import _has from 'lodash/has'; +import _set from 'lodash/set'; +import _unset from 'lodash/unset'; + +interface IDeprecation { + formerKey: string; + currentKey: string; +} + +/** + * Processes a deprecated config property with respect to a configuration. + * NOTE: This mutates `config`. + * + * If the deprecated config property is found to be set on `config`, it is + * moved to the new config property unless a conflicting setting exists. If + * `issueWarning` is `true`, warnings are issues when: + * + * - The deprecated config property is found to be set on `config` + * - The value at the deprecated config property is moved to the new property + * - The value at the deprecated config property is ignored in favor of the value at the new property + */ +export default function processDeprecation(config: object, deprecation: IDeprecation, issueWarning: boolean) { + const { formerKey, currentKey } = deprecation; + if (_has(config, formerKey)) { + let isTransfered = false; + let isIgnored = false; + if (!_has(config, currentKey)) { + // the new key is not set so transfer the value at the old key + const value = _get(config, formerKey); + _set(config, currentKey, value); + isTransfered = true; + } else { + isIgnored = true; + } + _unset(config, formerKey); + + if (issueWarning) { + const warnings = [`"${formerKey}" is deprecated, instead use "${currentKey}"`]; + if (isTransfered) { + warnings.push(`The value at "${formerKey}" has been moved to "${currentKey}"`); + } + if (isIgnored) { + warnings.push( + `The value at "${formerKey}" is being ignored in favor of the value at "${currentKey}"` + ); + } + + console.warn(warnings.join('\n')); + } + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-scripts.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-scripts.ts new file mode 100644 index 0000000000..06397b5ef1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/config/process-scripts.ts @@ -0,0 +1,16 @@ +import { getConfigValue } from './get-config'; +import { TScript } from '../../types/config'; + +export default function processScripts() { + const scripts = getConfigValue('scripts'); + if (scripts) { + scripts.forEach((script: TScript) => { + if (script.type === 'inline') { + const textElem = document.createTextNode(script.text); + const scriptElem = document.createElement('script'); + scriptElem.append(textElem); + document.body.appendChild(scriptElem); + } + }); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/configure-store.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/configure-store.ts new file mode 100644 index 0000000000..632552ba70 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/configure-store.ts @@ -0,0 +1,47 @@ +import { createStore, combineReducers, applyMiddleware, compose, Store, StoreEnhancer } from 'redux'; +import { createReduxHistoryContext } from 'redux-first-history'; +import { createHashHistory } from 'history'; + +import traceTimeline from '../components/TraceSearchPage/TraceTimelineViewer/duck'; +import jaegerReducers from '../reducers'; +import * as jaegerMiddlewares from '../middlewares'; +import { getAppEnvironment } from './constants'; +import { ReduxState } from '../types'; + +declare global { + // eslint-disable-next-line @typescript-eslint/naming-convention + interface Window { + __REDUX_DEVTOOLS_EXTENSION__?: () => StoreEnhancer; + } +} + +const { createReduxHistory, routerMiddleware, routerReducer } = createReduxHistoryContext({ + history: createHashHistory(), +}); + +export default function configureStore(): Store { + const middlewares = [ + ...Object.keys(jaegerMiddlewares) + .map(key => (jaegerMiddlewares as any)[key]) + .filter(Boolean), + routerMiddleware, + ]; + + let enhancer: StoreEnhancer = applyMiddleware(...middlewares); + + if (getAppEnvironment() !== 'production' && window && window.__REDUX_DEVTOOLS_EXTENSION__) { + enhancer = compose(enhancer, window.__REDUX_DEVTOOLS_EXTENSION__()); + } + + return createStore( + combineReducers({ + ...jaegerReducers, + traceTimeline, + router: routerReducer, + }) as any, + enhancer + ); +} + +export const store = configureStore(); +export const history = createReduxHistory(store); diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/constants.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/constants.ts new file mode 100644 index 0000000000..e13d42d5eb --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/constants.ts @@ -0,0 +1,21 @@ +/** + * Provides access to constants injected by the build system. + */ + +/** + * Get the current execution environment, as inferred from NODE_ENV at build time. + */ +export function getAppEnvironment() { + return __APP_ENVIRONMENT__; +} + +/** + * Get injected version details as a JSON-formatted string. + */ +export function getVersionInfo() { + return __REACT_APP_VSN_STATE__; +} + +export function shouldDebugGoogleAnalytics() { + return __REACT_APP_GA_DEBUG__; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/date.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/date.ts new file mode 100644 index 0000000000..e00ec648fd --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/date.ts @@ -0,0 +1,254 @@ +import dayjs, { ConfigType } from 'dayjs'; +import _dropWhile from 'lodash/dropWhile'; +import _round from 'lodash/round'; +import _duration, { DurationUnitType } from 'dayjs/plugin/duration'; + +import { toFloatPrecision } from './number'; +import { Microseconds } from '../types/units'; + +dayjs.extend(_duration); + +const TODAY = 'Today'; +const YESTERDAY = 'Yesterday'; + +export const STANDARD_DATE_FORMAT = 'YYYY-MM-DD'; +export const STANDARD_TIME_FORMAT = 'HH:mm'; +export const STANDARD_DATETIME_FORMAT = 'MMMM D YYYY, HH:mm:ss.SSS'; + +/** @constant 1ms as the number of microseconds, which is the precision of Jaeger timestamps */ +export const ONE_MILLISECOND = 1000 * 1; + +const ONE_SECOND = 1000 * ONE_MILLISECOND; +const ONE_MINUTE = 60 * ONE_SECOND; +const ONE_HOUR = 60 * ONE_MINUTE; +const ONE_DAY = 24 * ONE_HOUR; + +const DEFAULT_MS_PRECISION = Math.log10(ONE_MILLISECOND); + +const UNIT_STEPS: { unit: string; microseconds: number; ofPrevious: number }[] = [ + { unit: 'd', microseconds: ONE_DAY, ofPrevious: 24 }, + { unit: 'h', microseconds: ONE_HOUR, ofPrevious: 60 }, + { unit: 'm', microseconds: ONE_MINUTE, ofPrevious: 60 }, + { unit: 's', microseconds: ONE_SECOND, ofPrevious: 1000 }, + { unit: 'ms', microseconds: ONE_MILLISECOND, ofPrevious: 1000 }, + { unit: 'μs', microseconds: 1, ofPrevious: 1000 }, +]; + +type ShortTimeUnit = 'μs' | 'ms' | 's' | 'm' | 'h' | 'd'; +type LongTimeUnit = 'microseconds' | 'milliseconds' | 'seconds' | 'minutes' | 'hours' | 'days'; + +const timeUnitToShortTermMapper: { + [key in LongTimeUnit]: ShortTimeUnit; +} = { + microseconds: 'μs', + milliseconds: 'ms', + seconds: 's', + minutes: 'm', + hours: 'h', + days: 'd', +} as const; + +/** + * @param {number} timestamp + * @param {number} initialTimestamp + * @param {number} totalDuration + * @return {number} 0-100 percentage + */ +export function getPercentageOfDuration(duration: number, totalDuration: number): number { + return (duration / totalDuration) * 100; +} + +const quantizeDuration = (duration: number, floatPrecision: number, conversionFactor: number): number => + toFloatPrecision(duration / conversionFactor, floatPrecision) * conversionFactor; + +/** + * @param {number} duration - Unix Time + * @return {string} formatted, unit-labelled string with time in milliseconds + * + * @example + * ``` + * formatDate(0) // => 1970-01-01 + * ``` + */ +export function formatDate(duration: number): string { + return dayjs(duration / ONE_MILLISECOND).format(STANDARD_DATE_FORMAT); +} + +/** + * @param {number} duration - Unix Time + * @return {string} formatted, unit-labelled string with time in milliseconds + * + * @example + * ``` + * formatTime(0) // => 00:00 + * ``` + */ +export function formatTime(duration: number): string { + return dayjs(duration / ONE_MILLISECOND).format(STANDARD_TIME_FORMAT); +} + +/** + * @param {number} duration - Unix Time + * @return {string} formatted, unit-labelled string with time in milliseconds + * + * @example + * ``` + * formatDatetime(0) // => January 1 1970, 00:00:00.000 + * ``` + */ +export function formatDatetime(duration: number): string { + return dayjs(duration / ONE_MILLISECOND).format(STANDARD_DATETIME_FORMAT); +} + +/** + * @param {number} duration - Unix Time + * @return {string} formatted, unit-labelled string with time in milliseconds + * + * @example + * ``` + * formatMillisecondTime(1_000) // => 1ms + * formatMillisecondTime(10_000) // => 10ms + * ``` + */ +export function formatMillisecondTime(duration: number): string { + const targetDuration = quantizeDuration(duration, DEFAULT_MS_PRECISION, ONE_MILLISECOND); + return `${dayjs.duration(targetDuration / ONE_MILLISECOND).asMilliseconds()}ms`; +} + +/** + * @param {number} duration - Unix Time + * @return {string} formatted, unit-labelled string with time in seconds + * + * @example + * ``` + * formatSecondTime(1_000_000) // => 1s + * formatSecondTime(10_000_000) // => 10s + * ``` + */ +export function formatSecondTime(duration: number): string { + const targetDuration = quantizeDuration(duration, DEFAULT_MS_PRECISION, ONE_SECOND); + return `${dayjs.duration(targetDuration / ONE_MILLISECOND).asSeconds()}s`; +} + +/** + * Humanizes a duration for display with up to two units. + * Shows both primary and secondary units when applicable (e.g., "2d 3h"). + * For decimal-based units (μs, ms, s), displays as a decimal (e.g., "2.36ms"). + * + * @param duration - Duration in microseconds + * @returns Formatted string with up to 2 units (e.g., "2.36ms", "2d 3h") + * + * @example + * formatDuration(2357) // => "2.36ms" + * formatDuration(183840000000) // => "2d 3h" + */ +export function formatDuration(duration: Microseconds): string { + // Drop all units that are too large except the last one + const [primaryUnit, secondaryUnit] = _dropWhile( + UNIT_STEPS, + ({ microseconds }, index) => index < UNIT_STEPS.length - 1 && microseconds > duration + ); + + if (primaryUnit.ofPrevious === 1000) { + // If the unit is decimal based, display as a decimal + return `${_round(duration / primaryUnit.microseconds, 2)}${primaryUnit.unit}`; + } + + const primaryValue = Math.floor(duration / primaryUnit.microseconds); + const primaryUnitString = `${primaryValue}${primaryUnit.unit}`; + const secondaryValue = Math.round((duration / secondaryUnit.microseconds) % primaryUnit.ofPrevious); + const secondaryUnitString = `${secondaryValue}${secondaryUnit.unit}`; + return secondaryValue === 0 ? primaryUnitString : `${primaryUnitString} ${secondaryUnitString}`; +} + +export function formatRelativeDate(value: ConfigType, fullMonthName = false): string { + const m = dayjs.isDayjs(value) ? value : dayjs(value); + + const monthFormat = fullMonthName ? 'MMMM' : 'MMM'; + const dt = new Date(); + if (dt.getFullYear() !== m.year()) { + return m.format(`${monthFormat} D, YYYY`); + } + const mMonth = m.month(); + const mDate = m.date(); + const date = dt.getDate(); + if (mMonth === dt.getMonth() && mDate === date) { + return TODAY; + } + dt.setDate(date - 1); + if (mMonth === dt.getMonth() && mDate === dt.getDate()) { + return YESTERDAY; + } + return m.format(`${monthFormat} D`); +} + +export const getSuitableTimeUnit = (microseconds: number): LongTimeUnit => { + if (microseconds < 1000) { + return 'microseconds'; + } + + const durationInMilliseconds = dayjs.duration(microseconds / 1000, 'ms'); + + const longUnitsDescending: Exclude[] = [ + 'days', + 'hours', + 'minutes', + 'seconds', + 'milliseconds', + ]; + + return longUnitsDescending.find(timeUnit => { + const durationInTimeUnit = durationInMilliseconds.as(timeUnit); + + return durationInTimeUnit >= 1; + })!; +}; + +export function convertTimeUnitToShortTerm(timeUnit: LongTimeUnit): ShortTimeUnit | '' { + return timeUnitToShortTermMapper[timeUnit] ?? ''; +} + +export function convertToTimeUnit(microseconds: number, targetTimeUnit: string): number { + if (microseconds < 1000) { + return microseconds; + } + + return dayjs.duration(microseconds / 1000, 'ms').as(targetTimeUnit as DurationUnitType); +} + +/** + * Formats a duration in microseconds to a compact string with 3 significant digits. + * Useful for displaying durations in tables where space is limited and excessive precision + * reduces readability. + * + * @param microseconds - Duration in microseconds + * @returns Formatted string with 3 significant digits and appropriate unit (μs, ms, s, m) + * + * @example + * formatDurationCompact(123) // => "123μs" + * formatDurationCompact(13835) // => "13.8ms" + * formatDurationCompact(135842) // => "136ms" + * formatDurationCompact(1835200) // => "1.84s" + */ +export function formatDurationCompact(microseconds: Microseconds): string { + if (microseconds < 1000) { + return `${Math.round(microseconds)}μs`; + } + + const ms = microseconds / 1000; + if (ms < 1000) { + // Format to 3 significant digits + const formatted = ms < 10 ? ms.toPrecision(2) : ms < 100 ? ms.toPrecision(3) : Math.round(ms); + return `${formatted}ms`; + } + + const s = ms / 1000; + if (s < 60) { + const formatted = s < 10 ? s.toPrecision(2) : s < 100 ? s.toPrecision(3) : Math.round(s); + return `${formatted}s`; + } + + const m = s / 60; + const formatted = m < 10 ? m.toPrecision(2) : m < 100 ? m.toPrecision(3) : Math.round(m); + return `${formatted}m`; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/documentTitle.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/documentTitle.ts new file mode 100644 index 0000000000..b4c7ede641 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/documentTitle.ts @@ -0,0 +1,35 @@ +import * as React from 'react'; + +type Props = { + title?: string | null; +}; + +/** + * DocumentTitle - minimal replacement for react-helmet title usage. + * - Sets document.title to `title` when mounted/updated. + * - Restores previous document.title when unmounted. + * + * Note: client-side only. For SSR/head management, consider react-helmet-async. + */ +const DocumentTitle: React.FC = ({ title }) => { + const prevTitleRef = React.useRef(typeof document !== 'undefined' ? document.title : null); + + React.useEffect(() => { + if (typeof title === 'string' && title !== document.title) { + document.title = title; + } + return () => { + if (prevTitleRef.current != null) { + try { + document.title = prevTitleRef.current; + } catch (e) { + // ignore in weird test envs + } + } + }; + }, [title]); + + return null; +}; + +export default DocumentTitle; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/embedded-url.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/embedded-url.ts new file mode 100644 index 0000000000..bcc049fba6 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/embedded-url.ts @@ -0,0 +1,53 @@ +import _flatMap from 'lodash/flatMap'; +import queryString from 'query-string'; + +import { EmbeddedState } from '../types/embedded'; + +function getStrings(value: string): string; +function getStrings(value: object): string[]; +function getStrings(value: string | object): string | string[] { + return typeof value === 'string' ? value : _flatMap(value, getStrings); +} + +const VALUE_ENABLED = '1'; +export const VERSION_0 = 'v0'; + +// uiEmbed=v0 +// uiSearchHideGraph=1 +// uiTimelineCollapseTitle=1 +// uiTimelineHideMinimap=1 +// uiTimelineHideSummary=1 +const STATE_PARAMS_V0 = { + searchHideGraph: 'uiSearchHideGraph', + timeline: { + collapseTitle: 'uiTimelineCollapseTitle', + hideMinimap: 'uiTimelineHideMinimap', + hideSummary: 'uiTimelineHideSummary', + }, +}; + +const PARAM_KEYS_V0 = getStrings(STATE_PARAMS_V0); + +export function getEmbeddedState(search: string): null | EmbeddedState { + const { uiEmbed, ...rest } = queryString.parse(search); + if (uiEmbed !== VERSION_0) { + return null; + } + return { + version: VERSION_0, + searchHideGraph: rest[STATE_PARAMS_V0.searchHideGraph] === VALUE_ENABLED, + timeline: { + collapseTitle: rest[STATE_PARAMS_V0.timeline.collapseTitle] === VALUE_ENABLED, + hideMinimap: rest[STATE_PARAMS_V0.timeline.hideMinimap] === VALUE_ENABLED, + hideSummary: rest[STATE_PARAMS_V0.timeline.hideSummary] === VALUE_ENABLED, + }, + }; +} + +export function stripEmbeddedState(state: Record) { + const { uiEmbed = undefined, ...rv } = state; + if (uiEmbed === VERSION_0) { + PARAM_KEYS_V0.forEach(Reflect.deleteProperty.bind(null, rv)); + } + return rv; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/filter-spans.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/filter-spans.ts new file mode 100644 index 0000000000..c2afd4a465 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/filter-spans.ts @@ -0,0 +1,75 @@ +import { KeyValuePair, Span } from '../types/trace'; +import { IOtelSpan, IAttribute } from '../types/otel'; +import { TNil } from '../types'; + +export default function filterSpans(textFilter: string, spans: ReadonlyArray | TNil) { + if (!spans) { + return null; + } + + // if a span field includes at least one filter in includeFilters, the span is a match + const includeFilters: string[] = []; + + // values with keys that include text in any one of the excludeKeys will be ignored + const excludeKeys: string[] = []; + + // split textFilter by whitespace, but not that in double quotes, remove empty strings, and extract includeFilters and excludeKeys + const regex = /[^\s"]+|"([^"]*)"/g; + const match = textFilter.match(regex); + const results = match ? match.map(e => e.replace(/"(.*)"/, '$1')) : []; + + results.filter(Boolean).forEach(w => { + if (w[0] === '-') { + excludeKeys.push(w.substr(1).toLowerCase()); + } else { + includeFilters.push(w.toLowerCase()); + } + }); + + const isTextInFilters = (filters: Array, text: string) => + filters.some(filter => text.toLowerCase().includes(filter)); + + const isTextInKeyValues = (kvs: ReadonlyArray) => + kvs + ? kvs.some(kv => { + // ignore checking key and value for a match if key is in excludeKeys + if (isTextInFilters(excludeKeys, kv.key)) return false; + const value = (kv as any).value; // handle legacy KeyValuePair and IAttribute + if (value === null || value === undefined) return false; + const valueString = String(value); + // match if key, value or key=value string matches an item in includeFilters + return ( + isTextInFilters(includeFilters, kv.key) || + isTextInFilters(includeFilters, valueString) || + isTextInFilters(includeFilters, `${kv.key}=${valueString}`) + ); + }) + : false; + + const isSpanAMatch = (span: Span | IOtelSpan) => { + if ('operationName' in span) { + // Legacy Span + return ( + isTextInFilters(includeFilters, span.operationName) || + isTextInFilters(includeFilters, span.process.serviceName) || + isTextInKeyValues(span.tags) || + (Array.isArray(span.logs) && span.logs.some(log => isTextInKeyValues(log.fields))) || + isTextInKeyValues(span.process.tags) || + includeFilters.some(filter => filter.replace(/^0*/, '') === span.spanID.replace(/^0*/, '')) + ); + } + // IOtelSpan + return ( + isTextInFilters(includeFilters, span.name) || + isTextInFilters(includeFilters, span.resource.serviceName) || + isTextInKeyValues(span.attributes) || + (Array.isArray(span.events) && span.events.some(event => isTextInKeyValues(event.attributes))) || + isTextInKeyValues(span.resource.attributes) || + includeFilters.some(filter => filter.replace(/^0*/, '') === span.spanID.replace(/^0*/, '')) + ); + }; + + // declare as const because need to disambiguate the type + const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span | IOtelSpan) => span.spanID)); + return rv; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/oltp2jaeger-multi-out.json b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/oltp2jaeger-multi-out.json new file mode 100644 index 0000000000..6e37231af0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/oltp2jaeger-multi-out.json @@ -0,0 +1,524 @@ +{ + "data": [ + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spans": [ + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "26f5ef9dbb885479", + "operationName": "ohboy.do", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + } + } + ], + "startTime": 1711138246978000, + "duration": 46460, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "ohboy" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 38000, + "depth": 2, + "hasChildren": false, + "childSpanIds": [] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "e76628f4e6dde174", + "operationName": "ohboy.do", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + } + } + ], + "startTime": 1711138247025000, + "duration": 71355, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "ohboy" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 85000, + "depth": 2, + "hasChildren": false, + "childSpanIds": [] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "49b8e9efa1e8a3b1", + "operationName": "info", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "span": { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + } + ], + "startTime": 1711138246943000, + "duration": 173150, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 3000, + "depth": 1, + "hasChildren": true, + "childSpanIds": ["e76628f4e6dde174", "26f5ef9dbb885479"] + }, + { + "traceID": "c620759e5d60fafb8ee0922b30e06bc6", + "spanID": "eff3153be6b1fb93", + "operationName": "main", + "references": [], + "startTime": 1711138246940000, + "duration": 176321, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "widgets" + }, + { + "key": "otel.library.version", + "type": "string", + "value": "1.2.3" + }, + { + "key": "span.kind", + "type": "string", + "value": "internal" + } + ], + "logs": [], + "processID": "p1", + "warnings": [], + "process": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + }, + "relativeStartTime": 0, + "depth": 0, + "hasChildren": true, + "childSpanIds": ["49b8e9efa1e8a3b1"] + } + ], + "processes": { + "p1": { + "serviceName": "example-trace", + "tags": [ + { + "key": "telemetry.sdk.language", + "type": "string", + "value": "nodejs" + }, + { + "key": "telemetry.sdk.name", + "type": "string", + "value": "opentelemetry" + }, + { + "key": "telemetry.sdk.version", + "type": "string", + "value": "1.22.0" + } + ] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in-error.json b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in-error.json new file mode 100644 index 0000000000..e83daa4d82 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in-error.json @@ -0,0 +1,43 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [] + }, + "scopeSpans": [ + { + "scope": { + "name": "telemetrygen" + }, + "spans": [ + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + } + ] + } + ], + "schemaUrl": "https://opentelemetry.io/schemas/1.4.0" + } + ] +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in.json b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in.json new file mode 100644 index 0000000000..1a62a0a1e8 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-in.json @@ -0,0 +1,74 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "telemetrygen" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "telemetrygen" + }, + "spans": [ + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + }, + { + "traceId": "83a9efd15c1c98a977e0711cc93ee28b", + "spanId": "e127af99e3b3e074", + "parentSpanId": "909541b92cf05311", + "name": "okey-dokey-0", + "kind": 2, + "startTimeUnixNano": "1706678909209712000", + "endTimeUnixNano": "1706678909209835000", + "attributes": [ + { + "key": "net.peer.ip", + "value": { + "stringValue": "1.2.3.4" + } + }, + { + "key": "peer.service", + "value": { + "stringValue": "telemetrygen-client" + } + } + ], + "status": {} + } + ] + } + ], + "schemaUrl": "https://opentelemetry.io/schemas/1.4.0" + } + ] +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in-combined.json b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in-combined.json new file mode 100644 index 0000000000..eb2bc1ae7c --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in-combined.json @@ -0,0 +1,202 @@ +{ + "resourceSpans": [ + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "ohboy" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "26f5ef9dbb885479", + "parentSpanId": "49b8e9efa1e8a3b1", + "name": "ohboy.do", + "kind": 1, + "startTimeUnixNano": "1711138246978000000", + "endTimeUnixNano": "1711138247024460625", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "ohboy" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "e76628f4e6dde174", + "parentSpanId": "49b8e9efa1e8a3b1", + "name": "ohboy.do", + "kind": 1, + "startTimeUnixNano": "1711138247025000000", + "endTimeUnixNano": "1711138247096355250", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "widgets", + "version": "1.2.3" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "49b8e9efa1e8a3b1", + "parentSpanId": "eff3153be6b1fb93", + "name": "info", + "kind": 1, + "startTimeUnixNano": "1711138246943000000", + "endTimeUnixNano": "1711138247116150875", + "status": {} + } + ] + } + ] + }, + { + "resource": { + "attributes": [ + { + "key": "service.name", + "value": { + "stringValue": "example-trace" + } + }, + { + "key": "telemetry.sdk.language", + "value": { + "stringValue": "nodejs" + } + }, + { + "key": "telemetry.sdk.name", + "value": { + "stringValue": "opentelemetry" + } + }, + { + "key": "telemetry.sdk.version", + "value": { + "stringValue": "1.22.0" + } + } + ] + }, + "scopeSpans": [ + { + "scope": { + "name": "widgets", + "version": "1.2.3" + }, + "spans": [ + { + "traceId": "c620759e5d60fafb8ee0922b30e06bc6", + "spanId": "eff3153be6b1fb93", + "parentSpanId": "", + "name": "main", + "kind": 1, + "startTimeUnixNano": "1711138246940000000", + "endTimeUnixNano": "1711138247116321958", + "status": {} + } + ] + } + ] + } + ] +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in.json.txt b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in.json.txt new file mode 100644 index 0000000000..5c92bedab2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-multi-in.json.txt @@ -0,0 +1,4 @@ +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"ohboy"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"26f5ef9dbb885479","parentSpanId":"49b8e9efa1e8a3b1","name":"ohboy.do","kind":1,"startTimeUnixNano":"1711138246978000000","endTimeUnixNano":"1711138247024460625","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"ohboy"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"e76628f4e6dde174","parentSpanId":"49b8e9efa1e8a3b1","name":"ohboy.do","kind":1,"startTimeUnixNano":"1711138247025000000","endTimeUnixNano":"1711138247096355250","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"widgets","version":"1.2.3"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"49b8e9efa1e8a3b1","parentSpanId":"eff3153be6b1fb93","name":"info","kind":1,"startTimeUnixNano":"1711138246943000000","endTimeUnixNano":"1711138247116150875","status":{}}]}]}]} +{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"example-trace"}},{"key":"telemetry.sdk.language","value":{"stringValue":"nodejs"}},{"key":"telemetry.sdk.name","value":{"stringValue":"opentelemetry"}},{"key":"telemetry.sdk.version","value":{"stringValue":"1.22.0"}}]},"scopeSpans":[{"scope":{"name":"widgets","version":"1.2.3"},"spans":[{"traceId":"c620759e5d60fafb8ee0922b30e06bc6","spanId":"eff3153be6b1fb93","parentSpanId":"","name":"main","kind":1,"startTimeUnixNano":"1711138246940000000","endTimeUnixNano":"1711138247116321958","status":{}}]}]}]} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-out.json b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-out.json new file mode 100644 index 0000000000..313659814b --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/fixtures/otlp2jaeger-out.json @@ -0,0 +1,98 @@ +{ + "data": [ + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spans": [ + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "e127af99e3b3e074", + "operationName": "okey-dokey-0", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "909541b92cf05311" + } + ], + "startTime": 1706678909209712, + "duration": 123, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "telemetrygen" + }, + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "peer.service", + "type": "string", + "value": "telemetrygen-client" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + }, + { + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "e127af99e3b3e074", + "operationName": "okey-dokey-0", + "references": [ + { + "refType": "CHILD_OF", + "traceID": "83a9efd15c1c98a977e0711cc93ee28b", + "spanID": "909541b92cf05311" + } + ], + "startTime": 1706678909209712, + "duration": 123, + "tags": [ + { + "key": "otel.library.name", + "type": "string", + "value": "telemetrygen" + }, + { + "key": "net.peer.ip", + "type": "string", + "value": "1.2.3.4" + }, + { + "key": "peer.service", + "type": "string", + "value": "telemetrygen-client" + }, + { + "key": "span.kind", + "type": "string", + "value": "server" + } + ], + "logs": [], + "processID": "p1", + "warnings": null + } + ], + "processes": { + "p1": { + "serviceName": "telemetrygen", + "tags": [] + } + }, + "warnings": null + } + ], + "total": 0, + "limit": 0, + "offset": 0, + "errors": null +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/generate-action-types.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/generate-action-types.ts new file mode 100644 index 0000000000..608bf23368 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/generate-action-types.ts @@ -0,0 +1,24 @@ +/** + * Util to generate an object of key:value pairs where key is + * `commonPrefix/topLevelTypes[i]` and value is `topLevelTypes[i]` for all `i` + * in `topLevelTypes`. + * + * @example generateActionTypes('a', ['b']) -> {'a/b': 'b'} + * + * @param commonPrefix A string that is prepended to each value in + * `topLevelTypes` to create a property name + * @param topLevelTypes An array of strings to generate property names from and + * to assign as the corresponding values. + * @returns {{[string]: string}} + */ +export default function generateActionTypes( + commonPrefix: string, + topLevelTypes: string[] +): Record { + const rv: Record = {}; + topLevelTypes.forEach(type => { + const fullType = `${commonPrefix}/${type}`; + rv[type] = fullType; + }); + return rv; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/guardReducer.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/guardReducer.ts new file mode 100644 index 0000000000..59e05fccea --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/guardReducer.ts @@ -0,0 +1,21 @@ +import { Action } from 'redux-actions'; + +export default function guardReducer(fn: (state: TState, value: TPayload) => TState) { + return function reducer(state: TState, { payload }: Action) { + if (!payload) { + return state; + } + return fn(state, payload); + }; +} + +export function guardReducerWithMeta( + fn: (state: TState, action: { meta: TMeta; payload: TPayload }) => TState +) { + return function reducer(state: TState, action: Action & { meta: TMeta }) { + if (!action.payload || !action.meta) { + return state; + } + return fn(state, action as { meta: TMeta; payload: TPayload }); + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/link-formatting.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/link-formatting.ts new file mode 100644 index 0000000000..c32d112117 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/link-formatting.ts @@ -0,0 +1,106 @@ +/** + * Formatters that can be used in link patterns. + * + * @property epoch_micros_to_date_iso - Converts epoch microseconds to ISO 8601 date string. + * @property pad_start - Pads the start of a string with a given character to a specified length. + * @property add - Adds a numeric offset to a number. + */ +const formatFunctions: Record(value: T, ...args: string[]) => T | string | number> = { + epoch_micros_to_date_iso: microsSinceEpoch => { + if (typeof microsSinceEpoch !== 'number') { + console.error('epoch_micros_to_date_iso() can only operate on numbers, ignoring formatting', { + value: microsSinceEpoch, + }); + return microsSinceEpoch; + } + + return new Date(microsSinceEpoch / 1000).toISOString(); + }, + pad_start: (value, desiredLengthString: string, padCharacter: string) => { + if (typeof value !== 'string') { + console.error('pad_start() can only operate on strings, ignoring formatting', { + value, + desiredLength: desiredLengthString, + padCharacter, + }); + return value; + } + const desiredLength = parseInt(desiredLengthString, 10); + if (Number.isNaN(desiredLength)) { + console.error('pad_start() needs a desired length as second argument, ignoring formatting', { + value, + desiredLength: desiredLengthString, + padCharacter, + }); + } + + return value.padStart(desiredLength, padCharacter); + }, + + add: (value, offsetString: string) => { + if (typeof value !== 'number') { + console.error('add() needs a numeric offset as an argument, ignoring formatting', { + value, + offsetString, + }); + return value; + } + + const offset = parseInt(offsetString, 10); + if (Number.isNaN(offset)) { + console.error('add() needs a valid offset in microseconds as second argument, ignoring formatting', { + value, + offsetString, + }); + return value; + } + + return value + offset; + }, +}; + +/** + * Parses a parameter string that may contain formatters (e.g. "param | formatter1 | formatter2 arg1"). + * Returns the parameter name and a chained function that applies all specified formatters. + * + * @param parameter - The parameter string to parse. + * @returns An object containing the parameter name and the formatting function (or null if no formatters). + */ +export function getParameterAndFormatter( + parameter: string +): { + parameterName: string; + formatFunction: ((value: T) => T | string | number) | null; +} { + const [parameterName, ...formatStrings] = parameter.split('|').map(part => part.trim()); + + // const formatFunctions = getFormatFunctions(); + + const formatters = formatStrings + .map(formatString => { + const [formatFunctionName, ...args] = formatString.split(/ +/); + const formatFunction = formatFunctions[formatFunctionName] as + | ((value: T, ...args: string[]) => T | string | number) + | undefined; + if (!formatFunction) { + console.error( + 'Unrecognized format function name, ignoring formatting. Other formatting functions may be applied', + { + parameter, + formatFunctionName, + validValues: Object.keys(formatFunctions), + } + ); + return null; + } + return (val: T) => formatFunction(val, ...args); + }) + .filter((fn): fn is NonNullable => fn != null); + + const chainedFormatFunction = (value: T) => formatters.reduce((acc, fn) => fn(acc) as T, value); + + return { + parameterName, + formatFunction: formatters.length ? chainedFormatFunction : null, + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/number.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/number.ts new file mode 100644 index 0000000000..09f1b56049 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/number.ts @@ -0,0 +1,25 @@ +/** + * given a number and a desired precision for the floating + * side, return the number at the new precision. + * + * toFloatPrecision(3.55, 1) // 3.5 + * toFloatPrecision(0.04422, 2) // 0.04 + * toFloatPrecision(6.24e6, 2) // 6240000.00 + * + * does not support numbers that use "e" notation on toString. + * + * @param {number} number + * @param {number} precision + * @return {number} number at new floating precision + */ + +export function toFloatPrecision(number: number, precision: number): number { + const log10Length = Math.floor(Math.log10(Math.abs(number))) + 1; + const targetPrecision = precision + log10Length; + + if (targetPrecision <= 0) { + return Math.trunc(number); + } + + return Number(number.toPrecision(targetPrecision)); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/parseQuery.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/parseQuery.ts new file mode 100644 index 0000000000..fdf85d9bb4 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/parseQuery.ts @@ -0,0 +1,23 @@ +import queryString, { ParseOptions } from 'query-string'; + +interface IParsedQuery { + [key: string]: string | null | (string | null)[]; +} + +function parseQuery(query: string, options?: ParseOptions): { [key: string]: string | string[] } { + const parsed: IParsedQuery = queryString.parse(query, options); + + const result: { [key: string]: string | string[] } = {}; + + Object.keys(parsed).forEach(key => { + if (Array.isArray(parsed[key])) { + result[key] = (parsed[key] as string[]).map((item: string | null) => item || ''); + } else { + result[key] = parsed[key] as string | string[]; + } + }); + + return result; +} + +export default parseQuery; diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/prefix-url.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/prefix-url.ts new file mode 100755 index 0000000000..5f6df70177 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/prefix-url.ts @@ -0,0 +1,35 @@ +import sitePrefix from '../site-prefix'; +import { getAppEnvironment } from './constants'; + +const origin = getAppEnvironment() === 'test' ? global.location.origin : window.location.origin; + +/** + * Generate the URL prefix from `sitePrefix` and use it for all subsequent calls + * to `prefixUrl()`. `sitePrefix` should be an absolute URL, e.g. with an origin. + * `pathPrefix` is just the path portion and should not have a trailing slash: + * + * - `"http://localhost:3000/"` to `""` + * - `"http://localhost:3000/abc/"` to `"/abc"` + * - `"http://localhost:3000/abc/def/"` to `"/abc/def"` + */ +// exported for tests +export function getPathPrefix(orig?: string, sitePref?: string) { + const o = orig == null ? '' : orig.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); + const s = sitePref == null ? '' : sitePref; + const rx = new RegExp(`^${o}|/$`, 'ig'); + return s.replace(rx, ''); +} + +const pathPrefix = getPathPrefix(origin, sitePrefix); + +/** + * Add the path prefix to the URL. See [site-prefix.js](../site-prefix.js) and + * the `` tag in [index.html](../../public/index.html) for details. + * + * @param {string} value The URL to have the prefix added to. + * @return {string} The resultant URL. + */ +export default function prefixUrl(value?: string) { + const s = value == null ? '' : String(value); + return `${pathPrefix}${s}`; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/readJsonFile.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/readJsonFile.ts new file mode 100644 index 0000000000..2675f44d26 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/readJsonFile.ts @@ -0,0 +1,72 @@ +import JaegerAPI from '../api/jaeger'; + +function tryParseMultiLineInput(input: string): any[] { + const jsonStrings = input.split('\n').filter((line: string) => line.trim() !== ''); + const parsedObjects: any[] = []; + + jsonStrings.forEach((jsonString: string, index: number) => { + try { + const traceObj = JSON.parse(jsonString.trim()); + parsedObjects.push(traceObj); + } catch (error) { + throw new Error(`Error parsing JSON at line ${index + 1}: ${(error as Error).message}`); + } + }); + + return parsedObjects; +} + +export default function readJsonFile(fileList: { file: File }): Promise { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => { + if (typeof reader.result !== 'string') { + reject(new Error('Invalid result type')); + return; + } + let traceObj; + try { + traceObj = JSON.parse(reader.result); + } catch (error) { + try { + traceObj = tryParseMultiLineInput(reader.result); + } catch (error) { + reject(error); + return; + } + } + if (Array.isArray(traceObj) && traceObj.every(obj => 'resourceSpans' in obj)) { + const mergedResourceSpans = traceObj.reduce((acc, obj) => { + acc.push(...obj.resourceSpans); + return acc; + }, []); + + traceObj = { resourceSpans: mergedResourceSpans }; + } + + if ('resourceSpans' in traceObj) { + JaegerAPI.transformOTLP(traceObj) + .then((result: string) => { + resolve(result); + }) + .catch(() => { + reject(new Error('Error converting traces to OTLP')); + }); + } else { + resolve(traceObj); + } + }; + reader.onerror = () => { + const errMessage = reader.error ? `: ${String(reader.error)}` : ''; + reject(new Error(`Error reading the JSON file${errMessage}`)); + }; + reader.onabort = () => { + reject(new Error(`Reading the JSON file has been aborted`)); + }; + try { + reader.readAsText(fileList.file); + } catch (error) { + reject(new Error(`Error reading the JSON file: ${(error as Error).message}`)); + } + }); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/regexp-escape.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/regexp-escape.ts new file mode 100644 index 0000000000..7746b987e5 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/regexp-escape.ts @@ -0,0 +1,6 @@ +/** + * Escape the meta-caharacters used in regular expressions. + */ +export default function regexpEscape(s: string): string { + return s.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/sort.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/sort.ts new file mode 100644 index 0000000000..d1e5926ee8 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/sort.ts @@ -0,0 +1,42 @@ +export function localeStringComparator(itemA: string, itemB: string): number { + return itemA.localeCompare(itemB); +} + +export function numberSortComparator(itemA: number, itemB: number): number { + return itemA - itemB; +} + +export function classNameForSortDir(dir: number): string { + return `sorted ${dir === 1 ? 'ascending' : 'descending'}`; +} + +type SortColumn = { + name: string; + defaultDir?: number; +}; + +type SortState = { + key: string; + dir: number; +}; + +export function getNewSortForClick(prevSort: SortState, column: SortColumn): SortState { + const { defaultDir = 1 } = column; + + return { + key: column.name, + dir: prevSort.key === column.name ? -1 * prevSort.dir : defaultDir, + }; +} + +export function createSortClickHandler( + column: SortColumn, + currentSortKey: string, + currentSortDir: number, + updateSort: (key: string, dir: number) => void +): () => void { + return function onClickSortingElement() { + const { key, dir } = getNewSortForClick({ key: currentSortKey, dir: currentSortDir }, column); + updateSort(key, dir); + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/span-ancestor-ids.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/span-ancestor-ids.ts new file mode 100644 index 0000000000..55572dba36 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/span-ancestor-ids.ts @@ -0,0 +1,19 @@ +import { TNil } from '../types'; +import { IOtelSpan } from '../types/otel'; + +/** + * Returns an array of ancestor span IDs for an OTEL span, using parentSpan. + * @param span - The IOtelSpan to get ancestors for + * @returns Array of ancestor span IDs, from immediate parent to root + */ +export default function spanAncestorIds(span: IOtelSpan | TNil): string[] { + const ancestorIDs: string[] = []; + if (!span) return ancestorIDs; + + let currentParent = span.parentSpan; + while (currentParent) { + ancestorIDs.push(currentParent.spanID); + currentParent = currentParent.parentSpan; + } + return ancestorIDs; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/stringSupplant.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/stringSupplant.ts new file mode 100644 index 0000000000..ff99989bb2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/stringSupplant.ts @@ -0,0 +1,31 @@ +import { getParameterAndFormatter } from './link-formatting'; + +const PARAMETER_REG_EXP = /#\{([^{}]*)\}/g; + +export function encodedStringSupplant( + str: string, + encodeFn: null | ((unencoded: string | number) => string), + map: Record +) { + return str.replace(PARAMETER_REG_EXP, (_, name) => { + const { parameterName, formatFunction } = getParameterAndFormatter(name); + const mapValue = map[parameterName]; + const formattedValue = formatFunction && mapValue ? formatFunction(mapValue) : mapValue; + + const value = formattedValue != null && encodeFn ? encodeFn(formattedValue) : mapValue; + return value == null ? '' : `${value}`; + }); +} + +export default function stringSupplant(str: string, map: Record) { + return encodedStringSupplant(str, null, map); +} + +export function getParamNames(str: string) { + const names = new Set(); + str.replace(PARAMETER_REG_EXP, (match, name) => { + names.add(name); + return match; + }); + return Array.from(names); +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/test/requestAnimationFrame.js b/app/vmui/packages/jaeger-ui-lite/src/utils/test/requestAnimationFrame.js new file mode 100644 index 0000000000..9dbb5a85b2 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/test/requestAnimationFrame.js @@ -0,0 +1,23 @@ +const DEFAULT_ELAPSE = 0; + +export default function requestAnimationFrame(callback) { + return setTimeout(callback, DEFAULT_ELAPSE); +} + +export function cancelAnimationFrame(id) { + return clearTimeout(id); +} + +export function polyfill(target, msElapse = DEFAULT_ELAPSE) { + const _target = target || global; + if (!_target.requestAnimationFrame) { + if (msElapse === DEFAULT_ELAPSE) { + _target.requestAnimationFrame = requestAnimationFrame; + } else { + _target.requestAnimationFrame = callback => setTimeout(callback, msElapse); + } + } + if (!_target.cancelAnimationFrame) { + _target.cancelAnimationFrame = cancelAnimationFrame; + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/update-ui-find.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/update-ui-find.ts new file mode 100644 index 0000000000..21ad781267 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/update-ui-find.ts @@ -0,0 +1,40 @@ +import queryString from 'query-string'; +import { NavigateFunction } from 'react-router-dom-v5-compat'; +import { History as RouterHistory, Location } from 'history'; + +import { TNil } from '../types'; + +export default function updateUiFind({ + history, + navigate, + location, + trackFindFunction, + uiFind, +}: { + history?: RouterHistory; + navigate?: NavigateFunction; + location: Location; + trackFindFunction?: (uiFind: string | TNil) => void; + uiFind?: string | TNil; +}) { + const parsed = queryString.parse(location.search); + const traceId = parsed.traceId; + + // If we are in trace view, do not touch URL search params. + if (traceId) { + if (trackFindFunction) trackFindFunction(uiFind); + return; + } + + const { uiFind: _oldUiFind, ...queryParams } = parsed; + if (trackFindFunction) trackFindFunction(uiFind); + if (uiFind) (queryParams as Record).uiFind = uiFind; + + const nextSearch = `?${queryString.stringify(queryParams)}`; + + if (navigate) { + navigate({ pathname: location.pathname, search: nextSearch }, { replace: true }); + } else if (history) { + history.replace({ ...location, search: nextSearch }); + } +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/version/get-version.ts b/app/vmui/packages/jaeger-ui-lite/src/utils/version/get-version.ts new file mode 100644 index 0000000000..dc2e86499f --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/version/get-version.ts @@ -0,0 +1,20 @@ +import defaultVersion from '../../constants/default-version'; + +let haveWarnedFactoryFn = false; + +export default function getVersion() { + const getJaegerVersion = window.getJaegerVersion; + if (typeof getJaegerVersion !== 'function') { + if (!haveWarnedFactoryFn) { + console.warn('Embedded version information not available'); + haveWarnedFactoryFn = true; + } + return { ...defaultVersion }; + } + const embedded = getJaegerVersion(); + if (!embedded) { + return { ...defaultVersion }; + } + + return { ...embedded }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/utils/withRouteProps.tsx b/app/vmui/packages/jaeger-ui-lite/src/utils/withRouteProps.tsx new file mode 100644 index 0000000000..282173ab75 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/utils/withRouteProps.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { useLocation, useParams } from 'react-router-dom'; +import { History, Location } from 'history'; +import { history } from './configure-store'; + +/** + * Interface representing route-related props passed to the enhanced component. + * @interface + * @property {Location} location - The current location object containing information about the URL. + * @property {string} pathname - The current URL pathname. + * @property {string} search - The current URL search string. + * @property {object} params - The URL parameters. + * @property {History} history - The history object for navigation. + */ +export type IWithRouteProps = { + location: Location; + pathname: string; + search: string; + params: object; + history: History; +}; + +/** + * Enhances a React component with route-related props. Works similar to withRouter export from react-router-dom v5 below. + * @function + * @param {React.ElementType} WrappedComponent - The component to be enhanced. + * @returns {React.Component} A higher-order component with route-related props. + */ +export default function withRouteProps(WrappedComponent: React.ElementType) { + /** + * @function + * @param {IWithRouteProps|object} props - The props passed to the enhanced component. + * @returns {React.Component} The enhanced component with additional route-related props. + */ + return function WithRouteProps(props: IWithRouteProps | object) { + /** + * The current location object containing information about the URL. + * @type {Location} + */ + const location = useLocation(); + + /** + * The URL parameters extracted from the route. + * @type {object} + */ + const params = useParams(); + + /** + * The current URL pathname. + * @type {string} + */ + const { pathname } = location; + + /** + * The current URL search string. + * @type {string} + */ + const { search } = location; + + /** + * Renders the enhanced component with route-related props. + * @returns {React.Component} The enhanced component with additional route-related props. + */ + return ( + + ); + }; +} diff --git a/app/vmui/packages/jaeger-ui-lite/src/vite-env.d.ts b/app/vmui/packages/jaeger-ui-lite/src/vite-env.d.ts new file mode 100644 index 0000000000..11f02fe2a0 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/app/vmui/packages/jaeger-ui-lite/ts-conv-progress.sh b/app/vmui/packages/jaeger-ui-lite/ts-conv-progress.sh new file mode 100755 index 0000000000..852286432a --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/ts-conv-progress.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +FLOW_LOC_NUM=$(ag -l @flow src | tr '\n' ' ' | xargs wc -l | tail -1 | awk '{print $1}') +JS_LOC_NUM=$(find src -name '*.js' \! -name '*.test.js' -print | tr '\n' ' ' | xargs wc -l | tail -1 | awk '{print $1}') +TSC_LOC_NUM=$(find src -name '*tsx' -print | tr '\n' ' ' | xargs wc -l | tail -1 | awk '{print $1}') +PCT_JS_DONE_NUM=$(awk "BEGIN { print ${TSC_LOC_NUM} / (${JS_LOC_NUM} + ${TSC_LOC_NUM}) * 100 }") +PCT_FLOW_DONE_NUM=$(awk "BEGIN { print ${TSC_LOC_NUM} / (${FLOW_LOC_NUM} + ${TSC_LOC_NUM}) * 100 }") + +FLOW_LOC=$(printf "%'d" $FLOW_LOC_NUM) +JS_LOC=$(printf "%'d" $JS_LOC_NUM) +TSC_LOC=$(printf "%'d" $TSC_LOC_NUM) +PCT_JS_DONE=$(printf '%.2f %%' $PCT_JS_DONE_NUM) +PCT_FLOW_DONE=$(printf '%.2f %%' $PCT_FLOW_DONE_NUM) + +echo "LOC" +echo "" +echo "TypeScript $TSC_LOC" +echo "Flow $FLOW_LOC" +echo "" +echo "JavaScript $JS_LOC" +echo "Complete $PCT_JS_DONE" diff --git a/app/vmui/packages/jaeger-ui-lite/tsconfig.json b/app/vmui/packages/jaeger-ui-lite/tsconfig.json new file mode 100644 index 0000000000..d98c278c53 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/tsconfig.json @@ -0,0 +1,8 @@ + +{ + "extends": "../../tsconfig", + "compilerOptions": { + "jsx": "react-jsx" + }, + "include": ["src/**/*.tsx", "src/**/*.ts", "src/**/*.json", "typings"] +} diff --git a/app/vmui/packages/jaeger-ui-lite/tsconfig.lint.json b/app/vmui/packages/jaeger-ui-lite/tsconfig.lint.json new file mode 100644 index 0000000000..21bd4dbbe7 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/tsconfig.lint.json @@ -0,0 +1,28 @@ + +// This file is necessary because the build system (Vite) requires +// isolatedModules to be true but for linting we need it to be false. We run +// tsc-lint from the project root, but packages have their own tsconfig for +// the lint so the typings from one package doesn't pollute other packages. +// For that to work, project references are used +// (https://www.typescriptlang.org/docs/handbook/project-references.html) which +// requires `composite: true` and `isolatedModules: false`. +// Since project references use emitted declarations to determine whether the project +// is up to date, a single index.d.ts file is specified as the output. +{ + "extends": "./tsconfig", + "compilerOptions": { + "isolatedModules": false, + "composite": true, + "outFile": "index.d.ts" + }, + "files": [ + "src/components/App/index.tsx", + "src/components/DependencyGraph/index.tsx", + "src/components/SearchTracePage/index.tsx", + "src/components/SearchTracePage/SearchForm.tsx", + "src/components/SearchTracePage/SearchResults/ScatterPlot.tsx", + "src/components/DependencyGraph/sample_data/large.json", + "src/components/DependencyGraph/sample_data/small.json", + "package.json" + ] +} diff --git a/app/vmui/packages/jaeger-ui-lite/vite.config.mts b/app/vmui/packages/jaeger-ui-lite/vite.config.mts new file mode 100644 index 0000000000..8e41efe6f1 --- /dev/null +++ b/app/vmui/packages/jaeger-ui-lite/vite.config.mts @@ -0,0 +1,127 @@ +/* eslint-disable import/no-extraneous-dependencies */ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import legacy from '@vitejs/plugin-legacy'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const proxyConfig = { + target: 'http://localhost:16686', + secure: false, + changeOrigin: true, + ws: true, + xfwd: true, +}; + +/** + * Vite plugin to inject local UI config during development. + * This mimics the behavior of the Go query-service which injects config into index.html. + * + * Supports two config file formats: + * 1. jaeger-ui.config.js - JavaScript file that exports a config object (or function returning one) + * 2. jaeger-ui.config.json - JSON file with config object + * + * The plugin only runs in development mode (npm start). + * + * Security note: These config files are local to the developer's machine and are + * excluded from git via .gitignore. The content is injected into the HTML during + * development only, similar to how the Go query-service injects config in production. + */ +function jaegerUiConfigPlugin() { + const jsConfigPath = path.resolve(__dirname, 'jaeger-ui.config.js'); + const jsonConfigPath = path.resolve(__dirname, 'jaeger-ui.config.json'); + + return { + name: 'jaeger-ui-config', + transformIndexHtml: { + order: 'pre' as const, + async handler(html: string) { + // Check for JS config first (higher priority, like in Go server) + if (fs.existsSync(jsConfigPath)) { + try { + const jsContent = fs.readFileSync(jsConfigPath, 'utf-8'); + // Replace the JAEGER_CONFIG_JS comment with UIConfig function + // This mimics the Go server behavior for .js config files + const uiConfigFn = `function UIConfig() { ${jsContent} }`; + html = html.replace('// JAEGER_CONFIG_JS', uiConfigFn); + console.log('[jaeger-ui-config] Loaded config from jaeger-ui.config.js'); + return html; + } catch (err) { + console.error('[jaeger-ui-config] Error loading jaeger-ui.config.js:', err); + } + } + + // Check for JSON config + if (fs.existsSync(jsonConfigPath)) { + try { + const jsonContent = fs.readFileSync(jsonConfigPath, 'utf-8'); + // Validate it's valid JSON and use stringified result for injection + const parsedConfig = JSON.parse(jsonContent); + // Replace DEFAULT_CONFIG with the JSON content + // This mimics the Go server behavior for .json config files + html = html.replace( + 'const JAEGER_CONFIG = DEFAULT_CONFIG;', + `const JAEGER_CONFIG = ${JSON.stringify(parsedConfig)};` + ); + console.log('[jaeger-ui-config] Loaded config from jaeger-ui.config.json'); + return html; + } catch (err) { + console.error('[jaeger-ui-config] Error loading jaeger-ui.config.json:', err); + } + } + + return html; + }, + }, + }; +} + +// https://vitejs.dev/config/ +export default defineConfig({ + define: { + __REACT_APP_GA_DEBUG__: JSON.stringify(process.env.REACT_APP_GA_DEBUG || ''), + __REACT_APP_VSN_STATE__: JSON.stringify(process.env.REACT_APP_VSN_STATE || ''), + __APP_ENVIRONMENT__: JSON.stringify(process.env.NODE_ENV || 'development'), + }, + plugins: [ + jaegerUiConfigPlugin(), + react({ + babel: { + babelrc: true, + }, + }), + legacy({ + targets: ['>0.5%', 'not dead', 'not ie <= 11', 'not op_mini all'], + }), + ], + css: { + preprocessorOptions: { + less: { + math: 'always', + javascriptEnabled: true, + }, + }, + }, + server: { + proxy: { + // Proxy jaeger-query resource paths for local development. + '/api': proxyConfig, + '/analytics': proxyConfig, + '/serviceedges': proxyConfig, + '/qualitymetrics-v2': proxyConfig, + }, + }, + base: './', + build: { + outDir: 'build', + assetsDir: 'static', + commonjsOptions: { + // Ensure we transform modules that contain a mix of ES imports + // and CommonJS require() calls to avoid stray require() calls in production. + transformMixedEsModules: true, + }, + }, +}); diff --git a/app/vmui/packages/vmui/package.json b/app/vmui/packages/vmui/package.json index ae1a1cce6b..6799d155f5 100644 --- a/app/vmui/packages/vmui/package.json +++ b/app/vmui/packages/vmui/package.json @@ -15,16 +15,22 @@ "test:dev": "vitest" }, "dependencies": { + "antd": "6.1.1", "classnames": "^2.5.1", "dayjs": "^1.11.13", + "jaeger-ui-lite": "workspace:*", "lodash.debounce": "^4.0.8", "lodash.orderby": "^4.6.0", "lodash.throttle": "^4.1.1", "marked": "^16.0.0", - "preact": "^10.26.9", "qs": "^6.14.0", - "react-input-mask": "^2.0.4", - "react-router-dom": "^7.6.3", + "query-string": "^9.3.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-imask": "^7.6.1", + "react-redux": "^9.2.0", + "react-router-dom": "5.3.4", + "react-router-dom-v5-compat": "6.30.0", "uplot": "^1.6.32", "vite": "^7.0.0", "web-vitals": "^5.0.3" @@ -32,19 +38,21 @@ "devDependencies": { "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.30.0", - "@preact/preset-vite": "^2.10.2", "@testing-library/jest-dom": "^6.6.3", "@testing-library/preact": "^3.2.4", + "@types/history": "^5.0.0", "@types/lodash.debounce": "^4.0.9", "@types/lodash.orderby": "^4.6.9", "@types/lodash.throttle": "^4.1.9", "@types/node": "^24.0.8", "@types/qs": "^6.14.0", "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.8", "@types/react-input-mask": "^3.0.6", "@types/react-router-dom": "^5.3.3", "@typescript-eslint/eslint-plugin": "^8.35.1", "@typescript-eslint/parser": "^8.35.1", + "@vitejs/plugin-react": "^5.1.2", "cross-env": "^7.0.3", "eslint": "^9.30.0", "eslint-plugin-react": "^7.37.5", @@ -69,4 +77,4 @@ "last 1 safari version" ] } -} +} \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/App.tsx b/app/vmui/packages/vmui/src/App.tsx index e6d39786a1..58743b0ef9 100644 --- a/app/vmui/packages/vmui/src/App.tsx +++ b/app/vmui/packages/vmui/src/App.tsx @@ -1,36 +1,38 @@ -import { FC, useState } from "preact/compat"; -import { HashRouter, Route, Routes } from "react-router-dom"; +import { FC, useState } from "react"; +import { HashRouter, Route, Switch } from "react-router-dom"; +import { CompatRouter } from 'react-router-dom-v5-compat'; import AppContextProvider from "./contexts/AppContextProvider"; import ThemeProvider from "./components/Main/ThemeProvider/ThemeProvider"; -import ExploreLogs from "./pages/ExploreLogs/ExploreLogs"; -import LogsLayout from "./layouts/LogsLayout/LogsLayout"; +import TracesLayout from "./layouts/TracesLayout/TracesLayout"; import "./constants/markedPlugins"; +import JaegerRoutesInVmui from "./jaeger/JaegerRoutesInVmui"; const App: FC = () => { const [loadedTheme, setLoadedTheme] = useState(false); - return <> + return ( - - <> - - {loadedTheme && ( - - } - > + + + <> + + {loadedTheme && ( + } + path="/" + render={() => ( + + + + )} /> - - - )} - - + + )} + + + - ; + ); }; export default App; diff --git a/app/vmui/packages/vmui/src/api/types.ts b/app/vmui/packages/vmui/src/api/types.ts index 4d8410b435..1866d764ca 100644 --- a/app/vmui/packages/vmui/src/api/types.ts +++ b/app/vmui/packages/vmui/src/api/types.ts @@ -1,5 +1,5 @@ import uPlot from "uplot"; -import { ReactNode } from "preact/compat"; +import { ReactNode } from "react"; export interface MetricBase { group: number; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsChart.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsChart.tsx deleted file mode 100644 index ee4e69182a..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsChart.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { FC, useState } from "preact/compat"; -import "./style.scss"; -import "uplot/dist/uPlot.min.css"; -import { AlignedData } from "uplot"; -import { TimeParams } from "../../../types"; -import classNames from "classnames"; -import { LogHits } from "../../../api/types"; -import { GraphOptions, GRAPH_STYLES } from "./types"; -import BarHitsOptions from "./BarHitsOptions/BarHitsOptions"; -import BarHitsPlot from "./BarHitsPlot/BarHitsPlot"; - -interface Props { - logHits: LogHits[]; - data: AlignedData; - period: TimeParams; - setPeriod: ({ from, to }: { from: Date, to: Date }) => void; - onApplyFilter: (value: string) => void; -} -const BarHitsChart: FC = ({ logHits, data: _data, period, setPeriod, onApplyFilter }) => { - const [graphOptions, setGraphOptions] = useState({ - graphStyle: GRAPH_STYLES.LINE_STEPPED, - stacked: false, - fill: false, - hideChart: false, - }); - - - return ( -
- {!graphOptions.hideChart && ( - - )} - -
- ); -}; - -export default BarHitsChart; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegend.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegend.tsx deleted file mode 100644 index ce118d9b34..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegend.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { FC, useEffect, useState } from "preact/compat"; -import uPlot, { Series } from "uplot"; -import "./style.scss"; -import BarHitsLegendItem from "./BarHitsLegendItem"; -import { LegendLogHits } from "../../../../api/types"; - -interface Props { - uPlotInst: uPlot; - legendDetails: LegendLogHits[]; - onApplyFilter: (value: string) => void; -} - -const BarHitsLegend: FC = ({ uPlotInst, legendDetails, onApplyFilter }) => { - const [series, setSeries] = useState([]); - const totalHits = legendDetails[0]?.totalHits || 0; - - const getSeries = () => { - return uPlotInst.series.filter(s => s.scale !== "x"); - }; - - const handleRedrawGraph = () => { - uPlotInst.redraw(); - }; - - useEffect(() => { - if (!uPlotInst.hooks.draw) { - uPlotInst.hooks.draw = []; - } - uPlotInst.hooks.draw.push(() => { - setSeries(getSeries()); - }); - }, [uPlotInst]); - - return ( -
- {legendDetails.map((legend) => ( - - ))} -
-
- Total hits: {totalHits.toLocaleString("en-US")} -
-
- L-Click toggles visibility.  - R-Click opens menu. -
-
-
- ); -}; - -export default BarHitsLegend; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegendItem.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegendItem.tsx deleted file mode 100644 index 6cb142d796..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/BarHitsLegendItem.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { FC, useMemo, useRef, useState, MouseEvent } from "preact/compat"; -import classNames from "classnames"; -import { Series } from "uplot"; -import { LegendLogHits } from "../../../../api/types"; -import { getStreamPairs } from "../../../../utils/logs"; -import { formatNumberShort } from "../../../../utils/math"; -import Popper from "../../../Main/Popper/Popper"; -import useBoolean from "../../../../hooks/useBoolean"; -import LegendHitsMenu from "../LegendHitsMenu/LegendHitsMenu"; - -interface Props { - legend: LegendLogHits; - series: Series[]; - onRedrawGraph: () => void; - onApplyFilter: (value: string) => void; -} - -const BarHitsLegendItem: FC = ({ legend, series, onRedrawGraph, onApplyFilter }) => { - const { - value: openContextMenu, - setTrue: handleOpenContextMenu, - setFalse: handleCloseContextMenu, - } = useBoolean(false); - - const legendRef = useRef(null); - const [clickPosition, setClickPosition] = useState<{ top: number; left: number } | null>(null); - - const targetSeries = useMemo(() => series.find(s => s.label === legend.label), [series]); - - const fields = useMemo(() => getStreamPairs(legend.label), [legend.label]); - - const label = fields.join(", "); - const totalShortFormatted = formatNumberShort(legend.total); - - const handleClickByStream = (e: MouseEvent) => { - if (!targetSeries) return; - - if (e.metaKey || e.ctrlKey) { - targetSeries.show = !targetSeries.show; - } else { - const isOnlyTargetVisible = series.every(s => s === targetSeries || !s.show); - series.forEach(s => { - s.show = isOnlyTargetVisible || (s === targetSeries); - }); - } - - onRedrawGraph(); - }; - - const handleContextMenu = (e: MouseEvent) => { - e.preventDefault(); - setClickPosition({ top: e.clientY, left: e.clientX }); - handleOpenContextMenu(); - }; - - return ( -
-
-
{label}
- ({totalShortFormatted}) - - - -
- ); -}; - -export default BarHitsLegendItem; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/style.scss b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/style.scss deleted file mode 100644 index ae10ddfbdb..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsLegend/style.scss +++ /dev/null @@ -1,126 +0,0 @@ -@use "src/styles/variables" as *; - -.vm-bar-hits-legend { - display: flex; - flex-wrap: wrap; - padding: 0 $padding-small $padding-small; - color: $color-text; - - &-item { - max-width: 50%; - display: flex; - align-items: center; - gap: $padding-small; - font-size: $font-size-small; - padding: $padding-small $padding-global; - border-radius: $border-radius-small; - cursor: pointer; - transition: 0.2s; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - - &_hide { - text-decoration: line-through; - opacity: 0.5; - } - - &__marker { - min-width: 14px; - max-width: 14px; - height: 14px; - border: $color-background-block; - } - - &__label { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - - &__total { - color: $color-text-secondary; - font-style: italic; - grid-column: 2; - } - } - - &-info { - flex-grow: 1; - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - padding-top: $padding-small; - color: $color-text-secondary; - font-size: $font-size-small; - - code { - display: inline-block; - padding: calc($padding-small / 2) $padding-small; - font-size: $font-size-small; - text-align: center; - background-color: $color-background-body; - background-repeat: repeat-x; - border: $border-divider; - border-radius: 4px; - } - } -} - -.vm-legend { - position: relative; - display: flex; - flex-direction: column; - cursor: default; - width: 100%; - - &-group { - min-width: 23%; - width: 100%; - margin: 0 $padding-global $padding-global 0; - - &-header { - display: grid; - align-items: center; - gap: $padding-small; - margin-bottom: 1px; - border-bottom: $border-divider; - font-size: $font-size-small; - padding: $padding-small; - - &-title { - display: flex; - align-items: center; - gap: $padding-small; - color: $color-text-secondary; - - b { - color: $color-text; - } - } - - &-labels { - display: flex; - flex-wrap: wrap; - gap: $padding-small; - color: $color-text-secondary; - - &__item { - color: $color-text; - cursor: pointer; - - &:hover { - text-decoration: underline; - } - - &:not(:last-child):after { - content: ","; - } - } - } - } - } -} - diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/BarHitsOptions.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/BarHitsOptions.tsx deleted file mode 100644 index 7bfe897312..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/BarHitsOptions.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import { FC, useEffect, useMemo, useRef } from "preact/compat"; -import { GraphOptions, GRAPH_STYLES } from "../types"; -import Switch from "../../../Main/Switch/Switch"; -import "./style.scss"; -import useStateSearchParams from "../../../../hooks/useStateSearchParams"; -import { useSearchParams } from "react-router-dom"; -import Button from "../../../Main/Button/Button"; -import { SettingsIcon, VisibilityIcon, VisibilityOffIcon } from "../../../Main/Icons"; -import Tooltip from "../../../Main/Tooltip/Tooltip"; -import Popper from "../../../Main/Popper/Popper"; -import useBoolean from "../../../../hooks/useBoolean"; - -interface Props { - onChange: (options: GraphOptions) => void; -} - -const BarHitsOptions: FC = ({ onChange }) => { - const [searchParams, setSearchParams] = useSearchParams(); - const optionsButtonRef = useRef(null); - const { - value: openOptions, - toggle: toggleOpenOptions, - setFalse: handleCloseOptions, - } = useBoolean(false); - - const [stacked, setStacked] = useStateSearchParams(false, "stacked"); - const [fill, setFill] = useStateSearchParams("true", "fill"); - const [hideChart, setHideChart] = useStateSearchParams(false, "hide_chart"); - - const options: GraphOptions = useMemo(() => ({ - graphStyle: GRAPH_STYLES.BAR, - stacked, - fill: fill === "true", - hideChart, - }), [stacked, fill, hideChart]); - - const handleChangeFill = (val: boolean) => { - setFill(`${val}`); - searchParams.set("fill", `${val}`); - setSearchParams(searchParams); - }; - - const handleChangeStacked = (val: boolean) => { - setStacked(val); - val ? searchParams.set("stacked", "true") : searchParams.delete("stacked"); - setSearchParams(searchParams); - }; - - const toggleHideChart = () => { - setHideChart(prev => { - const newVal = !prev; - newVal ? searchParams.set("hide_chart", "true") : searchParams.delete("hide_chart"); - setSearchParams(searchParams); - return newVal; - }); - }; - - useEffect(() => { - onChange(options); - }, [options]); - - return ( -
- -
- -
-
- -
-
- -
-
-
-
- ); -}; - -export default BarHitsOptions; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/style.scss b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/style.scss deleted file mode 100644 index 5e0a0e83fe..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsOptions/style.scss +++ /dev/null @@ -1,37 +0,0 @@ -@use "src/styles/variables" as *; - -.vm-bar-hits-options { - display: flex; - align-items: center; - position: absolute; - top: $padding-small; - right: $padding-small; - z-index: 2; - - &-settings { - display: grid; - align-items: flex-start; - min-width: 200px; - gap: $padding-global; - padding-bottom: $padding-global; - - &-item { - padding: 0 $padding-global; - - &_list { - padding: 0; - } - - &__title { - font-size: $font-size-small; - color: $color-text-secondary; - padding: 0 $padding-small $padding-small; - } - - &:last-child { - border-bottom: none; - } - - } - } -} diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsPlot/BarHitsPlot.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsPlot/BarHitsPlot.tsx deleted file mode 100644 index d657dea1c6..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsPlot/BarHitsPlot.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { FC, useCallback, useEffect, useMemo, useRef, useState } from "preact/compat"; -import useElementSize from "../../../../hooks/useElementSize"; -import uPlot, { AlignedData } from "uplot"; -import { GraphOptions } from "../types"; -import usePlotScale from "../../../../hooks/uplot/usePlotScale"; -import useReadyChart from "../../../../hooks/uplot/useReadyChart"; -import useZoomChart from "../../../../hooks/uplot/useZoomChart"; -import stack from "../../../../utils/uplot/stack"; -import useBarHitsOptions, { getLabelFromLogHit } from "../hooks/useBarHitsOptions"; -import { LegendLogHits, LogHits } from "../../../../api/types"; -import { addSeries, delSeries, setBand } from "../../../../utils/uplot"; -import classNames from "classnames"; -import BarHitsTooltip from "../BarHitsTooltip/BarHitsTooltip"; -import { TimeParams } from "../../../../types"; -import BarHitsLegend from "../BarHitsLegend/BarHitsLegend"; -import { calculateTotalHits, sortLogHits } from "../../../../utils/logs"; - -interface Props { - logHits: LogHits[]; - data: AlignedData; - period: TimeParams; - setPeriod: ({ from, to }: { from: Date, to: Date }) => void; - onApplyFilter: (value: string) => void; - graphOptions: GraphOptions; -} - -const BarHitsPlot: FC = ({ graphOptions, logHits, data: _data, period, setPeriod, onApplyFilter }: Props) => { - const [containerRef, containerSize] = useElementSize(); - const uPlotRef = useRef(null); - const [uPlotInst, setUPlotInst] = useState(); - - const { xRange, setPlotScale } = usePlotScale({ period, setPeriod }); - const { onReadyChart, isPanning } = useReadyChart(setPlotScale); - useZoomChart({ uPlotInst, xRange, setPlotScale }); - - const { data, bands } = useMemo(() => { - return graphOptions.stacked ? stack(_data, () => false) : { data: _data, bands: [] }; - }, [graphOptions, _data]); - - const { options, series, focusDataIdx } = useBarHitsOptions({ - data, - logHits, - bands, - xRange, - containerSize, - onReadyChart, - setPlotScale, - graphOptions - }); - - const prepareLegend = useCallback((hits: LogHits[], totalHits: number): LegendLogHits[] => { - return hits.map((hit) => { - const label = getLabelFromLogHit(hit); - - const legendItem: LegendLogHits = { - label, - isOther: hit._isOther, - fields: hit.fields, - total: hit.total || 0, - totalHits, - stroke: series.find((s) => s.label === label)?.stroke, - }; - - return legendItem; - }).sort(sortLogHits("total")); - }, [series]); - - - const legendDetails: LegendLogHits[] = useMemo(() => { - const totalHits = calculateTotalHits(logHits); - return prepareLegend(logHits, totalHits); - }, [logHits, prepareLegend]); - - useEffect(() => { - if (!uPlotInst) return; - - const oldSeriesMap = new Map(uPlotInst.series.map(s => [s.label, s])); - - const syncedSeries = series.map(s => { - const old = oldSeriesMap.get(s.label); - return old ? { ...s, show: old.show } : s; - }); - - delSeries(uPlotInst); - addSeries(uPlotInst, syncedSeries, true); - setBand(uPlotInst, syncedSeries); - uPlotInst.redraw(); - }, [series, uPlotInst]); - - useEffect(() => { - if (!uPlotInst) return; - uPlotInst.delBand(); - bands.forEach(band => { - uPlotInst.addBand(band); - }); - uPlotInst.redraw(); - }, [bands]); - - useEffect(() => { - if (!uPlotRef.current) return; - const uplot = new uPlot(options, data, uPlotRef.current); - setUPlotInst(uplot); - return () => uplot.destroy(); - }, [uPlotRef.current]); - - useEffect(() => { - if (!uPlotInst) return; - uPlotInst.scales.x.range = () => [xRange.min, xRange.max]; - uPlotInst.redraw(); - }, [xRange]); - - useEffect(() => { - if (!uPlotInst) return; - uPlotInst.setSize(containerSize); - uPlotInst.redraw(); - }, [containerSize]); - - useEffect(() => { - if (!uPlotInst) return; - uPlotInst.setData(data); - uPlotInst.redraw(); - }, [data]); - - return ( - <> -
-
- -
- {uPlotInst && } - - ); -}; - -export default BarHitsPlot; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/BarHitsTooltip.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/BarHitsTooltip.tsx deleted file mode 100644 index 2cb9370a8c..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/BarHitsTooltip.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import { FC, useMemo, useRef } from "preact/compat"; -import uPlot, { AlignedData } from "uplot"; -import dayjs from "dayjs"; -import { DATE_TIME_FORMAT } from "../../../../constants/date"; -import classNames from "classnames"; -import "./style.scss"; -import { sortLogHits } from "../../../../utils/logs"; - -interface Props { - data: AlignedData; - uPlotInst?: uPlot; - focusDataIdx: number; -} - -const timeFormat = (ts: number) => dayjs(ts * 1000).tz().format(DATE_TIME_FORMAT); - -const BarHitsTooltip: FC = ({ data, focusDataIdx, uPlotInst }) => { - const tooltipRef = useRef(null); - - const tooltipData = useMemo(() => { - const series = uPlotInst?.series || []; - const [time, ...values] = data.map((d) => d[focusDataIdx] || 0); - const step = (data[0][1] - data[0][0]); - const timeNext = time + step; - - const tooltipItems = values.map((value, i) => { - const targetSeries = series[i + 1]; - const stroke = (targetSeries?.stroke as () => string)?.(); - const label = targetSeries?.label as string; - const show = targetSeries?.show; - return { - label, - stroke, - value, - show - }; - }).filter(item => item.value > 0 && item.show).sort(sortLogHits("value")); - - const point = { - top: tooltipItems[0] ? uPlotInst?.valToPos?.(tooltipItems[0].value, "y") || 0 : 0, - left: uPlotInst?.valToPos?.(time, "x") || 0, - }; - - return { - point, - values: tooltipItems, - total: tooltipItems.reduce((acc, item) => acc + item.value, 0), - timestamp: `${timeFormat(time)} - ${timeFormat(timeNext)}`, - }; - }, [focusDataIdx, uPlotInst, data]); - - const tooltipPosition = useMemo(() => { - if (!uPlotInst || !tooltipData.total || !tooltipRef.current) return; - - const { top, left } = tooltipData.point; - const uPlotPosition = { - left: parseFloat(uPlotInst.over.style.left), - top: parseFloat(uPlotInst.over.style.top) - }; - - const { - width: uPlotWidth, - height: uPlotHeight - } = uPlotInst.over.getBoundingClientRect(); - - const { - width: tooltipWidth, - height: tooltipHeight - } = tooltipRef.current.getBoundingClientRect(); - - const margin = 50; - const overflowX = left + tooltipWidth >= uPlotWidth ? tooltipWidth + (2 * margin) : 0; - const overflowY = top + tooltipHeight >= uPlotHeight ? tooltipHeight + (2 * margin) : 0; - - const position = { - top: top + uPlotPosition.top + margin - overflowY, - left: left + uPlotPosition.left + margin - overflowX - }; - - if (position.left < 0) position.left = 20; - if (position.top < 0) position.top = 20; - - return position; - }, [tooltipData, uPlotInst, tooltipRef.current]); - - return ( -
-
- {tooltipData.values.map((item, i) => ( -
- -

- {item.label} - {item.value.toLocaleString("en-US")} -

-
- ))} -
- {tooltipData.values.length > 1 && ( -
- -

- Total - {tooltipData.total.toLocaleString("en-US")} -

-
- )} -
-
- {tooltipData.timestamp} -
-
-
- ); -}; - -export default BarHitsTooltip; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/style.scss b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/style.scss deleted file mode 100644 index e35c8f64cb..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/BarHitsTooltip/style.scss +++ /dev/null @@ -1,177 +0,0 @@ -@use "src/styles/variables" as *; - -$chart-tooltip-width: 370px; -$chart-tooltip-icon-width: 25px; -$chart-tooltip-half-icon: calc($chart-tooltip-icon-width / 2); -$chart-tooltip-date-width: $chart-tooltip-width - (2*$chart-tooltip-icon-width) - (3*$padding-global); -$chart-tooltip-x: -1 * ($padding-small + $padding-global + $chart-tooltip-date-width + $chart-tooltip-half-icon); -$chart-tooltip-y: -1 * ($padding-global + $chart-tooltip-half-icon); - -.vm-bar-hits-tooltip { - opacity: 0; - pointer-events: none; - gap: $padding-small; - - &_visible { - opacity: 1; - pointer-events: auto; - } - - &-item { - display: grid; - grid-template-columns: 1fr auto; - align-items: center; - gap: $padding-global; - max-width: 100%; - - &__label { - display: inline-block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - - &__date { - white-space: nowrap; - } -} - -.vm-chart-tooltip { - position: absolute; - display: grid; - gap: $padding-global; - width: $chart-tooltip-width; - padding: $padding-global; - border-radius: $border-radius-medium; - background: $color-background-tooltip; - color: $color-white; - font-size: $font-size-small; - font-weight: normal; - line-height: 150%; - word-wrap: break-word; - font-family: $font-family-monospace; - z-index: 98; - user-select: text; - pointer-events: none; - - &_hits { - white-space: pre-wrap; - word-break: break-all; - width: auto; - max-width: calc(100vw/3); - } - - &_hits &-data { - display: grid; - grid-template-columns: $font-size 1fr; - } - - &_sticky { - pointer-events: auto; - z-index: 99; - } - - &_moved { - position: fixed; - margin-top: $chart-tooltip-y; - margin-left: $chart-tooltip-x; - } - - &-header { - display: grid; - grid-template-columns: 1fr $chart-tooltip-icon-width $chart-tooltip-icon-width; - gap: $padding-small; - align-items: center; - justify-content: center; - min-height: 25px; - - &__title { - grid-row: 1; - } - - &__close { - grid-row: 1; - grid-column: 3; - color: $color-white; - } - - &__drag { - grid-row: 1; - grid-column: 2; - color: $color-white; - cursor: move; - } - - &__date { - grid-column: 1; - display: grid; - gap: 2px; - } - } - - &-data { - display: flex; - align-items: center; - justify-content: flex-start; - gap: $padding-small; - - &_margin-bottom { - margin-bottom: $padding-global; - } - - &_margin-top { - margin-top: $padding-global; - } - - &__marker { - min-width: $font-size; - max-width: $font-size; - width: $font-size; - height: $font-size; - border: 1px solid rgba($color-white, 0.5); - - &_tranparent { - opacity: 0; - } - } - - &__value { - line-height: 1; - font-size: $font-size; - } - } - - &-stats { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-start; - gap: $padding-small $padding-global; - - &-row { - display: grid; - align-items: center; - justify-content: flex-start; - - &:not(:last-child) { - padding-right: $padding-small; - } - - &__key { - line-height: 1; - margin-right: calc($padding-small/2); - } - - &__value { - font-weight: bold; - } - } - } - - &__info { - word-break: break-all; - white-space: pre-wrap; - } -} - diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenu.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenu.tsx deleted file mode 100644 index 2fd4cf7eaa..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenu.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { FC } from "preact/compat"; -import "./style.scss"; -import { LegendLogHits } from "../../../../api/types"; -import LegendHitsMenuStats from "./LegendHitsMenuStats"; -import LegendHitsMenuBase from "./LegendHitsMenuBase"; -import LegendHitsMenuRow from "./LegendHitsMenuRow"; -import LegendHitsMenuFields from "./LegendHitsMenuFields"; -import { LOGS_LIMIT_HITS } from "../../../../constants/logs"; - -const otherDescription = `aggregated results for fields not in the top ${LOGS_LIMIT_HITS}`; - -interface Props { - legend: LegendLogHits; - fields: string[]; - onApplyFilter: (value: string) => void; - onClose: () => void; -} - -const LegendHitsMenu: FC = ({ legend, fields, onApplyFilter, onClose }) => { - return ( -
-
- -
- - {!legend.isOther && ( - - )} - - {!legend.isOther && ( - - )} - - -
- ); -}; - -export default LegendHitsMenu; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuBase.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuBase.tsx deleted file mode 100644 index 68e38e25dd..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuBase.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { FC } from "preact/compat"; -import LegendHitsMenuRow from "./LegendHitsMenuRow"; -import useCopyToClipboard from "../../../../hooks/useCopyToClipboard"; -import { CopyIcon, FilterIcon, FilterOffIcon } from "../../../Main/Icons"; -import { LegendLogHits, LegendLogHitsMenu } from "../../../../api/types"; -import { LOGS_GROUP_BY } from "../../../../constants/logs"; - -interface Props { - legend: LegendLogHits; - onApplyFilter: (value: string) => void; - onClose: () => void; -} - -const LegendHitsMenuBase: FC = ({ legend, onApplyFilter, onClose }) => { - const copyToClipboard = useCopyToClipboard(); - - const handleAddStreamToFilter = () => { - onApplyFilter(`${LOGS_GROUP_BY}: ${legend.label}`); - onClose(); - }; - - const handleExcludeStreamToFilter = () => { - onApplyFilter(`(NOT ${LOGS_GROUP_BY}: ${legend.label})`); - onClose(); - }; - - const handlerCopyLabel = async () => { - await copyToClipboard(legend.label, `${legend.label} has been copied`); - onClose(); - }; - - const options: LegendLogHitsMenu[] = [ - { - title: `Copy ${LOGS_GROUP_BY} name`, - icon: , - handler: handlerCopyLabel, - }, - { - title: `Add ${LOGS_GROUP_BY} to filter`, - icon: , - handler: handleAddStreamToFilter, - }, - { - title: `Exclude ${LOGS_GROUP_BY} to filter`, - icon: , - handler: handleExcludeStreamToFilter, - } - ]; - - return ( -
- {options.map(({ icon, title, handler }) => ( - - ))} -
- ); -}; - -export default LegendHitsMenuBase; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuFields.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuFields.tsx deleted file mode 100644 index f568112c4c..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuFields.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { FC, useMemo } from "preact/compat"; -import LegendHitsMenuRow from "./LegendHitsMenuRow"; -import { CopyIcon, FilterIcon, FilterOffIcon } from "../../../Main/Icons"; -import { convertToFieldFilter } from "../../../../utils/logs"; -import { LegendLogHitsMenu } from "../../../../api/types"; -import useCopyToClipboard from "../../../../hooks/useCopyToClipboard"; - -interface Props { - fields: string[]; - onApplyFilter: (value: string) => void; - onClose: () => void; -} - -const LegendHitsMenuFields: FC = ({ fields, onApplyFilter, onClose }) => { - const copyToClipboard = useCopyToClipboard(); - - const handleCopy = (field: string) => async () => { - await copyToClipboard(field, `${field} has been copied`); - onClose(); - }; - - const handleAddToFilter = (field: string) => () => { - onApplyFilter(field); - onClose(); - }; - - const handleExcludeToFilter = (field: string) => () => { - onApplyFilter(`-${field}`); - onClose(); - }; - - const generateFieldMenu = (field: string): LegendLogHitsMenu[] => { - return [ - { - title: "Copy", - icon: , - handler: handleCopy(field), - }, - { - title: "Add to filter", - icon: , - handler: handleAddToFilter(field), - }, - { - title: "Exclude to filter", - icon: , - handler: handleExcludeToFilter(field), - } - ]; - }; - - const fieldsWithMenu: LegendLogHitsMenu[] = useMemo(() => { - return fields.map(field => { - const title = convertToFieldFilter(field); - return { - title, - submenu: generateFieldMenu(title), - }; - }); - }, [fields]); - - return ( -
- {fieldsWithMenu?.map((field) => ( - - ))} -
- ); -}; - -export default LegendHitsMenuFields; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuRow.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuRow.tsx deleted file mode 100644 index 22f8d68d46..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuRow.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { FC, useRef, useState, useEffect, ReactNode } from "preact/compat"; -import classNames from "classnames"; -import Tooltip from "../../../Main/Tooltip/Tooltip"; -import { LegendLogHitsMenu } from "../../../../api/types"; -import { ArrowDropDownIcon } from "../../../Main/Icons"; -import useClickOutside from "../../../../hooks/useClickOutside"; - -interface Props { - title: string | ReactNode; - handler?: () => void; - iconStart?: ReactNode; - iconEnd?: ReactNode; - className?: string; - submenu?: LegendLogHitsMenu[]; -} - -const LegendHitsMenuRow: FC = ({ title, handler, iconStart, iconEnd, className, submenu }) => { - const containerRef = useRef(null); - const titleRef = useRef(null); - const submenuRef = useRef(null); - - const [isOverflownTitle, setIsOverflownTitle] = useState(false); - - const [openSubmenu, setOpenSubmenu] = useState(false); - const [posSubmenuLeft, setPosSubmenuLeft] = useState(false); - const hasSubmenu = !!submenu?.length; - - const handleToggleContextMenu = () => { - setOpenSubmenu(prev => !prev); - }; - - const handleCloseContextMenu = () => { - setOpenSubmenu(false); - }; - - const handleClick = () => { - handler && handler(); - hasSubmenu && handleToggleContextMenu(); - }; - - - useEffect(() => { - if (!titleRef.current) return; - setIsOverflownTitle(titleRef.current.scrollWidth > titleRef.current.clientWidth); - }, [title, titleRef]); - - useEffect(() => { - requestAnimationFrame(() => { - if (!openSubmenu || !submenuRef.current) { - setPosSubmenuLeft(false); - return; - } - - const { left, width } = submenuRef.current.getBoundingClientRect(); - setPosSubmenuLeft(left + width > window.innerWidth); - }); - }, [submenuRef, openSubmenu]); - - useClickOutside(containerRef, handleCloseContextMenu); - - const titleContent = ( -
- {title} -
- ); - - return ( -
- {iconStart &&
{iconStart}
} - {isOverflownTitle ? ({titleContent}) : titleContent} - {iconEnd && !hasSubmenu &&
{iconEnd}
} - - {hasSubmenu && ( -
- -
- )} - - {openSubmenu && submenu && ( -
-
- {submenu.map(({ icon, title, handler }) => ( - - ))} -
-
- )} -
- ); -}; - -export default LegendHitsMenuRow; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuStats.tsx b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuStats.tsx deleted file mode 100644 index e8022ebc65..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/LegendHitsMenuStats.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { FC } from "preact/compat"; -import { LegendLogHits } from "../../../../api/types"; - -interface Props { - legend: LegendLogHits; -} - -const LegendHitsMenuStats: FC = ({ legend }) => { - const totalFormatted = legend.total.toLocaleString("en-US"); - const percentage = Math.round((legend.total / legend.totalHits) * 100); - - return ( -
-
-
- Total: {totalFormatted} ({percentage}%) -
-
-
- ); -}; - -export default LegendHitsMenuStats; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/style.scss b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/style.scss deleted file mode 100644 index 18a095a647..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/LegendHitsMenu/style.scss +++ /dev/null @@ -1,178 +0,0 @@ -@use "src/styles/variables" as *; - -.vm-legend-hits-menu { - min-width: 160px; - z-index: 1; - - &_submenu { - position: absolute; - top: calc(-1 * $padding-small); - background-color: $color-background-block; - left: calc(100% + ($padding-small / 2)); - box-shadow: $box-shadow-popper; - border-radius: $border-radius-small; - animation: vm-submenu-show 150ms cubic-bezier(0.280, 0.840, 0.2, 1); - transform-origin: top left; - - &_left { - left: auto; - right: calc(100% + ($padding-small / 2)); - transform-origin: top right; - } - } - - &-section { - border-bottom: $border-divider; - - &:last-child { - border-bottom: none; - } - } - - &-row { - position: relative; - display: flex; - gap: $padding-small; - align-items: center; - justify-content: flex-start; - padding: 0 $padding-global; - transition: background-color 0.3s; - color: $color-text; - - &_interactive { - cursor: pointer; - - &:hover { - background-color: rgba(0, 0, 0, 0.05); - } - } - - &_info { - font-size: $font-size-small; - font-weight: 500; - padding-block: $padding-small; - } - - &_info &__icon { - color: $color-info; - } - - &__icon { - display: flex; - align-items: center; - justify-content: center; - width: 14px; - height: 14px; - - &_drop { - transform: rotate(-90deg); - } - } - - &__title { - flex-grow: 1; - padding: $padding-global 0; - position: relative; - max-width: 400px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - } - - &-other-list { - width: 80vw; - height: 80vh; - overflow: auto; - - &__search { - position: sticky; - top: 0; - padding: $padding-small 0; - background-color: $color-background-block; - border-bottom: $border-divider; - z-index: 2; - } - - &-row { - border-bottom: $border-divider; - - &_header { - border-bottom: none; - position: sticky; - top: 65px; - background-color: $color-background-block; - z-index: 1; - width: 100%; - - &:after { - content: ''; - position: absolute; - bottom: 0; - left: 0; - width: 100%; - height: 1px; - border-bottom: $border-divider; - } - } - } - - &-cell { - padding: calc($padding-small / 2) 0; - text-align: left; - - &_header { - padding: $padding-small; - font-weight: 500; - } - - &_number { - padding: $padding-small; - text-align: right; - font-variant-numeric: tabular-nums; - } - - &_fields { - width: 100%; - } - } - - &-fields { - display: flex; - flex-wrap: wrap; - align-items: center; - justify-content: flex-start; - - &__field { - padding: calc($padding-small / 2) $padding-small; - border-radius: $border-radius-small; - transition: background-color 0.3s; - - &:hover { - background-color: $color-hover-black; - } - - &:not(:last-child) { - &:after { - content: ','; - } - } - } - } - - &-actions { - display: flex; - align-items: center; - justify-content: center; - } - } -} - -@keyframes vm-submenu-show { - 0% { - opacity: 0; - } - 100% { - opacity: 1; - } -} diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/hooks/useBarHitsOptions.ts b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/hooks/useBarHitsOptions.ts deleted file mode 100644 index 6e5c424f8d..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/hooks/useBarHitsOptions.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { useMemo, useState } from "preact/compat"; -import { getAxes, getMinMaxBuffer, handleDestroy, setSelect } from "../../../../utils/uplot"; -import dayjs from "dayjs"; -import { dateFromSeconds, formatDateForNativeInput } from "../../../../utils/time"; -import uPlot, { AlignedData, Band, Options, Series } from "uplot"; -import { getCssVariable } from "../../../../utils/theme"; -import { useAppState } from "../../../../state/common/StateContext"; -import { MinMax, SetMinMax } from "../../../../types"; -import { LogHits } from "../../../../api/types"; -import getSeriesPaths from "../../../../utils/uplot/paths"; -import { GraphOptions, GRAPH_STYLES } from "../types"; -import { getMaxFromArray } from "../../../../utils/math"; - -const seriesColors = [ - "color-log-hits-bar-1", - "color-log-hits-bar-2", - "color-log-hits-bar-3", - "color-log-hits-bar-4", - "color-log-hits-bar-5", -]; - -const strokeWidth = { - [GRAPH_STYLES.BAR]: 1, - [GRAPH_STYLES.LINE_STEPPED]: 2, - [GRAPH_STYLES.LINE]: 1.2, - [GRAPH_STYLES.POINTS]: 0, -}; - -interface UseGetBarHitsOptionsArgs { - data: AlignedData; - logHits: LogHits[]; - xRange: MinMax; - bands?: Band[]; - containerSize: { width: number, height: number }; - setPlotScale: SetMinMax; - onReadyChart: (u: uPlot) => void; - graphOptions: GraphOptions; -} - -export const OTHER_HITS_LABEL = "other"; - -export const getLabelFromLogHit = (logHit: LogHits) => { - if (logHit?._isOther) return OTHER_HITS_LABEL; - const fields = Object.values(logHit?.fields || {}); - return fields.map((value) => value || "\"\"").join(", "); -}; - -const getYRange = (u: uPlot, _initMin = 0, initMax = 1) => { - const maxValues = u.series.filter(({ scale }) => scale === "y").map(({ max }) => max || initMax); - const max = getMaxFromArray(maxValues); - return getMinMaxBuffer(0, max || initMax); -}; - -const useBarHitsOptions = ({ - data, - logHits, - xRange, - bands, - containerSize, - onReadyChart, - setPlotScale, - graphOptions -}: UseGetBarHitsOptionsArgs) => { - const { isDarkTheme } = useAppState(); - - const [focusDataIdx, setFocusDataIdx] = useState(-1); - - const setCursor = (u: uPlot) => { - const dataIdx = u.cursor.idx ?? -1; - setFocusDataIdx(dataIdx); - }; - - const series: Series[] = useMemo(() => { - let visibleColorIndex = 0; - - return data.map((_d, i) => { - if (i === 0) return {}; // x-axis - - const logHit = logHits?.[i - 1]; - const label = getLabelFromLogHit(logHit); - - const isOther = logHit?._isOther; - const colorVar = isOther - ? "color-log-hits-bar-0" - : seriesColors[visibleColorIndex++]; - - const color = getCssVariable(colorVar); - - return { - label, - width: strokeWidth[graphOptions.graphStyle], - spanGaps: true, - show: true, - stroke: color, - fill: graphOptions.fill && !isOther ? `${color}80` : graphOptions.fill ? color : "", - paths: getSeriesPaths(graphOptions.graphStyle), - }; - }); - }, [isDarkTheme, data, graphOptions]); - - const options: Options = { - series, - bands, - width: containerSize.width || (window.innerWidth / 2), - height: containerSize.height || 200, - cursor: { - points: { - width: (u, seriesIdx, size) => size / 4, - size: (u, seriesIdx) => (u.series?.[seriesIdx]?.points?.size || 1) * 1.5, - stroke: (u, seriesIdx) => `${series?.[seriesIdx]?.stroke || "#ffffff"}`, - fill: () => "#ffffff", - }, - }, - scales: { - x: { - time: true, - range: () => [xRange.min, xRange.max] - }, - y: { - range: getYRange - } - }, - hooks: { - drawSeries: [], - ready: [onReadyChart], - setCursor: [setCursor], - setSelect: [setSelect(setPlotScale)], - destroy: [handleDestroy], - }, - legend: { show: false }, - axes: getAxes([{}, { scale: "y" }]), - tzDate: ts => dayjs(formatDateForNativeInput(dateFromSeconds(ts))).local().toDate(), - }; - - return { - options, - series, - focusDataIdx, - }; -}; - -export default useBarHitsOptions; diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/style.scss b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/style.scss deleted file mode 100644 index 303dfd2caa..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/style.scss +++ /dev/null @@ -1,22 +0,0 @@ -@use "src/styles/variables" as *; - -.vm-bar-hits-chart { - position: relative; - width: 100%; - height: 200px; - - &__wrapper { - display: flex; - flex-direction: column; - width: 100%; - height: 100%; - - &_hidden { - min-height: 90px; - } - } - - &_panning { - pointer-events: none; - } -} diff --git a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/types.ts b/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/types.ts deleted file mode 100644 index ae60e90664..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/BarHitsChart/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -export enum GRAPH_STYLES { - BAR = "Bars", - LINE = "Lines", - LINE_STEPPED = "Stepped lines", - POINTS = "Points", -} - -export interface GraphOptions { - graphStyle: GRAPH_STYLES; - stacked: boolean; - fill: boolean; - hideChart: boolean; -} diff --git a/app/vmui/packages/vmui/src/components/Chart/GraphTips/GraphTips.tsx b/app/vmui/packages/vmui/src/components/Chart/GraphTips/GraphTips.tsx deleted file mode 100644 index dff88a43a2..0000000000 --- a/app/vmui/packages/vmui/src/components/Chart/GraphTips/GraphTips.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { FC } from "preact/compat"; -import "./style.scss"; -import Button from "../../Main/Button/Button"; -import { TipIcon } from "../../Main/Icons"; -import Tooltip from "../../Main/Tooltip/Tooltip"; -import Modal from "../../Main/Modal/Modal"; -import useBoolean from "../../../hooks/useBoolean"; -import tips from "./constants/tips"; - -const GraphTips: FC = () => { - const { - value: showTips, - setFalse: handleCloseTips, - setTrue: handleOpenTips - } = useBoolean(false); - - return ( - <> - -
diff --git a/app/vmui/packages/vmui/src/components/Main/Hyperlink/Hyperlink.tsx b/app/vmui/packages/vmui/src/components/Main/Hyperlink/Hyperlink.tsx index d870e9acc7..10d88ee0a8 100644 --- a/app/vmui/packages/vmui/src/components/Main/Hyperlink/Hyperlink.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Hyperlink/Hyperlink.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode } from "preact/compat"; +import { FC, ReactNode } from "react"; import classNames from "classnames"; interface Hyperlink { diff --git a/app/vmui/packages/vmui/src/components/Main/Icons/PreviewIcons.tsx b/app/vmui/packages/vmui/src/components/Main/Icons/PreviewIcons.tsx index 3a31cc3fde..7f0da5000f 100644 --- a/app/vmui/packages/vmui/src/components/Main/Icons/PreviewIcons.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Icons/PreviewIcons.tsx @@ -1,4 +1,4 @@ -import { FC } from "preact/compat"; +import { FC } from "react"; import * as icons from "./index"; import useCopyToClipboard from "../../../hooks/useCopyToClipboard"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx index ffa5cbb77f..6651e5cbc1 100644 --- a/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Icons/index.tsx @@ -5,7 +5,7 @@ export const LogoTracesIcon = () => ( viewBox="285.258 277.826 94.444 33.1" fill="currentColor" > - + ); diff --git a/app/vmui/packages/vmui/src/components/Main/LineLoader/LineLoader.tsx b/app/vmui/packages/vmui/src/components/Main/LineLoader/LineLoader.tsx index 394b3a8f5c..9d9a8e58e3 100644 --- a/app/vmui/packages/vmui/src/components/Main/LineLoader/LineLoader.tsx +++ b/app/vmui/packages/vmui/src/components/Main/LineLoader/LineLoader.tsx @@ -1,4 +1,4 @@ -import { FC } from "preact/compat"; +import { FC } from "react"; import "./style.scss"; const LineLoader: FC = () => { diff --git a/app/vmui/packages/vmui/src/components/Main/Modal/Modal.tsx b/app/vmui/packages/vmui/src/components/Main/Modal/Modal.tsx index dbebab692d..ca8798cee9 100644 --- a/app/vmui/packages/vmui/src/components/Main/Modal/Modal.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Modal/Modal.tsx @@ -1,10 +1,12 @@ -import { FC, useCallback, useEffect, createPortal, ReactNode, MouseEvent } from "preact/compat"; +import { FC, useCallback, useEffect, ReactNode, MouseEvent } from "react"; +import { createPortal } from "react-dom" import { CloseIcon } from "../Icons"; import Button from "../Button/Button"; import "./style.scss"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import classNames from "classnames"; -import { useLocation, useNavigate } from "react-router-dom"; +import { useLocation } from "react-router-dom"; +import { useNavigate } from "../../../router/useNavigate"; import useEventListener from "../../../hooks/useEventListener"; interface ModalProps { diff --git a/app/vmui/packages/vmui/src/components/Main/Pagination/Pagination.tsx b/app/vmui/packages/vmui/src/components/Main/Pagination/Pagination.tsx index 2bc948dabe..5a6737810f 100644 --- a/app/vmui/packages/vmui/src/components/Main/Pagination/Pagination.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Pagination/Pagination.tsx @@ -1,5 +1,5 @@ import { ArrowDownIcon } from "../Icons"; -import { FC, useMemo } from "preact/compat"; +import { FC, useMemo } from "react"; import classNames from "classnames"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/Pagination/SelectLimit/SelectLimit.tsx b/app/vmui/packages/vmui/src/components/Main/Pagination/SelectLimit/SelectLimit.tsx index 9329bb644d..fe5a09a85b 100644 --- a/app/vmui/packages/vmui/src/components/Main/Pagination/SelectLimit/SelectLimit.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Pagination/SelectLimit/SelectLimit.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useRef } from "preact/compat"; +import { FC, useMemo, useRef } from "react"; import { ArrowDropDownIcon } from "../../Icons"; import useBoolean from "../../../../hooks/useBoolean"; import Popper from "../../Popper/Popper"; diff --git a/app/vmui/packages/vmui/src/components/Main/Popper/Popper.tsx b/app/vmui/packages/vmui/src/components/Main/Popper/Popper.tsx index 5bdb50090c..d78c7a556d 100644 --- a/app/vmui/packages/vmui/src/components/Main/Popper/Popper.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Popper/Popper.tsx @@ -7,16 +7,17 @@ import { useRef, useState, useCallback, - createPortal, RefObject -} from "preact/compat"; +} from "react"; +import { createPortal } from "react-dom" import classNames from "classnames"; import "./style.scss"; import useClickOutside from "../../../hooks/useClickOutside"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import Button from "../Button/Button"; import { CloseIcon } from "../Icons"; -import { useLocation, useNavigate } from "react-router-dom"; +import { useLocation } from "react-router-dom"; +import { useNavigate } from "../../../router/useNavigate"; import useEventListener from "../../../hooks/useEventListener"; interface PopperProps { diff --git a/app/vmui/packages/vmui/src/components/Main/Select/MultipleSelectedValue/MultipleSelectedValue.tsx b/app/vmui/packages/vmui/src/components/Main/Select/MultipleSelectedValue/MultipleSelectedValue.tsx index aed41b3705..bd843c9f31 100644 --- a/app/vmui/packages/vmui/src/components/Main/Select/MultipleSelectedValue/MultipleSelectedValue.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Select/MultipleSelectedValue/MultipleSelectedValue.tsx @@ -1,4 +1,4 @@ -import { FC, MouseEvent } from "preact/compat"; +import { FC, MouseEvent } from "react"; import useDeviceDetect from "../../../../hooks/useDeviceDetect"; import { CloseIcon } from "../../Icons"; diff --git a/app/vmui/packages/vmui/src/components/Main/Select/Select.tsx b/app/vmui/packages/vmui/src/components/Main/Select/Select.tsx index 5cbc090a48..dc4791251f 100644 --- a/app/vmui/packages/vmui/src/components/Main/Select/Select.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Select/Select.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useMemo, useRef, useState, RefObject, FormEvent, MouseEvent } from "preact/compat"; +import { FC, useEffect, useMemo, useRef, useState, RefObject, FormEvent, MouseEvent } from "react"; import classNames from "classnames"; import { ArrowDropDownIcon, CloseIcon } from "../Icons"; import Autocomplete from "../Autocomplete/Autocomplete"; diff --git a/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/ShortcutKeys.tsx b/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/ShortcutKeys.tsx index 04f2cdfbe6..227502db5b 100644 --- a/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/ShortcutKeys.tsx +++ b/app/vmui/packages/vmui/src/components/Main/ShortcutKeys/ShortcutKeys.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback } from "preact/compat"; +import { FC, useCallback } from "react"; import { getAppModeEnable } from "../../../utils/app-mode"; import Button from "../Button/Button"; import { KeyboardIcon } from "../Icons"; diff --git a/app/vmui/packages/vmui/src/components/Main/Switch/Switch.tsx b/app/vmui/packages/vmui/src/components/Main/Switch/Switch.tsx index eb15df28fd..e3569e8d12 100644 --- a/app/vmui/packages/vmui/src/components/Main/Switch/Switch.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Switch/Switch.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode } from "preact/compat"; +import { FC, ReactNode } from "react"; import classNames from "classnames"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/Tabs/TabItem.tsx b/app/vmui/packages/vmui/src/components/Main/Tabs/TabItem.tsx index 8d942fa04d..1b1d3cf684 100644 --- a/app/vmui/packages/vmui/src/components/Main/Tabs/TabItem.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Tabs/TabItem.tsx @@ -1,4 +1,4 @@ -import { Component, FC, Ref } from "preact/compat"; +import { Component, FC, Ref } from "react"; import classNames from "classnames"; import { getCssVariable } from "../../../utils/theme"; import { TabItemType } from "./Tabs"; diff --git a/app/vmui/packages/vmui/src/components/Main/Tabs/TabItemWrapper.tsx b/app/vmui/packages/vmui/src/components/Main/Tabs/TabItemWrapper.tsx index 2bc237ec10..d4afca06e4 100644 --- a/app/vmui/packages/vmui/src/components/Main/Tabs/TabItemWrapper.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Tabs/TabItemWrapper.tsx @@ -1,4 +1,4 @@ -import { FC, ReactNode } from "preact/compat"; +import { FC, ReactNode } from "react"; import { NavLink } from "react-router-dom"; interface TabItemWrapperProps { diff --git a/app/vmui/packages/vmui/src/components/Main/Tabs/Tabs.tsx b/app/vmui/packages/vmui/src/components/Main/Tabs/Tabs.tsx index e140e335bc..e40f2ac35c 100644 --- a/app/vmui/packages/vmui/src/components/Main/Tabs/Tabs.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Tabs/Tabs.tsx @@ -1,4 +1,4 @@ -import { Component, FC, useRef, useState, ReactNode, useEffect } from "preact/compat"; +import { Component, FC, useRef, useState, ReactNode, useEffect } from "react"; import { getCssVariable } from "../../../utils/theme"; import TabItem from "./TabItem"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/TextField/TextField.tsx b/app/vmui/packages/vmui/src/components/Main/TextField/TextField.tsx index b018d4f6af..ac9fcf3ad4 100644 --- a/app/vmui/packages/vmui/src/components/Main/TextField/TextField.tsx +++ b/app/vmui/packages/vmui/src/components/Main/TextField/TextField.tsx @@ -8,7 +8,7 @@ import { MouseEvent, HTMLInputTypeAttribute, ReactNode -} from "preact/compat"; +} from "react"; import classNames from "classnames"; import { useAppState } from "../../../state/common/StateContext"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; diff --git a/app/vmui/packages/vmui/src/components/Main/TextField/TextFieldMessage.tsx b/app/vmui/packages/vmui/src/components/Main/TextField/TextFieldMessage.tsx index d7bf97cf5f..aea03c2764 100644 --- a/app/vmui/packages/vmui/src/components/Main/TextField/TextFieldMessage.tsx +++ b/app/vmui/packages/vmui/src/components/Main/TextField/TextFieldMessage.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useMemo, useRef, useState } from "preact/compat"; +import { FC, useEffect, useMemo, useRef, useState } from "react"; import useEventListener from "../../../hooks/useEventListener"; import classNames from "classnames"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/ThemeProvider/ThemeProvider.ts b/app/vmui/packages/vmui/src/components/Main/ThemeProvider/ThemeProvider.ts index 4ea1c65a80..f2e17305f1 100644 --- a/app/vmui/packages/vmui/src/components/Main/ThemeProvider/ThemeProvider.ts +++ b/app/vmui/packages/vmui/src/components/Main/ThemeProvider/ThemeProvider.ts @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "preact/compat"; +import { FC, useEffect, useState } from "react"; import { getContrastColor } from "../../../utils/color"; import { getCssVariable, isSystemDark, setCssVariable } from "../../../utils/theme"; import { AppParams, getAppModeEnable, getAppModeParams } from "../../../utils/app-mode"; diff --git a/app/vmui/packages/vmui/src/components/Main/Toggle/Toggle.tsx b/app/vmui/packages/vmui/src/components/Main/Toggle/Toggle.tsx index 786641a44a..0031588e62 100644 --- a/app/vmui/packages/vmui/src/components/Main/Toggle/Toggle.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Toggle/Toggle.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef, useState, ReactNode } from "preact/compat"; +import { FC, useEffect, useRef, useState, ReactNode } from "react"; import classNames from "classnames"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/components/Main/Tooltip/Tooltip.tsx b/app/vmui/packages/vmui/src/components/Main/Tooltip/Tooltip.tsx index 7164a2dbfd..a9c1bbfcd9 100644 --- a/app/vmui/packages/vmui/src/components/Main/Tooltip/Tooltip.tsx +++ b/app/vmui/packages/vmui/src/components/Main/Tooltip/Tooltip.tsx @@ -1,4 +1,5 @@ -import { FC, useEffect, useMemo, useRef, useState, Fragment, createPortal, ReactNode } from "preact/compat"; +import { FC, useEffect, useMemo, useRef, useState, Fragment, ReactNode } from "react"; +import { createPortal } from "react-dom" import "./style.scss"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; @@ -22,7 +23,7 @@ const Tooltip: FC = ({ const [isOpen, setIsOpen] = useState(false); const [popperSize, setPopperSize] = useState({ width: 0, height: 0 }); - const buttonRef = useRef(null); + const buttonRef = useRef(null); const popperRef = useRef(null); const onScrollWindow = () => setIsOpen(false); @@ -111,11 +112,11 @@ const Tooltip: FC = ({ return ( <> - {children} - +
{!isMobile && isOpen && createPortal((
{ diff --git a/app/vmui/packages/vmui/src/hooks/useElementSize.ts b/app/vmui/packages/vmui/src/hooks/useElementSize.ts index d6609046c3..3cf6c20002 100644 --- a/app/vmui/packages/vmui/src/hooks/useElementSize.ts +++ b/app/vmui/packages/vmui/src/hooks/useElementSize.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from "preact/compat"; +import { useCallback, useEffect, useState } from "react"; import useEventListener from "./useEventListener"; export interface ElementSize { diff --git a/app/vmui/packages/vmui/src/hooks/useEventListener.ts b/app/vmui/packages/vmui/src/hooks/useEventListener.ts index 19b597e236..0ce2f1729e 100644 --- a/app/vmui/packages/vmui/src/hooks/useEventListener.ts +++ b/app/vmui/packages/vmui/src/hooks/useEventListener.ts @@ -1,4 +1,4 @@ -import { RefObject, useEffect, useRef } from "preact/compat"; +import { RefObject, useEffect, useRef } from "react"; // MediaQueryList Event based useEventListener interface function useEventListener( diff --git a/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts b/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts index 47086646f6..9b47d28037 100644 --- a/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts +++ b/app/vmui/packages/vmui/src/hooks/useFetchAppConfig.ts @@ -1,5 +1,5 @@ import { useAppDispatch } from "../state/common/StateContext"; -import { useEffect, useState } from "preact/compat"; +import { useEffect, useState } from "react"; import { ErrorTypes } from "../types"; const useFetchFlags = () => { diff --git a/app/vmui/packages/vmui/src/hooks/useLocalStorageBoolean.ts b/app/vmui/packages/vmui/src/hooks/useLocalStorageBoolean.ts index d0bba3aaae..7055ad330a 100644 --- a/app/vmui/packages/vmui/src/hooks/useLocalStorageBoolean.ts +++ b/app/vmui/packages/vmui/src/hooks/useLocalStorageBoolean.ts @@ -1,4 +1,4 @@ -import { useMemo, useState, useCallback } from "preact/compat"; +import { useMemo, useState, useCallback } from "react"; import { getFromStorage, saveToStorage, StorageKeys } from "../utils/storage"; import useEventListener from "./useEventListener"; diff --git a/app/vmui/packages/vmui/src/hooks/usePrevious.ts b/app/vmui/packages/vmui/src/hooks/usePrevious.ts index d13e861e0d..05744458ac 100644 --- a/app/vmui/packages/vmui/src/hooks/usePrevious.ts +++ b/app/vmui/packages/vmui/src/hooks/usePrevious.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "preact/compat"; +import { useEffect, useRef } from "react"; function usePrevious(value: T): T | undefined { const ref = useRef(); diff --git a/app/vmui/packages/vmui/src/hooks/useQuickAutocomplete.ts b/app/vmui/packages/vmui/src/hooks/useQuickAutocomplete.ts index f1623d273b..d3889b74d4 100644 --- a/app/vmui/packages/vmui/src/hooks/useQuickAutocomplete.ts +++ b/app/vmui/packages/vmui/src/hooks/useQuickAutocomplete.ts @@ -1,4 +1,4 @@ -import { useCallback } from "preact/compat"; +import { useCallback } from "react"; import useEventListener from "./useEventListener"; import { useQueryDispatch } from "../state/query/QueryStateContext"; diff --git a/app/vmui/packages/vmui/src/hooks/useSearchParamsFromObject.ts b/app/vmui/packages/vmui/src/hooks/useSearchParamsFromObject.ts index 592ab52c28..08fbe4dbec 100644 --- a/app/vmui/packages/vmui/src/hooks/useSearchParamsFromObject.ts +++ b/app/vmui/packages/vmui/src/hooks/useSearchParamsFromObject.ts @@ -1,5 +1,7 @@ -import { useNavigate, useSearchParams } from "react-router-dom"; -import { useCallback } from "preact/compat"; +import { useNavigate } from "../router/useNavigate"; +import { useSearchParams } from "../router/useSearchParams"; +import { useCallback } from "react"; + const useSearchParamsFromObject = () => { diff --git a/app/vmui/packages/vmui/src/hooks/useSortedCategories.ts b/app/vmui/packages/vmui/src/hooks/useSortedCategories.ts index e284fb5992..c712d7d8c3 100644 --- a/app/vmui/packages/vmui/src/hooks/useSortedCategories.ts +++ b/app/vmui/packages/vmui/src/hooks/useSortedCategories.ts @@ -1,4 +1,4 @@ -import { useMemo } from "preact/compat"; +import { useMemo } from "react"; import { MetricBase } from "../api/types"; export type MetricCategory = { diff --git a/app/vmui/packages/vmui/src/hooks/useStateSearchParams.ts b/app/vmui/packages/vmui/src/hooks/useStateSearchParams.ts index 3a88220ff6..3699822708 100644 --- a/app/vmui/packages/vmui/src/hooks/useStateSearchParams.ts +++ b/app/vmui/packages/vmui/src/hooks/useStateSearchParams.ts @@ -1,5 +1,5 @@ -import { Dispatch, useState, useEffect, SetStateAction } from "preact/compat"; -import { useSearchParams } from "react-router-dom"; +import { Dispatch, useState, useEffect, SetStateAction } from "react"; +import { useSearchParams } from "../router/useSearchParams"; const useStateSearchParams = (defaultState: T, key: string): [T, Dispatch>] => { const [searchParams] = useSearchParams(); diff --git a/app/vmui/packages/vmui/src/hooks/useSystemTheme.ts b/app/vmui/packages/vmui/src/hooks/useSystemTheme.ts index 24cd0bcaa8..6eddd4bd80 100644 --- a/app/vmui/packages/vmui/src/hooks/useSystemTheme.ts +++ b/app/vmui/packages/vmui/src/hooks/useSystemTheme.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "preact/compat"; +import { useEffect, useState } from "react"; import { isSystemDark } from "../utils/theme"; const useThemeDetector = () => { diff --git a/app/vmui/packages/vmui/src/hooks/useTenant.ts b/app/vmui/packages/vmui/src/hooks/useTenant.ts index 08c869c795..d26d47b48b 100644 --- a/app/vmui/packages/vmui/src/hooks/useTenant.ts +++ b/app/vmui/packages/vmui/src/hooks/useTenant.ts @@ -1,5 +1,5 @@ -import { useMemo } from "preact/compat"; -import { useSearchParams } from "react-router-dom"; +import { useMemo } from "react"; +import { useSearchParams } from "../router/useSearchParams"; export const useTenant = () => { const [searchParams] = useSearchParams(); diff --git a/app/vmui/packages/vmui/src/hooks/useUnmount.ts b/app/vmui/packages/vmui/src/hooks/useUnmount.ts index f0eab10a43..4942709e56 100644 --- a/app/vmui/packages/vmui/src/hooks/useUnmount.ts +++ b/app/vmui/packages/vmui/src/hooks/useUnmount.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "preact/compat"; +import { useEffect, useRef } from "react"; export function useUnmount(fn: () => void) { const fnRef = useRef(fn); diff --git a/app/vmui/packages/vmui/src/hooks/useWindowSize.ts b/app/vmui/packages/vmui/src/hooks/useWindowSize.ts index a497484606..35332bd2e8 100644 --- a/app/vmui/packages/vmui/src/hooks/useWindowSize.ts +++ b/app/vmui/packages/vmui/src/hooks/useWindowSize.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from "preact/compat"; +import { useEffect, useState } from "react"; import useEventListener from "./useEventListener"; interface WindowSize { diff --git a/app/vmui/packages/vmui/src/index.tsx b/app/vmui/packages/vmui/src/index.tsx index a98782da35..add40f4ae9 100644 --- a/app/vmui/packages/vmui/src/index.tsx +++ b/app/vmui/packages/vmui/src/index.tsx @@ -1,14 +1,15 @@ -import { render } from "preact/compat"; +import React from "react"; +import { createRoot } from "react-dom/client"; + import "./constants/dayjsPlugins"; -import reportWebVitals from "./reportWebVitals"; import "./styles/style.scss"; import App from "./App"; -const root = document.getElementById("root"); -if (root) render(, root); +const container = document.getElementById("root"); +if (!container) { + throw new Error("Root container missing in index.html"); +} -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +const root = createRoot(container); +root.render(); \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/jaeger/JaegerRoutesInVmui.tsx b/app/vmui/packages/vmui/src/jaeger/JaegerRoutesInVmui.tsx new file mode 100644 index 0000000000..24df6a268e --- /dev/null +++ b/app/vmui/packages/vmui/src/jaeger/JaegerRoutesInVmui.tsx @@ -0,0 +1,55 @@ +// src/jaeger/JaegerRoutesInVmui.tsx +import React from "react"; +import { Provider } from "react-redux"; +import { Route, Redirect, Switch } from "react-router-dom"; + +// Jaeger UI components +import NotFound from "jaeger-ui-lite/src/components/App/NotFound"; +import Page from "jaeger-ui-lite/src/components/App/Page"; +import TraceSearchPage from "jaeger-ui-lite/src/components/TraceSearchPage"; +import { ROUTE_PATH as traceSearchPath } from "jaeger-ui-lite/src/components/TraceSearchPage/url"; + +// Jaeger runtime init +import JaegerAPI, { DEFAULT_API_ROOT } from "jaeger-ui-lite/src/api/jaeger"; +import processScripts from "jaeger-ui-lite/src/utils/config/process-scripts"; +import prefixUrl from "jaeger-ui-lite/src/utils/prefix-url"; +import { store } from "jaeger-ui-lite/src/utils/configure-store"; + +// Jaeger styles +import "jaeger-ui-lite/src/components/common/vars.css"; +import "jaeger-ui-lite/src/components/common/utils.css"; +import "antd/dist/reset.css"; +// import "jaeger-ui-lite/src/components/App/index.css"; +import './ub.css' + + +JaegerAPI.apiRoot = DEFAULT_API_ROOT; +processScripts(); + +export default function JaegerRoutesInVmui() { + return ( + + {/* @ts-ignore */} + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/app/vmui/packages/vmui/src/jaeger/ub.css b/app/vmui/packages/vmui/src/jaeger/ub.css new file mode 100644 index 0000000000..fb33037f2c --- /dev/null +++ b/app/vmui/packages/vmui/src/jaeger/ub.css @@ -0,0 +1,744 @@ +.ub-flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex +} + +@media (min-width: 40em) { + .ub-sm-flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex + } +} + +@media (min-width: 52em) { + .ub-md-flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex + } +} + +@media (min-width: 64em) { + .ub-lg-flex { + display: -webkit-box; + display: -ms-flexbox; + display: flex + } +} + +.ub-flex-column { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column +} + +.ub-flex-wrap { + -ms-flex-wrap: wrap; + flex-wrap: wrap +} + +.ub-items-start { + -webkit-box-align: start; + -ms-flex-align: start; + align-items: flex-start +} + +.ub-items-end { + -webkit-box-align: end; + -ms-flex-align: end; + align-items: flex-end +} + +.ub-items-center { + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center +} + +.ub-items-baseline { + -webkit-box-align: baseline; + -ms-flex-align: baseline; + align-items: baseline +} + +.ub-items-stretch { + -webkit-box-align: stretch; + -ms-flex-align: stretch; + align-items: stretch +} + +.ub-self-start { + -ms-flex-item-align: start; + align-self: flex-start +} + +.ub-self-end { + -ms-flex-item-align: end; + align-self: flex-end +} + +.ub-self-center { + -ms-flex-item-align: center; + align-self: center +} + +.ub-self-baseline { + -ms-flex-item-align: baseline; + align-self: baseline +} + +.ub-self-stretch { + -ms-flex-item-align: stretch; + align-self: stretch +} + +.ub-justify-start { + -webkit-box-pack: start; + -ms-flex-pack: start; + justify-content: flex-start +} + +.ub-justify-end { + -webkit-box-pack: end; + -ms-flex-pack: end; + justify-content: flex-end +} + +.ub-justify-center { + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center +} + +.ub-justify-between { + -webkit-box-pack: justify; + -ms-flex-pack: justify; + justify-content: space-between +} + +.ub-justify-around { + -ms-flex-pack: distribute; + justify-content: space-around +} + +.ub-content-start { + -ms-flex-line-pack: start; + align-content: flex-start +} + +.ub-content-end { + -ms-flex-line-pack: end; + align-content: flex-end +} + +.ub-content-center { + -ms-flex-line-pack: center; + align-content: center +} + +.ub-content-between { + -ms-flex-line-pack: justify; + align-content: space-between +} + +.ub-content-around { + -ms-flex-line-pack: distribute; + align-content: space-around +} + +.ub-content-stretch { + -ms-flex-line-pack: stretch; + align-content: stretch +} + +.ub-flex-auto { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + min-width: 0; + min-height: 0 +} + +.ub-flex-none { + -webkit-box-flex: 0; + -ms-flex: none; + flex: none +} + +.ub-order-0 { + -webkit-box-ordinal-group: 1; + -ms-flex-order: 0; + order: 0 +} + +.ub-order-1 { + -webkit-box-ordinal-group: 2; + -ms-flex-order: 1; + order: 1 +} + +.ub-order-2 { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2 +} + +.ub-order-3 { + -webkit-box-ordinal-group: 4; + -ms-flex-order: 3; + order: 3 +} + +.ub-order-last { + -webkit-box-ordinal-group: 100000; + -ms-flex-order: 99999; + order: 99999 +} + +.ub-inline { + display: inline +} + +.ub-block { + display: block +} + +.ub-inline-block { + display: inline-block +} + +.ub-table { + display: table +} + +.ub-table-cell { + display: table-cell +} + +.ub-overflow-hidden { + overflow: hidden +} + +.ub-overflow-scroll { + overflow: scroll +} + +.ub-overflow-auto { + overflow: auto +} + +.ub-clearfix:before, +.ub-clearfix:after { + content: " "; + display: table +} + +.ub-clearfix:after { + clear: both +} + +.ub-left { + float: left +} + +.ub-right { + float: right +} + +.ub-fit { + max-width: 100% +} + +.ub-max-width-1 { + max-width: 24rem +} + +.ub-max-width-2 { + max-width: 32rem +} + +.ub-max-width-3 { + max-width: 48rem +} + +.ub-max-width-4 { + max-width: 64rem +} + +.ub-border-box { + -webkit-box-sizing: border-box; + box-sizing: border-box +} + +.ub-m0 { + margin: 0 +} + +.ub-mt0 { + margin-top: 0 +} + +.ub-mr0 { + margin-right: 0 +} + +.ub-mb0 { + margin-bottom: 0 +} + +.ub-ml0 { + margin-left: 0 +} + +.ub-mx0 { + margin-left: 0; + margin-right: 0 +} + +.ub-my0 { + margin-top: 0; + margin-bottom: 0 +} + +.ub-m1 { + margin: .25rem +} + +.ub-mt1 { + margin-top: .25rem +} + +.ub-mr1 { + margin-right: .25rem +} + +.ub-mb1 { + margin-bottom: .25rem +} + +.ub-ml1 { + margin-left: .25rem +} + +.ub-mx1 { + margin-left: .25rem; + margin-right: .25rem +} + +.ub-my1 { + margin-top: .25rem; + margin-bottom: .25rem +} + +.ub-m2 { + margin: .5rem +} + +.ub-mt2 { + margin-top: .5rem +} + +.ub-mr2 { + margin-right: .5rem +} + +.ub-mb2 { + margin-bottom: .5rem +} + +.ub-ml2 { + margin-left: .5rem +} + +.ub-mx2 { + margin-left: .5rem; + margin-right: .5rem +} + +.ub-my2 { + margin-top: .5rem; + margin-bottom: .5rem +} + +.ub-m3 { + margin: 1rem +} + +.ub-mt3 { + margin-top: 1rem +} + +.ub-mr3 { + margin-right: 1rem +} + +.ub-mb3 { + margin-bottom: 1rem +} + +.ub-ml3 { + margin-left: 1rem +} + +.ub-mx3 { + margin-left: 1rem; + margin-right: 1rem +} + +.ub-my3 { + margin-top: 1rem; + margin-bottom: 1rem +} + +.ub-m4 { + margin: 2rem +} + +.ub-mt4 { + margin-top: 2rem +} + +.ub-mr4 { + margin-right: 2rem +} + +.ub-mb4 { + margin-bottom: 2rem +} + +.ub-ml4 { + margin-left: 2rem +} + +.ub-mx4 { + margin-left: 2rem; + margin-right: 2rem +} + +.ub-my4 { + margin-top: 2rem; + margin-bottom: 2rem +} + +.ub-mxn1 { + margin-left: -.25rem; + margin-right: -.25rem +} + +.ub-mxn2 { + margin-left: -.5rem; + margin-right: -.5rem +} + +.ub-mxn3 { + margin-left: -1rem; + margin-right: -1rem +} + +.ub-mxn4 { + margin-left: -2rem; + margin-right: -2rem +} + +.ub-ml-auto { + margin-left: auto +} + +.ub-mr-auto { + margin-right: auto +} + +.ub-mx-auto { + margin-left: auto; + margin-right: auto +} + +.ub-p0 { + padding: 0 +} + +.ub-pt0 { + padding-top: 0 +} + +.ub-pr0 { + padding-right: 0 +} + +.ub-pb0 { + padding-bottom: 0 +} + +.ub-pl0 { + padding-left: 0 +} + +.ub-px0 { + padding-left: 0; + padding-right: 0 +} + +.ub-py0 { + padding-top: 0; + padding-bottom: 0 +} + +.ub-p1 { + padding: .25rem +} + +.ub-pt1 { + padding-top: .25rem +} + +.ub-pr1 { + padding-right: .25rem +} + +.ub-pb1 { + padding-bottom: .25rem +} + +.ub-pl1 { + padding-left: .25rem +} + +.ub-py1 { + padding-top: .25rem; + padding-bottom: .25rem +} + +.ub-px1 { + padding-left: .25rem; + padding-right: .25rem +} + +.ub-p2 { + padding: .5rem +} + +.ub-pt2 { + padding-top: .5rem +} + +.ub-pr2 { + padding-right: .5rem +} + +.ub-pb2 { + padding-bottom: .5rem +} + +.ub-pl2 { + padding-left: .5rem +} + +.ub-py2 { + padding-top: .5rem; + padding-bottom: .5rem +} + +.ub-px2 { + padding-left: .5rem; + padding-right: .5rem +} + +.ub-p3 { + padding: 1rem +} + +.ub-pt3 { + padding-top: 1rem +} + +.ub-pr3 { + padding-right: 1rem +} + +.ub-pb3 { + padding-bottom: 1rem +} + +.ub-pl3 { + padding-left: 1rem +} + +.ub-py3 { + padding-top: 1rem; + padding-bottom: 1rem +} + +.ub-px3 { + padding-left: 1rem; + padding-right: 1rem +} + +.ub-p4 { + padding: 2rem +} + +.ub-pt4 { + padding-top: 2rem +} + +.ub-pr4 { + padding-right: 2rem +} + +.ub-pb4 { + padding-bottom: 2rem +} + +.ub-pl4 { + padding-left: 2rem +} + +.ub-py4 { + padding-top: 2rem; + padding-bottom: 2rem +} + +.ub-px4 { + padding-left: 2rem; + padding-right: 2rem +} + +.ub-relative { + position: relative +} + +.ub-absolute { + position: absolute +} + +.ub-fixed { + position: fixed +} + +.ub-top-0 { + top: 0 +} + +.ub-right-0 { + right: 0 +} + +.ub-bottom-0 { + bottom: 0 +} + +.ub-left-0 { + left: 0 +} + +.ub-z1 { + z-index: 1 +} + +.ub-z2 { + z-index: 2 +} + +.ub-z3 { + z-index: 3 +} + +.ub-z4 { + z-index: 4 +} + +.ub-font-family-inherit { + font-family: inherit +} + +.ub-font-size-inherit { + font-size: inherit +} + +.ub-text-decoration-none { + text-decoration: none +} + +.ub-bold { + font-weight: 700 +} + +.ub-regular { + font-weight: 400 +} + +.ub-italic { + font-style: italic +} + +.ub-caps { + text-transform: uppercase; + letter-spacing: .2em +} + +.ub-tx-left-align { + text-align: left +} + +.ub-tx-center { + text-align: center +} + +.ub-tx-right-align { + text-align: right +} + +.ub-tx-justify { + text-align: justify +} + +.ub-nowrap { + white-space: nowrap +} + +.ub-break-word { + word-wrap: break-word +} + +.ub-line-height-noraml { + line-height: normal +} + +.ub-line-height-1 { + line-height: 1 +} + +.ub-line-height-2 { + line-height: 1.125 +} + +.ub-line-height-3 { + line-height: 1.25 +} + +.ub-line-height-4 { + line-height: 1.5 +} + +.ub-list-style-none { + list-style: none +} + +.ub-underline { + text-decoration: underline +} + +.ub-truncate { + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap +} + +.ub-list-reset { + list-style: none; + padding-left: 0 +} \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/layouts/Footer/Footer.tsx b/app/vmui/packages/vmui/src/layouts/Footer/Footer.tsx index 94df3dfbe9..638464120e 100644 --- a/app/vmui/packages/vmui/src/layouts/Footer/Footer.tsx +++ b/app/vmui/packages/vmui/src/layouts/Footer/Footer.tsx @@ -1,4 +1,4 @@ -import { FC, memo } from "preact/compat"; +import { FC, memo } from "react"; import { LogoShortIcon } from "../../components/Main/Icons"; import "./style.scss"; import { footerLinksToLogs } from "../../constants/footerLinks"; diff --git a/app/vmui/packages/vmui/src/layouts/Header/Header.tsx b/app/vmui/packages/vmui/src/layouts/Header/Header.tsx index 5ecc534a70..db1a7b213d 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/Header.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/Header.tsx @@ -1,5 +1,5 @@ -import { FC, useMemo, ComponentType } from "preact/compat"; -import { useNavigate } from "react-router-dom"; +import { FC, useMemo, ComponentType } from "react"; +import { useNavigate } from "../../router/useNavigate"; import router from "../../router"; import { getAppModeEnable, getAppModeParams } from "../../utils/app-mode"; import { LogoTracesIcon } from "../../components/Main/Icons"; diff --git a/app/vmui/packages/vmui/src/layouts/Header/HeaderControls/HeaderControls.tsx b/app/vmui/packages/vmui/src/layouts/Header/HeaderControls/HeaderControls.tsx index 3cc03e36f9..3b31ba58ac 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/HeaderControls/HeaderControls.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/HeaderControls/HeaderControls.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from "preact/compat"; +import { FC, useMemo } from "react"; import { RouterOptions, routerOptions, RouterOptionsHeader } from "../../../router"; import { useLocation } from "react-router-dom"; import { diff --git a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx index f1571f4041..6f5463ac10 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/HeaderNav.tsx @@ -1,4 +1,4 @@ -import { FC, useState, useEffect } from "preact/compat"; +import { FC, useState, useEffect } from "react"; import { useLocation } from "react-router-dom"; import "./style.scss"; import NavItem from "./NavItem"; diff --git a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavItem.tsx b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavItem.tsx index d56a9411b8..64e9b8952e 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavItem.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavItem.tsx @@ -1,4 +1,4 @@ -import { FC } from "preact/compat"; +import { FC } from "react"; import { NavLink } from "react-router-dom"; import classNames from "classnames"; import { NavigationItemType } from "../../../router/navigation"; diff --git a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavSubItem.tsx b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavSubItem.tsx index 6c3a714829..150af8ed87 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavSubItem.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/HeaderNav/NavSubItem.tsx @@ -1,4 +1,4 @@ -import { FC, useRef, useState, useEffect } from "preact/compat"; +import { FC, useRef, useState, useEffect } from "react"; import { useLocation } from "react-router-dom"; import classNames from "classnames"; import { ArrowDropDownIcon } from "../../../components/Main/Icons"; diff --git a/app/vmui/packages/vmui/src/layouts/Header/SidebarNav/SidebarHeader.tsx b/app/vmui/packages/vmui/src/layouts/Header/SidebarNav/SidebarHeader.tsx index 12bb61d2ce..5e0816ff71 100644 --- a/app/vmui/packages/vmui/src/layouts/Header/SidebarNav/SidebarHeader.tsx +++ b/app/vmui/packages/vmui/src/layouts/Header/SidebarNav/SidebarHeader.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef } from "preact/compat"; +import { FC, useEffect, useRef } from "react"; import { useLocation } from "react-router-dom"; import classNames from "classnames"; import HeaderNav from "../HeaderNav/HeaderNav"; diff --git a/app/vmui/packages/vmui/src/layouts/LogsLayout/ControlsLogsLayout.tsx b/app/vmui/packages/vmui/src/layouts/LogsLayout/ControlsLogsLayout.tsx index 04e2e7cf9c..1067d8cccc 100644 --- a/app/vmui/packages/vmui/src/layouts/LogsLayout/ControlsLogsLayout.tsx +++ b/app/vmui/packages/vmui/src/layouts/LogsLayout/ControlsLogsLayout.tsx @@ -1,4 +1,4 @@ -import { FC } from "preact/compat"; +import { FC } from "react"; import classNames from "classnames"; import GlobalSettings from "../../components/Configurators/GlobalSettings/GlobalSettings"; import { ControlsProps } from "../Header/HeaderControls/HeaderControls"; diff --git a/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx b/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx index 3a5aa584e2..38f201a441 100644 --- a/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx +++ b/app/vmui/packages/vmui/src/layouts/LogsLayout/LogsLayout.tsx @@ -1,6 +1,6 @@ -import { FC, useEffect } from "preact/compat"; +import { FC, useEffect } from "react"; import Header from "../Header/Header"; -import { Outlet, useLocation } from "react-router-dom"; +import { useLocation } from "react-router-dom"; import "./style.scss"; import { getAppModeEnable } from "../../utils/app-mode"; import classNames from "classnames"; @@ -10,7 +10,7 @@ import useDeviceDetect from "../../hooks/useDeviceDetect"; import ControlsLogsLayout from "./ControlsLogsLayout"; import { footerLinksToLogs } from "../../constants/footerLinks"; -const LogsLayout: FC = () => { +const LogsLayout: FC<{ children?: any }> = ({ children }) => { const appModeEnable = getAppModeEnable(); const { isMobile } = useDeviceDetect(); const { pathname } = useLocation(); @@ -18,24 +18,31 @@ const LogsLayout: FC = () => { const setDocumentTitle = () => { const defaultTitle = "UI for VictoriaTraces"; const routeTitle = routerOptions[router.home]?.title; - document.title = routeTitle ? `${routeTitle} - ${defaultTitle}` : defaultTitle; + + document.title = routeTitle + ? `${routeTitle} - ${defaultTitle}` + : defaultTitle; }; useEffect(setDocumentTitle, [pathname]); - return
-
-
- -
- {!appModeEnable &&
} -
; + return ( +
+
+ +
+ {children} +
+ + {!appModeEnable &&
} +
+ ); }; export default LogsLayout; diff --git a/app/vmui/packages/vmui/src/layouts/TracesLayout/ControlsTracesLayout.tsx b/app/vmui/packages/vmui/src/layouts/TracesLayout/ControlsTracesLayout.tsx new file mode 100644 index 0000000000..1067d8cccc --- /dev/null +++ b/app/vmui/packages/vmui/src/layouts/TracesLayout/ControlsTracesLayout.tsx @@ -0,0 +1,26 @@ +import { FC } from "react"; +import classNames from "classnames"; +import GlobalSettings from "../../components/Configurators/GlobalSettings/GlobalSettings"; +import { ControlsProps } from "../Header/HeaderControls/HeaderControls"; +import { TimeSelector } from "../../components/Configurators/TimeRangeSettings/TimeSelector/TimeSelector"; +import TenantsFields from "../../components/Configurators/GlobalSettings/TenantsConfiguration/TenantsFields"; +import { ExecutionControls } from "../../components/Configurators/TimeRangeSettings/ExecutionControls/ExecutionControls"; + +const ControlsLogsLayout: FC = ({ isMobile }) => { + + return ( +
+ + + + +
+ ); +}; + +export default ControlsLogsLayout; diff --git a/app/vmui/packages/vmui/src/layouts/TracesLayout/TracesLayout.tsx b/app/vmui/packages/vmui/src/layouts/TracesLayout/TracesLayout.tsx new file mode 100644 index 0000000000..bd4995dda6 --- /dev/null +++ b/app/vmui/packages/vmui/src/layouts/TracesLayout/TracesLayout.tsx @@ -0,0 +1,48 @@ +import { FC, useEffect } from "react"; +import Header from "../Header/Header"; +import { useLocation } from "react-router-dom"; +import "./style.scss"; +import { getAppModeEnable } from "../../utils/app-mode"; +import classNames from "classnames"; +import Footer from "../Footer/Footer"; +import router, { routerOptions } from "../../router"; +import useDeviceDetect from "../../hooks/useDeviceDetect"; +import ControlsTracesLayout from "./ControlsTracesLayout"; +import { footerLinksToLogs } from "../../constants/footerLinks"; + +const LogsLayout: FC<{ children?: any }> = ({ children }) => { + const appModeEnable = getAppModeEnable(); + const { isMobile } = useDeviceDetect(); + const { pathname } = useLocation(); + + const setDocumentTitle = () => { + const defaultTitle = "UI for VictoriaTraces"; + const routeTitle = routerOptions[router.home]?.title; + + document.title = routeTitle + ? `${routeTitle} - ${defaultTitle}` + : defaultTitle; + }; + + useEffect(setDocumentTitle, [pathname]); + + return ( +
+
+ +
+ {children} +
+ + {!appModeEnable &&
} +
+ ); +}; + +export default LogsLayout; diff --git a/app/vmui/packages/vmui/src/layouts/TracesLayout/style.scss b/app/vmui/packages/vmui/src/layouts/TracesLayout/style.scss new file mode 100644 index 0000000000..3a7a5b6f56 --- /dev/null +++ b/app/vmui/packages/vmui/src/layouts/TracesLayout/style.scss @@ -0,0 +1,58 @@ +@use "src/styles/variables" as *; + +.vm-trace-container { + display: flex; + flex-direction: column; + + // 关键:不要用 min-height 来承载布局,改成 height 锁住容器高度 + // height: calc(($vh * 100) - var(--scrollbar-height)); + height: 100vh; + min-height: 0; + + // 可选但强烈建议:把页面滚动锁住,让滚动只发生在 body 区域 + overflow: hidden; + + // &-body { + // flex: 1 1 auto; + + // // 关键:允许在 flex 布局里变小,否则会被内容撑开,滚动跑到外层 + // min-height: 0; + + // // 关键:滚动发生在这里 + // overflow: auto; + + // padding: $padding-medium; + // background-color: $color-background-body; + + // &_mobile { + // padding: $padding-small 0 0; + // } + + // @media (max-width: 768px) { + // padding: $padding-small 0 0; + // } + + // &_app { + // padding: $padding-small 0; + // background-color: transparent; + // } + // } + &-body { + flex: 1 1 auto; + min-height: 0; + + /* 不滚它,改为滚 main */ + overflow: hidden; + + >div { + height: 100%; + min-height: 0; + } + + >div>main { + height: 100%; + min-height: 0; + overflow: auto; + } + } +} \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/DownloadLogsButton/DownloadLogsButton.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/DownloadLogsButton/DownloadLogsButton.tsx index 56470d3c12..9b39e69c40 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/DownloadLogsButton/DownloadLogsButton.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/DownloadLogsButton/DownloadLogsButton.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useCallback } from "preact/compat"; +import { FC, useMemo, useCallback } from "react"; import dayjs from "dayjs"; import DownloadButton from "../../../components/DownloadButton/DownloadButton"; import { DATE_FILENAME_FORMAT } from "../../../constants/date"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogs.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogs.tsx index 17d243beba..0cf6ca7dda 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogs.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogs.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useMemo, useState } from "preact/compat"; +import { FC, useEffect, useMemo, useState } from "react"; import ExploreLogsBody from "./ExploreLogsBody/ExploreLogsBody"; import useStateSearchParams from "../../hooks/useStateSearchParams"; import useSearchParamsFromObject from "../../hooks/useSearchParamsFromObject"; @@ -14,7 +14,7 @@ import ExploreLogsBarChart from "./ExploreLogsBarChart/ExploreLogsBarChart"; import { useFetchLogHits } from "./hooks/useFetchLogHits"; import { LOGS_ENTRIES_LIMIT } from "../../constants/logs"; import { getTimeperiodForDuration, relativeTimeOptions } from "../../utils/time"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../router/useSearchParams"; import { useQueryDispatch, useQueryState } from "../../state/query/QueryStateContext"; import { getUpdatedHistory } from "../../components/QueryHistory/utils"; import { useDebounceCallback } from "../../hooks/useDebounceCallback"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBarChart/ExploreLogsBarChart.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBarChart/ExploreLogsBarChart.tsx index fc16f3332d..e0212ce011 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBarChart/ExploreLogsBarChart.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBarChart/ExploreLogsBarChart.tsx @@ -1,4 +1,4 @@ -import { FC, useCallback, useMemo } from "preact/compat"; +import { FC, useCallback, useMemo } from "react"; import "./style.scss"; import useDeviceDetect from "../../../hooks/useDeviceDetect"; import classNames from "classnames"; @@ -10,7 +10,7 @@ import BarHitsChart from "../../../components/Chart/BarHitsChart/BarHitsChart"; import Alert from "../../../components/Main/Alert/Alert"; import { TimeParams } from "../../../types"; import LineLoader from "../../../components/Main/LineLoader/LineLoader"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { getHitsTimeParams } from "../../../utils/logs"; interface Props { diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/ExploreLogsBody.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/ExploreLogsBody.tsx index 8d85c34e26..dd60e825e4 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/ExploreLogsBody.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/ExploreLogsBody.tsx @@ -1,4 +1,4 @@ -import { FC, useRef } from "preact/compat"; +import { FC, useRef } from "react"; import { CodeIcon, ListIcon, TableIcon, PlayIcon } from "../../../components/Main/Icons"; import Tabs from "../../../components/Main/Tabs/Tabs"; import "./style.scss"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/TableLogs.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/TableLogs.tsx index 72c32b590b..d26d9a894c 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/TableLogs.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/TableLogs.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo, useEffect, useRef, useState } from "preact/compat"; +import { FC, useMemo, useEffect, useRef, useState } from "react"; import "./style.scss"; import Table from "../../../components/Table/Table"; import { Logs } from "../../../api/types"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/types.ts b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/types.ts index 2d42c9c0d8..45a2f10c8a 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/types.ts +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/types.ts @@ -1,5 +1,5 @@ import { Logs } from "../../../api/types"; -import { RefObject } from "preact/compat"; +import { RefObject } from "react"; export interface ViewProps { data: Logs[]; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/GroupView/GroupView.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/GroupView/GroupView.tsx index 78fc587149..ea8c9a47cc 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/GroupView/GroupView.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/GroupView/GroupView.tsx @@ -1,4 +1,4 @@ -import { FC, memo } from "preact/compat"; +import { FC, memo } from "react"; import GroupLogs from "../../../GroupLogs/GroupLogs"; import { ViewProps } from "../../types"; import EmptyLogs from "../components/EmptyLogs/EmptyLogs"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonView.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonView.tsx index 09860ed16e..bfb946b463 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonView.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonView.tsx @@ -1,10 +1,11 @@ -import { FC, useMemo, useCallback, createPortal, memo } from "preact/compat"; +import { FC, useMemo, useCallback, memo } from "react"; +import { createPortal } from "react-dom" import DownloadLogsButton from "../../../DownloadLogsButton/DownloadLogsButton"; import JsonViewComponent from "../../../../../components/Views/JsonView/JsonView"; import { ViewProps } from "../../types"; import EmptyLogs from "../components/EmptyLogs/EmptyLogs"; import JsonViewSettings from "./JsonViewSettings/JsonViewSettings"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../../../router/useSearchParams"; import orderby from "lodash.orderby"; import "./style.scss"; import { Logs } from "../../../../../api/types"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonViewSettings/JsonViewSettings.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonViewSettings/JsonViewSettings.tsx index 64f68fc030..16834793a1 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonViewSettings/JsonViewSettings.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/JsonView/JsonViewSettings/JsonViewSettings.tsx @@ -1,17 +1,17 @@ -import { FC, useMemo, useRef, useState, useEffect, useCallback } from "preact/compat"; +import { FC, useMemo, useRef, useState, useEffect, useCallback } from "react"; import Button from "../../../../../../components/Main/Button/Button"; import { SettingsIcon, SortArrowDownIcon, SortArrowUpIcon, SortIcon } from "../../../../../../components/Main/Icons"; import Tooltip from "../../../../../../components/Main/Tooltip/Tooltip"; import Select from "../../../../../../components/Main/Select/Select"; import useBoolean from "../../../../../../hooks/useBoolean"; import Modal from "../../../../../../components/Main/Modal/Modal"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../../../../router/useSearchParams"; import "./style.scss"; import { SortDirection } from "../types"; + const title = "JSON settings"; const directionList = ["asc", "desc"]; - interface JsonSettingsProps { fields: string[]; sortQueryParamName: string; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingSettings.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingSettings.tsx index 83bc56b6f5..085ba24998 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingSettings.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingSettings.tsx @@ -1,4 +1,5 @@ -import { FC, RefObject, useCallback, useRef, createPortal } from "preact/compat"; +import { FC, RefObject, useCallback, useRef } from "react"; +import { createPortal } from "react-dom" import DownloadLogsButton from "../../../DownloadLogsButton/DownloadLogsButton"; import Button from "../../../../../components/Main/Button/Button"; import SelectLimit from "../../../../../components/Main/Pagination/SelectLimit/SelectLimit"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingView.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingView.tsx index 3b44a167fc..d5baf42e95 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingView.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/LiveTailingView.tsx @@ -1,11 +1,11 @@ -import { FC, useCallback, useEffect, useMemo, useRef, useState } from "preact/compat"; +import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react"; import { ViewProps } from "../../types"; import useStateSearchParams from "../../../../../hooks/useStateSearchParams"; import useSearchParamsFromObject from "../../../../../hooks/useSearchParamsFromObject"; import "./style.scss"; import { useLiveTailingLogs } from "./useLiveTailingLogs"; import { LOGS_DISPLAY_FIELDS, LOGS_URL_PARAMS } from "../../../../../constants/logs"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../../../router/useSearchParams"; import throttle from "lodash.throttle"; import GroupLogsItem from "../../../GroupLogs/GroupLogsItem"; import LiveTailingSettings from "./LiveTailingSettings"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/useLiveTailingLogs.ts b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/useLiveTailingLogs.ts index b02f2fdd15..396636e00a 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/useLiveTailingLogs.ts +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/LiveTailingView/useLiveTailingLogs.ts @@ -1,4 +1,4 @@ -import { Dispatch, MutableRefObject, SetStateAction, useCallback, useEffect, useRef, useState } from "preact/compat"; +import { Dispatch, MutableRefObject, SetStateAction, useCallback, useEffect, useRef, useState } from "react"; import { ErrorTypes } from "../../../../../types"; import { Logs } from "../../../../../api/types"; import { useAppState } from "../../../../../state/common/StateContext"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/TableView/TableView.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/TableView/TableView.tsx index c1d5db1dba..954dce3d93 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/TableView/TableView.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/TableView/TableView.tsx @@ -1,6 +1,6 @@ -import { FC, memo, useMemo, useCallback, useState } from "preact/compat"; +import { FC, memo, useMemo, useCallback, useState } from "react"; import DownloadLogsButton from "../../../DownloadLogsButton/DownloadLogsButton"; -import { createPortal } from "preact/compat"; +import { createPortal } from "react-dom" import "./style.scss"; import { ViewProps } from "../../types"; import useStateSearchParams from "../../../../../hooks/useStateSearchParams"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/components/EmptyLogs/EmptyLogs.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/components/EmptyLogs/EmptyLogs.tsx index bf3122133c..ce748ba65a 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/components/EmptyLogs/EmptyLogs.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsBody/views/components/EmptyLogs/EmptyLogs.tsx @@ -1,4 +1,4 @@ -import { FC } from "preact/compat"; +import { FC } from "react"; import "./style.scss"; const EmptyLogs: FC = () => { diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsHeader/ExploreLogsHeader.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsHeader/ExploreLogsHeader.tsx index cb0e2dde15..3b18978ad8 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsHeader/ExploreLogsHeader.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/ExploreLogsHeader/ExploreLogsHeader.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "preact/compat"; +import { FC, useEffect, useState } from "react"; import { InfoIcon, PlayIcon, SpinnerIcon, WikiIcon } from "../../../components/Main/Icons"; import "./style.scss"; import classNames from "classnames"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogs.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogs.tsx index 3556fd0b09..c7f2fe980a 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogs.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogs.tsx @@ -1,4 +1,5 @@ -import { createPortal, FC, useCallback, useEffect, useMemo, useState, RefObject } from "preact/compat"; +import { FC, useCallback, useEffect, useMemo, useState, RefObject } from "react"; +import { createPortal } from "react-dom" import "./style.scss"; import { Logs } from "../../../api/types"; import Accordion from "../../../components/Main/Accordion/Accordion"; @@ -7,7 +8,7 @@ import Tooltip from "../../../components/Main/Tooltip/Tooltip"; import GroupLogsItem from "./GroupLogsItem"; import Button from "../../../components/Main/Button/Button"; import { CollapseIcon, ExpandIcon } from "../../../components/Main/Icons"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { getStreamPairs } from "../../../utils/logs"; import GroupLogsConfigurators from "../../../components/LogsConfigurators/GroupLogsConfigurators/GroupLogsConfigurators"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFieldRow.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFieldRow.tsx index c8d22d991b..c05155c241 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFieldRow.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFieldRow.tsx @@ -1,9 +1,9 @@ -import { FC, memo, useCallback, useEffect, useState } from "preact/compat"; +import { FC, memo, useCallback, useEffect, useState } from "react"; import Tooltip from "../../../components/Main/Tooltip/Tooltip"; import Button from "../../../components/Main/Button/Button"; import { CopyIcon, StorageIcon, VisibilityIcon } from "../../../components/Main/Icons"; import useCopyToClipboard from "../../../hooks/useCopyToClipboard"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs"; interface Props { diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFields.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFields.tsx index d8e2090ed9..c8ca62a305 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFields.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsFields.tsx @@ -1,4 +1,4 @@ -import { FC, useMemo } from "preact/compat"; +import { FC, useMemo } from "react"; import { Logs } from "../../../api/types"; import "./style.scss"; import classNames from "classnames"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeader.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeader.tsx index 645414ba2c..c2cdaf8712 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeader.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeader.tsx @@ -1,6 +1,6 @@ -import { FC, MouseEvent, useCallback, useEffect, useRef, useState } from "preact/compat"; +import { FC, MouseEvent, useCallback, useEffect, useRef, useState } from "react"; import classNames from "classnames"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { useAppState } from "../../../state/common/StateContext"; import useEventListener from "../../../hooks/useEventListener"; import Popper from "../../../components/Main/Popper/Popper"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeaderItem.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeaderItem.tsx index d4d9cdbd9c..c625591b18 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeaderItem.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsHeaderItem.tsx @@ -1,9 +1,9 @@ -import { FC, MouseEvent, useState, useEffect } from "preact/compat"; +import { FC, MouseEvent, useState, useEffect } from "react"; import { useAppState } from "../../../state/common/StateContext"; import Tooltip from "../../../components/Main/Tooltip/Tooltip"; import classNames from "classnames"; import useCopyToClipboard from "../../../hooks/useCopyToClipboard"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { LOGS_GROUP_BY, LOGS_URL_PARAMS } from "../../../constants/logs"; import { convertToFieldFilter } from "../../../utils/logs"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsItem.tsx b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsItem.tsx index 36264e6405..50f25df967 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsItem.tsx +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/GroupLogs/GroupLogsItem.tsx @@ -1,4 +1,4 @@ -import { FC, memo, useMemo, useCallback, useEffect, useState, ReactNode } from "preact/compat"; +import { FC, memo, useMemo, useCallback, useEffect, useState, ReactNode } from "react"; import { Logs } from "../../../api/types"; import "./style.scss"; import useBoolean from "../../../hooks/useBoolean"; @@ -8,7 +8,7 @@ import { useLogsState } from "../../../state/logsPanel/LogsStateContext"; import dayjs from "dayjs"; import { useTimeState } from "../../../state/time/TimeStateContext"; import { marked } from "marked"; -import { useSearchParams } from "react-router-dom"; +import { useSearchParams } from "../../../router/useSearchParams"; import { LOGS_DATE_FORMAT, LOGS_URL_PARAMS } from "../../../constants/logs"; import { parseAnsiToHtml } from "../../../utils/ansiParser"; import GroupLogsFields from "./GroupLogsFields"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogHits.ts b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogHits.ts index d51f772b7a..d76a0f8eae 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogHits.ts +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogHits.ts @@ -1,4 +1,4 @@ -import { useEffect, useCallback, useMemo, useRef, useState } from "preact/compat"; +import { useEffect, useCallback, useMemo, useRef, useState } from "react"; import { getLogHitsUrl } from "../../../api/logs"; import { ErrorTypes, TimeParams } from "../../../types"; import { LogHits } from "../../../api/types"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogs.ts b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogs.ts index f910540151..ec4b6b8f7a 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogs.ts +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/useFetchLogs.ts @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "preact/compat"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { getLogsUrl } from "../../../api/logs"; import { ErrorTypes, TimeParams } from "../../../types"; import { Logs } from "../../../api/types"; diff --git a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/usePaginateGroups.ts b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/usePaginateGroups.ts index 3ead2ad7ba..227418a073 100644 --- a/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/usePaginateGroups.ts +++ b/app/vmui/packages/vmui/src/pages/ExploreLogs/hooks/usePaginateGroups.ts @@ -1,4 +1,4 @@ -import { useMemo } from "preact/compat"; +import { useMemo } from "react"; import { GroupLogsType } from "../../../types"; export const usePaginateGroups = ( diff --git a/app/vmui/packages/vmui/src/router/useNavigate.ts b/app/vmui/packages/vmui/src/router/useNavigate.ts new file mode 100644 index 0000000000..d30611b4d5 --- /dev/null +++ b/app/vmui/packages/vmui/src/router/useNavigate.ts @@ -0,0 +1,36 @@ +import { useCallback } from "react"; +import { useHistory } from "react-router-dom"; + +export type NavigateOptions = { + replace?: boolean; + state?: any; +}; + +export type NavigateFunction = ( + to: string | number | { pathname: string; search?: string; hash?: string }, + options?: NavigateOptions +) => void; + +export function useNavigate(): NavigateFunction { + const history = useHistory(); + + const navigate = useCallback( + (to, options = {}) => { + if (typeof to === "number") { + history.go(to); + return; + } + + const { replace = false, state } = options; + + if (replace) { + history.replace(to as any, state); + } else { + history.push(to as any, state); + } + }, + [history] + ); + + return navigate; +} diff --git a/app/vmui/packages/vmui/src/router/useSearchParams.ts b/app/vmui/packages/vmui/src/router/useSearchParams.ts new file mode 100644 index 0000000000..5eee278c5c --- /dev/null +++ b/app/vmui/packages/vmui/src/router/useSearchParams.ts @@ -0,0 +1,42 @@ +import { useLocation, useHistory } from "react-router-dom"; +import { useCallback, useMemo } from "react"; + +/** + * v5-compatible polyfill for react-router v6 useSearchParams + */ +export function useSearchParams(): [ + URLSearchParams, + (next: URLSearchParams | Record) => void +] { + const location = useLocation(); + const history = useHistory(); + + const searchParams = useMemo(() => { + return new URLSearchParams(location.search); + }, [location.search]); + + const setSearchParams = useCallback( + (next: URLSearchParams | Record) => { + let params: URLSearchParams; + + if (next instanceof URLSearchParams) { + params = next; + } else { + params = new URLSearchParams(); + Object.entries(next).forEach(([key, value]) => { + if (value != null) { + params.set(key, String(value)); + } + }); + } + + history.push({ + pathname: location.pathname, + search: params.toString(), + }); + }, + [history, location.pathname] + ); + + return [searchParams, setSearchParams]; +} diff --git a/app/vmui/packages/vmui/src/state/common/StateContext.tsx b/app/vmui/packages/vmui/src/state/common/StateContext.tsx index c5b771671c..3efa583935 100644 --- a/app/vmui/packages/vmui/src/state/common/StateContext.tsx +++ b/app/vmui/packages/vmui/src/state/common/StateContext.tsx @@ -1,4 +1,4 @@ -import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "preact/compat"; +import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "react"; import { Action, AppState, initialState, reducer } from "./reducer"; import { getQueryStringValue } from "../../utils/query-string"; diff --git a/app/vmui/packages/vmui/src/state/logsPanel/LogsStateContext.tsx b/app/vmui/packages/vmui/src/state/logsPanel/LogsStateContext.tsx index 53e0767867..58eeb633bc 100644 --- a/app/vmui/packages/vmui/src/state/logsPanel/LogsStateContext.tsx +++ b/app/vmui/packages/vmui/src/state/logsPanel/LogsStateContext.tsx @@ -1,4 +1,4 @@ -import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "preact/compat"; +import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "react"; import { LogsAction, LogsState, initialLogsState, reducer } from "./reducer"; type LogsStateContextType = { state: LogsState, dispatch: Dispatch }; diff --git a/app/vmui/packages/vmui/src/state/query/QueryStateContext.tsx b/app/vmui/packages/vmui/src/state/query/QueryStateContext.tsx index 1d7bea0c01..78e0be2d99 100644 --- a/app/vmui/packages/vmui/src/state/query/QueryStateContext.tsx +++ b/app/vmui/packages/vmui/src/state/query/QueryStateContext.tsx @@ -1,5 +1,7 @@ -import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "preact/compat"; +import { createContext, FC, useContext, useMemo, useReducer, useEffect, Dispatch } from "react"; import { QueryAction, QueryState, initialQueryState, reducer } from "./reducer"; +import { setQueriesToStorage } from "../../components/QueryHistory/utils"; +import { saveToStorage } from "../../utils/storage"; type QueryStateContextType = { state: QueryState, dispatch: Dispatch }; @@ -15,6 +17,16 @@ export const QueryStateProvider: FC = ({ children }) => { return { state, dispatch }; }, [state, dispatch]); + useEffect(() => { + // 每次 queryHistory 变化,落盘 + setQueriesToStorage('LOGS_QUERY_HISTORY', state.queryHistory); + }, [state.queryHistory]); + + useEffect(() => { + // 每次 autocomplete 变化,落盘 + saveToStorage("AUTOCOMPLETE", state.autocomplete); + }, [state.autocomplete]); + return {children} ; diff --git a/app/vmui/packages/vmui/src/state/query/reducer.ts b/app/vmui/packages/vmui/src/state/query/reducer.ts index 45190b2ac1..e8d7c732e2 100644 --- a/app/vmui/packages/vmui/src/state/query/reducer.ts +++ b/app/vmui/packages/vmui/src/state/query/reducer.ts @@ -47,7 +47,7 @@ export function reducer(state: QueryState, action: QueryAction): QueryState { query: action.payload.map(q => q) }; case "SET_QUERY_HISTORY": - setQueriesToStorage(action.payload.key, action.payload.history); + // setQueriesToStorage(action.payload.key, action.payload.history); return { ...state, queryHistory: action.payload.history @@ -59,7 +59,7 @@ export function reducer(state: QueryState, action: QueryAction): QueryState { queryHistory: state.queryHistory }; case "TOGGLE_AUTOCOMPLETE": - saveToStorage("AUTOCOMPLETE", !state.autocomplete); + // saveToStorage("AUTOCOMPLETE", !state.autocomplete); return { ...state, autocomplete: !state.autocomplete diff --git a/app/vmui/packages/vmui/src/state/time/TimeStateContext.tsx b/app/vmui/packages/vmui/src/state/time/TimeStateContext.tsx index 61bb857b17..2a136535ac 100644 --- a/app/vmui/packages/vmui/src/state/time/TimeStateContext.tsx +++ b/app/vmui/packages/vmui/src/state/time/TimeStateContext.tsx @@ -1,4 +1,4 @@ -import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "preact/compat"; +import { createContext, FC, useContext, useMemo, useReducer, Dispatch } from "react"; import { TimeAction, TimeState, initialTimeState, reducer } from "./reducer"; type TimeStateContextType = { state: TimeState, dispatch: Dispatch }; diff --git a/app/vmui/packages/vmui/src/styles/core.scss b/app/vmui/packages/vmui/src/styles/core.scss index 2824e274c2..c4c0f11ab7 100644 --- a/app/vmui/packages/vmui/src/styles/core.scss +++ b/app/vmui/packages/vmui/src/styles/core.scss @@ -13,7 +13,16 @@ html, body, #root { } body { - overflow: auto; + // overflow: auto; +} + +html, body, #root { + height: 100%; + margin: 0; +} + +html, body { + overflow: hidden; /* 关键:禁止 window 滚动 */ } * { @@ -121,10 +130,6 @@ input[type=number]::-webkit-outer-spin-button { } } -svg { - width: 100%; -} - /* Works on Firefox */ * { scrollbar-width: thin; diff --git a/app/vmui/packages/vmui/src/styles/style.scss b/app/vmui/packages/vmui/src/styles/style.scss index 6cffb208c3..4469a1643c 100644 --- a/app/vmui/packages/vmui/src/styles/style.scss +++ b/app/vmui/packages/vmui/src/styles/style.scss @@ -19,7 +19,7 @@ --color-error: #FD080E; --color-warning: #FF8308; --color-info: #03A9F4; - --color-success: #4CAF50; + --color-success: #898989; /* text palette */ --color-primary-text: #FFFFFF; diff --git a/app/vmui/packages/vmui/src/styles/variables.scss b/app/vmui/packages/vmui/src/styles/variables.scss index a6a066301c..5fa71e11a9 100644 --- a/app/vmui/packages/vmui/src/styles/variables.scss +++ b/app/vmui/packages/vmui/src/styles/variables.scss @@ -61,4 +61,4 @@ $box-shadow: var(--box-shadow); $box-shadow-popper: var(--box-shadow-popper); $color-hover-black: var(--color-hover-black); -$vh: var(--vh); +$vh: var(--vh); \ No newline at end of file diff --git a/app/vmui/packages/vmui/src/utils/ansiParser.tsx b/app/vmui/packages/vmui/src/utils/ansiParser.tsx index eff3ee5d56..874f56ed29 100644 --- a/app/vmui/packages/vmui/src/utils/ansiParser.tsx +++ b/app/vmui/packages/vmui/src/utils/ansiParser.tsx @@ -1,4 +1,4 @@ -import { ReactNode } from "preact/compat"; +import { ReactNode } from "react"; // Define a specific interface for the ANSI style properties. interface AnsiStyles { diff --git a/app/vmui/packages/vmui/src/utils/combine-components.tsx b/app/vmui/packages/vmui/src/utils/combine-components.tsx index 2560493bbb..68a910cd1e 100644 --- a/app/vmui/packages/vmui/src/utils/combine-components.tsx +++ b/app/vmui/packages/vmui/src/utils/combine-components.tsx @@ -1,4 +1,4 @@ -import { ComponentProps, FC, ReactNode } from "preact/compat"; +import { ComponentProps, FC, ReactNode } from "react"; type Props = { children: ReactNode }; diff --git a/app/vmui/packages/vmui/tsconfig.json b/app/vmui/packages/vmui/tsconfig.json index 3b26389509..8a86a8e7b4 100644 --- a/app/vmui/packages/vmui/tsconfig.json +++ b/app/vmui/packages/vmui/tsconfig.json @@ -2,11 +2,7 @@ "compilerOptions": { "target": "ESNext", "types": ["vite/client", "vitest/globals"], - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, @@ -20,17 +16,8 @@ "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", - "jsxImportSource": "preact", "downlevelIteration": true, - "noUnusedLocals": true, - "paths": { - "react": ["./node_modules/preact/compat/"], - "react/jsx-runtime": ["./node_modules/preact/jsx-runtime"], - "react-dom": ["./node_modules/preact/compat/"], - "react-dom/*": ["./node_modules/preact/compat/*"] - } + "noUnusedLocals": true }, - "include": [ - "src" - ] -} + "include": ["src"] +} \ No newline at end of file diff --git a/app/vmui/packages/vmui/vite.config.ts b/app/vmui/packages/vmui/vite.config.ts index 7fddce1f95..ca79f82217 100644 --- a/app/vmui/packages/vmui/vite.config.ts +++ b/app/vmui/packages/vmui/vite.config.ts @@ -1,20 +1,22 @@ import * as path from "path"; import { defineConfig, ProxyOptions } from "vite"; -import preact from "@preact/preset-vite"; +import react from "@vitejs/plugin-react"; import dynamicIndexHtmlPlugin from "./config/plugins/dynamicIndexHtml"; export default defineConfig(({ mode }) => { return { base: "", plugins: [ - preact(), + react(), dynamicIndexHtmlPlugin({ mode }) ], assetsInclude: ["**/*.md"], server: { + host: "0.0.0.0", open: true, port: 3000, + allowedHosts: ['local.vtraces.test'], }, resolve: { alias: { @@ -33,6 +35,11 @@ export default defineConfig(({ mode }) => { } } }, + define: { + __REACT_APP_GA_DEBUG__: JSON.stringify(process.env.REACT_APP_GA_DEBUG || ''), + __REACT_APP_VSN_STATE__: JSON.stringify(process.env.REACT_APP_VSN_STATE || ''), + __APP_ENVIRONMENT__: JSON.stringify(process.env.NODE_ENV || 'development'), + }, }; }); diff --git a/app/vmui/packages/vmui/vitest.config.ts b/app/vmui/packages/vmui/vitest.config.ts index a82affbf68..4b5c9028b2 100644 --- a/app/vmui/packages/vmui/vitest.config.ts +++ b/app/vmui/packages/vmui/vitest.config.ts @@ -1,15 +1,8 @@ import { defineConfig } from "vitest/config"; -import preact from "@preact/preset-vite"; +import react from "@vitejs/plugin-react"; export default defineConfig({ - plugins: [preact()], - resolve: { - alias: { - "react": "preact/compat", - "react-dom": "preact/compat", - "react/jsx-runtime": "preact/jsx-runtime", - }, - }, + plugins: [react()], test: { globals: true, environment: "jsdom", diff --git a/app/vmui/pnpm-lock.yaml b/app/vmui/pnpm-lock.yaml new file mode 100644 index 0000000000..0624b759bf --- /dev/null +++ b/app/vmui/pnpm-lock.yaml @@ -0,0 +1,10449 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} + + packages/jaeger-ui-lite: + dependencies: + '@pyroscope/flamegraph': + specifier: 0.21.4 + version: 0.21.4(react-dom@19.2.0)(react@19.2.0)(true-myth@5.4.0) + '@sentry/browser': + specifier: 10.32.0 + version: 10.32.0 + '@tanstack/react-virtual': + specifier: 3.13.12 + version: 3.13.12(react-dom@19.2.0)(react@19.2.0) + antd: + specifier: 6.1.1 + version: 6.1.1(react-dom@19.2.0)(react@19.2.0) + chance: + specifier: 1.1.13 + version: 1.1.13 + classnames: + specifier: 2.5.1 + version: 2.5.1 + combokeys: + specifier: 3.0.1 + version: 3.0.1 + copy-to-clipboard: + specifier: 3.3.3 + version: 3.3.3 + dayjs: + specifier: 1.11.19 + version: 1.11.19 + deep-freeze: + specifier: 0.0.1 + version: 0.0.1 + drange: + specifier: 2.0.1 + version: 2.0.1 + history: + specifier: 4.10.1 + version: 4.10.1 + isomorphic-fetch: + specifier: 3.0.0 + version: 3.0.0 + lodash: + specifier: 4.17.21 + version: 4.17.21 + logfmt: + specifier: 1.4.0 + version: 1.4.0 + lru-memoize: + specifier: 1.1.0 + version: 1.1.0 + match-sorter: + specifier: 8.2.0 + version: 8.2.0 + memoize-one: + specifier: 6.0.0 + version: 6.0.0 + object-hash: + specifier: 3.0.0 + version: 3.0.0 + query-string: + specifier: 9.3.1 + version: 9.3.1 + react: + specifier: 19.2.0 + version: 19.2.0 + react-circular-progressbar: + specifier: 2.2.0 + version: 2.2.0(react@19.2.0) + react-dom: + specifier: 19.2.0 + version: 19.2.0(react@19.2.0) + react-icons: + specifier: 5.5.0 + version: 5.5.0(react@19.2.0) + react-is: + specifier: 19.2.0 + version: 19.2.0 + react-json-view-lite: + specifier: 2.5.0 + version: 2.5.0(react@19.2.0) + react-redux: + specifier: 9.2.0 + version: 9.2.0(@types/react@19.2.7)(react@19.2.0)(redux@5.0.1) + react-router-dom: + specifier: 5.3.4 + version: 5.3.4(react@19.2.0) + react-router-dom-v5-compat: + specifier: 6.30.0 + version: 6.30.0(react-dom@19.2.0)(react-router-dom@5.3.4)(react@19.2.0) + recharts: + specifier: 3.6.0 + version: 3.6.0(@types/react@19.2.7)(react-dom@19.2.0)(react-is@19.2.0)(react@19.2.0)(redux@5.0.1) + redux: + specifier: 5.0.1 + version: 5.0.1 + redux-actions: + specifier: 3.0.3 + version: 3.0.3 + redux-first-history: + specifier: 5.2.0 + version: 5.2.0(history@4.10.1)(redux@5.0.1) + redux-promise-middleware: + specifier: 6.2.0 + version: 6.2.0(redux@5.0.1) + store: + specifier: 2.0.12 + version: 2.0.12 + tween-functions: + specifier: 1.2.0 + version: 1.2.0 + u-basscss: + specifier: 2.0.1 + version: 2.0.1 + devDependencies: + '@babel/preset-env': + specifier: 7.28.5 + version: 7.28.5(@babel/core@7.28.6) + '@babel/preset-react': + specifier: 7.28.5 + version: 7.28.5(@babel/core@7.28.6) + '@babel/preset-typescript': + specifier: 7.28.5 + version: 7.28.5(@babel/core@7.28.6) + '@testing-library/jest-dom': + specifier: 6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: 16.3.0 + version: 16.3.0(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.0)(react@19.2.0) + '@testing-library/user-event': + specifier: 14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/deep-freeze': + specifier: 0.1.5 + version: 0.1.5 + '@types/history': + specifier: 4.7.11 + version: 4.7.11 + '@types/jest': + specifier: 30.0.0 + version: 30.0.0 + '@types/lodash': + specifier: 4.17.21 + version: 4.17.21 + '@types/object-hash': + specifier: 3.0.6 + version: 3.0.6 + '@types/react': + specifier: 19.2.7 + version: 19.2.7 + '@types/react-router-dom': + specifier: 5.3.3 + version: 5.3.3 + '@types/redux-actions': + specifier: 2.2.1 + version: 2.2.1 + '@vitejs/plugin-legacy': + specifier: 7.2.1 + version: 7.2.1(terser@5.44.1)(vite@7.3.0) + '@vitejs/plugin-react': + specifier: 5.1.1 + version: 5.1.1(vite@7.3.0) + babel-jest: + specifier: 30.2.0 + version: 30.2.0(@babel/core@7.28.6) + babel-plugin-inline-react-svg: + specifier: 2.0.2 + version: 2.0.2(@babel/core@7.28.6) + babel-plugin-react-remove-properties: + specifier: 0.3.1 + version: 0.3.1 + jest: + specifier: 30.2.0 + version: 30.2.0 + jest-environment-jsdom: + specifier: 30.2.0 + version: 30.2.0 + jest-junit: + specifier: 16.0.0 + version: 16.0.0 + less: + specifier: 4.5.1 + version: 4.5.1 + terser: + specifier: 5.44.1 + version: 5.44.1 + vite: + specifier: 7.3.0 + version: 7.3.0(less@4.5.1)(terser@5.44.1) + + packages/vmui: + dependencies: + antd: + specifier: 6.1.1 + version: 6.1.1(react-dom@19.2.0)(react@19.2.0) + classnames: + specifier: ^2.5.1 + version: 2.5.1 + dayjs: + specifier: ^1.11.13 + version: 1.11.19 + jaeger-ui-lite: + specifier: workspace:* + version: link:../jaeger-ui-lite + lodash.debounce: + specifier: ^4.0.8 + version: 4.0.8 + lodash.orderby: + specifier: ^4.6.0 + version: 4.6.0 + lodash.throttle: + specifier: ^4.1.1 + version: 4.1.1 + marked: + specifier: ^16.0.0 + version: 16.4.2 + qs: + specifier: ^6.14.0 + version: 6.14.1 + query-string: + specifier: ^9.3.1 + version: 9.3.1 + react: + specifier: ^19.2.0 + version: 19.2.0 + react-dom: + specifier: ^19.2.0 + version: 19.2.0(react@19.2.0) + react-imask: + specifier: ^7.6.1 + version: 7.6.1(react@19.2.0) + react-redux: + specifier: ^9.2.0 + version: 9.2.0(@types/react@19.2.7)(react@19.2.0)(redux@5.0.1) + react-router-dom: + specifier: 5.3.4 + version: 5.3.4(react@19.2.0) + react-router-dom-v5-compat: + specifier: 6.30.0 + version: 6.30.0(react-dom@19.2.0)(react-router-dom@5.3.4)(react@19.2.0) + uplot: + specifier: ^1.6.32 + version: 1.6.32 + vite: + specifier: ^7.0.0 + version: 7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3) + web-vitals: + specifier: ^5.0.3 + version: 5.1.0 + devDependencies: + '@eslint/eslintrc': + specifier: ^3.3.1 + version: 3.3.3 + '@eslint/js': + specifier: ^9.30.0 + version: 9.39.2 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/preact': + specifier: ^3.2.4 + version: 3.2.4(preact@10.28.2) + '@types/history': + specifier: ^5.0.0 + version: 5.0.0 + '@types/lodash.debounce': + specifier: ^4.0.9 + version: 4.0.9 + '@types/lodash.orderby': + specifier: ^4.6.9 + version: 4.6.9 + '@types/lodash.throttle': + specifier: ^4.1.9 + version: 4.1.9 + '@types/node': + specifier: ^24.0.8 + version: 24.10.9 + '@types/qs': + specifier: ^6.14.0 + version: 6.14.0 + '@types/react': + specifier: ^19.1.8 + version: 19.2.7 + '@types/react-dom': + specifier: ^19.1.8 + version: 19.2.3(@types/react@19.2.7) + '@types/react-input-mask': + specifier: ^3.0.6 + version: 3.0.6 + '@types/react-router-dom': + specifier: ^5.3.3 + version: 5.3.3 + '@typescript-eslint/eslint-plugin': + specifier: ^8.35.1 + version: 8.53.1(@typescript-eslint/parser@8.53.1)(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.35.1 + version: 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@vitejs/plugin-react': + specifier: ^5.1.2 + version: 5.1.2(vite@7.3.0) + cross-env: + specifier: ^7.0.3 + version: 7.0.3 + eslint: + specifier: ^9.30.0 + version: 9.39.2 + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.2) + eslint-plugin-unused-imports: + specifier: ^4.1.4 + version: 4.3.0(@typescript-eslint/eslint-plugin@8.53.1)(eslint@9.39.2) + globals: + specifier: ^16.3.0 + version: 16.5.0 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + postcss: + specifier: ^8.5.6 + version: 8.5.6 + rollup-plugin-visualizer: + specifier: ^6.0.3 + version: 6.0.5 + sass-embedded: + specifier: ^1.89.2 + version: 1.97.3 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.10.9)(jsdom@26.1.0)(sass-embedded@1.97.3) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==, tarball: https://registry.npmmirror.com/@adobe/css-tools/-/css-tools-4.4.4.tgz} + + '@ant-design/colors@8.0.1': + resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==, tarball: https://registry.npmmirror.com/@ant-design/colors/-/colors-8.0.1.tgz} + + '@ant-design/cssinjs-utils@2.0.2': + resolution: {integrity: sha512-Mq3Hm6fJuQeFNKSp3+yT4bjuhVbdrsyXE2RyfpJFL0xiYNZdaJ6oFaE3zFrzmHbmvTd2Wp3HCbRtkD4fU+v2ZA==, tarball: https://registry.npmmirror.com/@ant-design/cssinjs-utils/-/cssinjs-utils-2.0.2.tgz} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + '@ant-design/cssinjs@2.0.3': + resolution: {integrity: sha512-HAo8SZ3a6G8v6jT0suCz1270na6EA3obeJWM4uzRijBhdwdoMAXWK2f4WWkwB28yUufsfk3CAhN1coGPQq4kNQ==, tarball: https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-2.0.3.tgz} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@3.0.0': + resolution: {integrity: sha512-eqvpP7xEDm2S7dUzl5srEQCBTXZMmY3ekf97zI+M2DHOYyKdJGH0qua0JACHTqbkRnD/KHFQP9J1uMJ/XWVzzA==, tarball: https://registry.npmmirror.com/@ant-design/fast-color/-/fast-color-3.0.0.tgz} + engines: {node: '>=8.x'} + + '@ant-design/icons-svg@4.4.2': + resolution: {integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==, tarball: https://registry.npmmirror.com/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz} + + '@ant-design/icons@6.1.0': + resolution: {integrity: sha512-KrWMu1fIg3w/1F2zfn+JlfNDU8dDqILfA5Tg85iqs1lf8ooyGlbkA+TkwfOKKgqpUmAiRY1PTFpuOU2DAIgSUg==, tarball: https://registry.npmmirror.com/@ant-design/icons/-/icons-6.1.0.tgz} + engines: {node: '>=8'} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@2.0.0': + resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==, tarball: https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz} + peerDependencies: + react: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==, tarball: https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-3.2.0.tgz} + + '@babel/code-frame@7.28.6': + resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==, tarball: https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.6': + resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==, tarball: https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.6': + resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==, tarball: https://registry.npmmirror.com/@babel/core/-/core-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.6': + resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==, tarball: https://registry.npmmirror.com/@babel/generator/-/generator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==, tarball: https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==, tarball: https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==, tarball: https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==, tarball: https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.6': + resolution: {integrity: sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA==, tarball: https://registry.npmmirror.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.6.tgz} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==, tarball: https://registry.npmmirror.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==, tarball: https://registry.npmmirror.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==, tarball: https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==, tarball: https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==, tarball: https://registry.npmmirror.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==, tarball: https://registry.npmmirror.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==, tarball: https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==, tarball: https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==, tarball: https://registry.npmmirror.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==, tarball: https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==, tarball: https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==, tarball: https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==, tarball: https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==, tarball: https://registry.npmmirror.com/@babel/helpers/-/helpers-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.6': + resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==, tarball: https://registry.npmmirror.com/@babel/parser/-/parser-7.28.6.tgz} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==, tarball: https://registry.npmmirror.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==, tarball: https://registry.npmmirror.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==, tarball: https://registry.npmmirror.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.28.6': + resolution: {integrity: sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6': + resolution: {integrity: sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.28.5': + resolution: {integrity: sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.28.6': + resolution: {integrity: sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==, tarball: https://registry.npmmirror.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.28.5': + resolution: {integrity: sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==, tarball: https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==, tarball: https://registry.npmmirror.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.28.5': + resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==, tarball: https://registry.npmmirror.com/@babel/preset-react/-/preset-react-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==, tarball: https://registry.npmmirror.com/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime-corejs3@7.28.6': + resolution: {integrity: sha512-kz2fAQ5UzjV7X7D3ySxmj3vRq89dTpqOZWv76Z6pNPztkwb/0Yj1Mtx1xFrYj6mbIHysxtBot8J4o0JLCblcFw==, tarball: https://registry.npmmirror.com/@babel/runtime-corejs3/-/runtime-corejs3-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==, tarball: https://registry.npmmirror.com/@babel/runtime/-/runtime-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==, tarball: https://registry.npmmirror.com/@babel/template/-/template-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.6': + resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==, tarball: https://registry.npmmirror.com/@babel/traverse/-/traverse-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.6': + resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==, tarball: https://registry.npmmirror.com/@babel/types/-/types-7.28.6.tgz} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==, tarball: https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz} + + '@bufbuild/protobuf@2.11.0': + resolution: {integrity: sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==, tarball: https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.11.0.tgz} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==, tarball: https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-5.1.0.tgz} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==, tarball: https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-2.1.4.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==, tarball: https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==, tarball: https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==, tarball: https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz} + engines: {node: '>=18'} + + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==, tarball: https://registry.npmmirror.com/@emnapi/core/-/core-1.8.1.tgz} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==, tarball: https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.8.1.tgz} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==, tarball: https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz} + + '@emotion/hash@0.8.0': + resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==, tarball: https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz} + + '@emotion/unitless@0.7.5': + resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==, tarball: https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz} + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==, tarball: https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==, tarball: https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==, tarball: https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==, tarball: https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==, tarball: https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==, tarball: https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==, tarball: https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==, tarball: https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==, tarball: https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==, tarball: https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==, tarball: https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==, tarball: https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==, tarball: https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==, tarball: https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==, tarball: https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==, tarball: https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==, tarball: https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==, tarball: https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==, tarball: https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==, tarball: https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==, tarball: https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==, tarball: https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==, tarball: https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==, tarball: https://registry.npmmirror.com/@eslint/config-array/-/config-array-0.21.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==, tarball: https://registry.npmmirror.com/@eslint/config-helpers/-/config-helpers-0.4.2.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==, tarball: https://registry.npmmirror.com/@eslint/core/-/core-0.17.0.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==, tarball: https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-3.3.3.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==, tarball: https://registry.npmmirror.com/@eslint/js/-/js-9.39.2.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==, tarball: https://registry.npmmirror.com/@eslint/object-schema/-/object-schema-2.1.7.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==, tarball: https://registry.npmmirror.com/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==, tarball: https://registry.npmmirror.com/@humanfs/core/-/core-0.19.1.tgz} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==, tarball: https://registry.npmmirror.com/@humanfs/node/-/node-0.16.7.tgz} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==, tarball: https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==, tarball: https://registry.npmmirror.com/@humanwhocodes/retry/-/retry-0.4.3.tgz} + engines: {node: '>=18.18'} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==, tarball: https://registry.npmmirror.com/@isaacs/cliui/-/cliui-8.0.2.tgz} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==, tarball: https://registry.npmmirror.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==, tarball: https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz} + engines: {node: '>=8'} + + '@jest/console@30.2.0': + resolution: {integrity: sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==, tarball: https://registry.npmmirror.com/@jest/console/-/console-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.2.0': + resolution: {integrity: sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==, tarball: https://registry.npmmirror.com/@jest/core/-/core-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==, tarball: https://registry.npmmirror.com/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment-jsdom-abstract@30.2.0': + resolution: {integrity: sha512-kazxw2L9IPuZpQ0mEt9lu9Z98SqR74xcagANmMBU16X0lS23yPc0+S6hGLUz8kVRlomZEs/5S/Zlpqwf5yu6OQ==, tarball: https://registry.npmmirror.com/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + jsdom: '*' + peerDependenciesMeta: + canvas: + optional: true + + '@jest/environment@30.2.0': + resolution: {integrity: sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==, tarball: https://registry.npmmirror.com/@jest/environment/-/environment-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@30.2.0': + resolution: {integrity: sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==, tarball: https://registry.npmmirror.com/@jest/expect-utils/-/expect-utils-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.2.0': + resolution: {integrity: sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==, tarball: https://registry.npmmirror.com/@jest/expect/-/expect-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@30.2.0': + resolution: {integrity: sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==, tarball: https://registry.npmmirror.com/@jest/fake-timers/-/fake-timers-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==, tarball: https://registry.npmmirror.com/@jest/get-type/-/get-type-30.1.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.2.0': + resolution: {integrity: sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==, tarball: https://registry.npmmirror.com/@jest/globals/-/globals-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==, tarball: https://registry.npmmirror.com/@jest/pattern/-/pattern-30.0.1.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.2.0': + resolution: {integrity: sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==, tarball: https://registry.npmmirror.com/@jest/reporters/-/reporters-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==, tarball: https://registry.npmmirror.com/@jest/schemas/-/schemas-30.0.5.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.2.0': + resolution: {integrity: sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==, tarball: https://registry.npmmirror.com/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==, tarball: https://registry.npmmirror.com/@jest/source-map/-/source-map-30.0.1.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.2.0': + resolution: {integrity: sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==, tarball: https://registry.npmmirror.com/@jest/test-result/-/test-result-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.2.0': + resolution: {integrity: sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==, tarball: https://registry.npmmirror.com/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@30.2.0': + resolution: {integrity: sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==, tarball: https://registry.npmmirror.com/@jest/transform/-/transform-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@30.2.0': + resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==, tarball: https://registry.npmmirror.com/@jest/types/-/types-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==, tarball: https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==, tarball: https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==, tarball: https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==, tarball: https://registry.npmmirror.com/@jridgewell/source-map/-/source-map-0.3.11.tgz} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==, tarball: https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==, tarball: https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==, tarball: https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz} + + '@parcel/watcher-android-arm64@2.5.6': + resolution: {integrity: sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==, tarball: https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.6': + resolution: {integrity: sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==, tarball: https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.6': + resolution: {integrity: sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==, tarball: https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.6': + resolution: {integrity: sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==, tarball: https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.6': + resolution: {integrity: sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm-musl@2.5.6': + resolution: {integrity: sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + resolution: {integrity: sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-arm64-musl@2.5.6': + resolution: {integrity: sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@parcel/watcher-linux-x64-glibc@2.5.6': + resolution: {integrity: sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@parcel/watcher-linux-x64-musl@2.5.6': + resolution: {integrity: sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==, tarball: https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@parcel/watcher-win32-arm64@2.5.6': + resolution: {integrity: sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.6': + resolution: {integrity: sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.6': + resolution: {integrity: sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==, tarball: https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.6': + resolution: {integrity: sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==, tarball: https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.5.6.tgz} + engines: {node: '>= 10.0.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==, tarball: https://registry.npmmirror.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz} + engines: {node: '>=14'} + + '@pkgr/core@0.2.9': + resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==, tarball: https://registry.npmmirror.com/@pkgr/core/-/core-0.2.9.tgz} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@pyroscope/flamegraph@0.21.4': + resolution: {integrity: sha512-/wkTHhBNapnnuLWQ40PyHa1pz8jVMmiLYO12Ak7qq1JhgVEccE39XrQMTkcFlM4FdpqM4/1w6U7fm4xebUAzZw==, tarball: https://registry.npmmirror.com/@pyroscope/flamegraph/-/flamegraph-0.21.4.tgz} + peerDependencies: + react: '>=16.14.0' + react-dom: '>=16.14.0' + true-myth: ^5.1.2 + + '@rc-component/async-validator@5.1.0': + resolution: {integrity: sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==, tarball: https://registry.npmmirror.com/@rc-component/async-validator/-/async-validator-5.1.0.tgz} + engines: {node: '>=14.x'} + + '@rc-component/cascader@1.9.0': + resolution: {integrity: sha512-2jbthe1QZrMBgtCvNKkJFjZYC3uKl4N/aYm5SsMvO3T+F+qRT1CGsSM9bXnh1rLj7jDk/GK0natShWF/jinhWQ==, tarball: https://registry.npmmirror.com/@rc-component/cascader/-/cascader-1.9.0.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/checkbox@1.0.1': + resolution: {integrity: sha512-08yTH8m+bSm8TOqbybbJ9KiAuIATti6bDs2mVeSfu4QfEnyeF6X0enHVvD1NEAyuBWEAo56QtLe++MYs2D9XiQ==, tarball: https://registry.npmmirror.com/@rc-component/checkbox/-/checkbox-1.0.1.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/collapse@1.1.2': + resolution: {integrity: sha512-ilBYk1dLLJHu5Q74dF28vwtKUYQ42ZXIIDmqTuVy4rD8JQVvkXOs+KixVNbweyuIEtJYJ7+t+9GVD9dPc6N02w==, tarball: https://registry.npmmirror.com/@rc-component/collapse/-/collapse-1.1.2.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/color-picker@3.0.3': + resolution: {integrity: sha512-V7gFF9O7o5XwIWafdbOtqI4BUUkEUkgdBwp6favy3xajMX/2dDqytFaiXlcwrpq6aRyPLp5dKLAG5RFKLXMeGA==, tarball: https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-3.0.3.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@2.0.1': + resolution: {integrity: sha512-HyZbYm47s/YqtP6pKXNMjPEMaukyg7P0qVfgMLzr7YiFNMHbK2fKTAGzms9ykfGHSfyf75nBbgWw+hHkp+VImw==, tarball: https://registry.npmmirror.com/@rc-component/context/-/context-2.0.1.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/dialog@1.5.1': + resolution: {integrity: sha512-by4Sf/a3azcb89WayWuwG19/Y312xtu8N81HoVQQtnsBDylfs+dog98fTAvLinnpeoWG52m/M7QLRW6fXR3l1g==, tarball: https://registry.npmmirror.com/@rc-component/dialog/-/dialog-1.5.1.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/drawer@1.3.0': + resolution: {integrity: sha512-rE+sdXEmv2W25VBQ9daGbnb4J4hBIEKmdbj0b3xpY+K7TUmLXDIlSnoXraIbFZdGyek9WxxGKK887uRnFgI+pQ==, tarball: https://registry.npmmirror.com/@rc-component/drawer/-/drawer-1.3.0.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/dropdown@1.0.2': + resolution: {integrity: sha512-6PY2ecUSYhDPhkNHHb4wfeAya04WhpmUSKzdR60G+kMNVUCX2vjT/AgTS0Lz0I/K6xrPMJ3enQbwVpeN3sHCgg==, tarball: https://registry.npmmirror.com/@rc-component/dropdown/-/dropdown-1.0.2.tgz} + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + '@rc-component/form@1.5.0': + resolution: {integrity: sha512-clF2Ws00bImVSOfaF4e2dLr631g5QOUD7M7kqb8es6fWXkJ1YO4nmjGJTQ/7QfB7iqY6JED4yV12ftKKD5/8GQ==, tarball: https://registry.npmmirror.com/@rc-component/form/-/form-1.5.0.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/image@1.5.3': + resolution: {integrity: sha512-/NR7QW9uCN8Ugar+xsHZOPvzPySfEhcW2/vLcr7VPRM+THZMrllMRv7LAUgW7ikR+Z67Ab67cgPp5K5YftpJsQ==, tarball: https://registry.npmmirror.com/@rc-component/image/-/image-1.5.3.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input-number@1.6.2': + resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==, tarball: https://registry.npmmirror.com/@rc-component/input-number/-/input-number-1.6.2.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/input@1.1.2': + resolution: {integrity: sha512-Q61IMR47piUBudgixJ30CciKIy9b1H95qe7GgEKOmSJVJXvFRWJllJfQry9tif+MX2cWFXWJf/RXz4kaCeq/Fg==, tarball: https://registry.npmmirror.com/@rc-component/input/-/input-1.1.2.tgz} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/mentions@1.6.0': + resolution: {integrity: sha512-KIkQNP6habNuTsLhUv0UGEOwG67tlmE7KNIJoQZZNggEZl5lQJTytFDb69sl5CK3TDdISCTjKP3nGEBKgT61CQ==, tarball: https://registry.npmmirror.com/@rc-component/mentions/-/mentions-1.6.0.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/menu@1.2.0': + resolution: {integrity: sha512-VWwDuhvYHSnTGj4n6bV3ISrLACcPAzdPOq3d0BzkeiM5cve8BEYfvkEhNoM0PLzv51jpcejeyrLXeMVIJ+QJlg==, tarball: https://registry.npmmirror.com/@rc-component/menu/-/menu-1.2.0.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: {integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==, tarball: https://registry.npmmirror.com/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz} + engines: {node: '>=8.x'} + + '@rc-component/motion@1.1.6': + resolution: {integrity: sha512-aEQobs/YA0kqRvHIPjQvOytdtdRVyhf/uXAal4chBjxDu6odHckExJzjn2D+Ju1aKK6hx3pAs6BXdV9+86xkgQ==, tarball: https://registry.npmmirror.com/@rc-component/motion/-/motion-1.1.6.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mutate-observer@2.0.1': + resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==, tarball: https://registry.npmmirror.com/@rc-component/mutate-observer/-/mutate-observer-2.0.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/notification@1.2.0': + resolution: {integrity: sha512-OX3J+zVU7rvoJCikjrfW7qOUp7zlDeFBK2eA3SFbGSkDqo63Sl4Ss8A04kFP+fxHSxMDIS9jYVEZtU1FNCFuBA==, tarball: https://registry.npmmirror.com/@rc-component/notification/-/notification-1.2.0.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/overflow@1.0.0': + resolution: {integrity: sha512-GSlBeoE0XTBi5cf3zl8Qh7Uqhn7v8RrlJ8ajeVpEkNe94HWy5l5BQ0Mwn2TVUq9gdgbfEMUmTX7tJFAg7mz0Rw==, tarball: https://registry.npmmirror.com/@rc-component/overflow/-/overflow-1.0.0.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/pagination@1.2.0': + resolution: {integrity: sha512-YcpUFE8dMLfSo6OARJlK6DbHHvrxz7pMGPGmC/caZSJJz6HRKHC1RPP001PRHCvG9Z/veD039uOQmazVuLJzlw==, tarball: https://registry.npmmirror.com/@rc-component/pagination/-/pagination-1.2.0.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/picker@1.9.0': + resolution: {integrity: sha512-OLisdk8AWVCG9goBU1dWzuH5QlBQk8jktmQ6p0/IyBFwdKGwyIZOSjnBYo8hooHiTdl0lU+wGf/OfMtVBw02KQ==, tarball: https://registry.npmmirror.com/@rc-component/picker/-/picker-1.9.0.tgz} + engines: {node: '>=12.x'} + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + '@rc-component/portal@2.2.0': + resolution: {integrity: sha512-oc6FlA+uXCMiwArHsJyHcIkX4q6uKyndrPol2eWX8YPkAnztHOPsFIRtmWG4BMlGE5h7YIRE3NiaJ5VS8Lb1QQ==, tarball: https://registry.npmmirror.com/@rc-component/portal/-/portal-2.2.0.tgz} + engines: {node: '>=12.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/progress@1.0.2': + resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==, tarball: https://registry.npmmirror.com/@rc-component/progress/-/progress-1.0.2.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.1.1': + resolution: {integrity: sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==, tarball: https://registry.npmmirror.com/@rc-component/qrcode/-/qrcode-1.1.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/rate@1.0.1': + resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==, tarball: https://registry.npmmirror.com/@rc-component/rate/-/rate-1.0.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/resize-observer@1.1.1': + resolution: {integrity: sha512-NfXXMmiR+SmUuKE1NwJESzEUYUFWIDUn2uXpxCTOLwiRUUakd62DRNFjRJArgzyFW8S5rsL4aX5XlyIXyC/vRA==, tarball: https://registry.npmmirror.com/@rc-component/resize-observer/-/resize-observer-1.1.1.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/segmented@1.2.3': + resolution: {integrity: sha512-L7G4S6zUpqHclOXK0wKKN2/VyqHa9tfDNxkoFjWOTPtQ0ROFaBwZhbf1+9sdZfIFkxJkpcShAmDOMEIBaFFqkw==, tarball: https://registry.npmmirror.com/@rc-component/segmented/-/segmented-1.2.3.tgz} + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@rc-component/select@1.3.6': + resolution: {integrity: sha512-CzbJ9TwmWcF5asvTMZ9BMiTE9CkkrigeOGRPpzCNmeZP7KBwwmYrmOIiKh9tMG7d6DyGAEAQ75LBxzPx+pGTHA==, tarball: https://registry.npmmirror.com/@rc-component/select/-/select-1.3.6.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/slider@1.0.1': + resolution: {integrity: sha512-uDhEPU1z3WDfCJhaL9jfd2ha/Eqpdfxsn0Zb0Xcq1NGQAman0TWaR37OWp2vVXEOdV2y0njSILTMpTfPV1454g==, tarball: https://registry.npmmirror.com/@rc-component/slider/-/slider-1.0.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/steps@1.2.2': + resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==, tarball: https://registry.npmmirror.com/@rc-component/steps/-/steps-1.2.2.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/switch@1.0.3': + resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==, tarball: https://registry.npmmirror.com/@rc-component/switch/-/switch-1.0.3.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/table@1.9.1': + resolution: {integrity: sha512-FVI5ZS/GdB3BcgexfCYKi3iHhZS3Fr59EtsxORszYGrfpH1eWr33eDNSYkVfLI6tfJ7vftJDd9D5apfFWqkdJg==, tarball: https://registry.npmmirror.com/@rc-component/table/-/table-1.9.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tabs@1.7.0': + resolution: {integrity: sha512-J48cs2iBi7Ho3nptBxxIqizEliUC+ExE23faspUQKGQ550vaBlv3aGF8Epv/UB1vFWeoJDTW/dNzgIU0Qj5i/w==, tarball: https://registry.npmmirror.com/@rc-component/tabs/-/tabs-1.7.0.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/textarea@1.1.2': + resolution: {integrity: sha512-9rMUEODWZDMovfScIEHXWlVZuPljZ2pd1LKNjslJVitn4SldEzq5vO1CL3yy3Dnib6zZal2r2DPtjy84VVpF6A==, tarball: https://registry.npmmirror.com/@rc-component/textarea/-/textarea-1.1.2.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tooltip@1.4.0': + resolution: {integrity: sha512-8Rx5DCctIlLI4raR0I0xHjVTf1aF48+gKCNeAAo5bmF5VoR5YED+A/XEqzXv9KKqrJDRcd3Wndpxh2hyzrTtSg==, tarball: https://registry.npmmirror.com/@rc-component/tooltip/-/tooltip-1.4.0.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/tour@2.2.1': + resolution: {integrity: sha512-BUCrVikGJsXli38qlJ+h2WyDD6dYxzDA9dV3o0ij6gYhAq6ooT08SUMWOikva9v4KZ2BEuluGl5bPcsjrSoBgQ==, tarball: https://registry.npmmirror.com/@rc-component/tour/-/tour-2.2.1.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tree-select@1.4.0': + resolution: {integrity: sha512-I3UAlO2hNqy9CSKc8EBaESgnmKk2QaRzuZ2XHZGFCgsSMkGl06mdF97sVfROM02YIb64ocgLKefsjE0Ch4ocwQ==, tarball: https://registry.npmmirror.com/@rc-component/tree-select/-/tree-select-1.4.0.tgz} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/tree@1.1.0': + resolution: {integrity: sha512-HZs3aOlvFgQdgrmURRc/f4IujiNBf4DdEeXUlkS0lPoLlx9RoqsZcF0caXIAMVb+NaWqKtGQDnrH8hqLCN5zlA==, tarball: https://registry.npmmirror.com/@rc-component/tree/-/tree-1.1.0.tgz} + engines: {node: '>=10.x'} + peerDependencies: + react: '*' + react-dom: '*' + + '@rc-component/trigger@3.9.0': + resolution: {integrity: sha512-X8btpwfrT27AgrZVOz4swclhEHTZcqaHeQMXXBgveagOiakTa36uObXbdwerXffgV8G9dH1fAAE0DHtVQs8EHg==, tarball: https://registry.npmmirror.com/@rc-component/trigger/-/trigger-3.9.0.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/upload@1.1.0': + resolution: {integrity: sha512-LIBV90mAnUE6VK5N4QvForoxZc4XqEYZimcp7fk+lkE4XwHHyJWxpIXQQwMU8hJM+YwBbsoZkGksL1sISWHQxw==, tarball: https://registry.npmmirror.com/@rc-component/upload/-/upload-1.1.0.tgz} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/util@1.7.0': + resolution: {integrity: sha512-tIvIGj4Vl6fsZFvWSkYw9sAfiCKUXMyhVz6kpKyZbwyZyRPqv2vxYZROdaO1VB4gqTNvUZFXh6i3APUiterw5g==, tarball: https://registry.npmmirror.com/@rc-component/util/-/util-1.7.0.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + '@rc-component/virtual-list@1.0.2': + resolution: {integrity: sha512-uvTol/mH74FYsn5loDGJxo+7kjkO4i+y4j87Re1pxJBs0FaeuMuLRzQRGaXwnMcV1CxpZLi2Z56Rerj2M00fjQ==, tarball: https://registry.npmmirror.com/@rc-component/virtual-list/-/virtual-list-1.0.2.tgz} + engines: {node: '>=8.x'} + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==, tarball: https://registry.npmmirror.com/@reduxjs/toolkit/-/toolkit-2.11.2.tgz} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@remix-run/router@1.23.0': + resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==, tarball: https://registry.npmmirror.com/@remix-run/router/-/router-1.23.0.tgz} + engines: {node: '>=14.0.0'} + + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==, tarball: https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz} + + '@rolldown/pluginutils@1.0.0-beta.53': + resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==, tarball: https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz} + + '@rollup/rollup-android-arm-eabi@4.56.0': + resolution: {integrity: sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.56.0': + resolution: {integrity: sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==, tarball: https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.56.0': + resolution: {integrity: sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.56.0': + resolution: {integrity: sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==, tarball: https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.56.0': + resolution: {integrity: sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.56.0': + resolution: {integrity: sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==, tarball: https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + resolution: {integrity: sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + resolution: {integrity: sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + resolution: {integrity: sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.56.0': + resolution: {integrity: sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + resolution: {integrity: sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.56.0': + resolution: {integrity: sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + resolution: {integrity: sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + resolution: {integrity: sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + resolution: {integrity: sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + resolution: {integrity: sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + resolution: {integrity: sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.56.0': + resolution: {integrity: sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.56.0': + resolution: {integrity: sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==, tarball: https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.56.0': + resolution: {integrity: sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==, tarball: https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.56.0': + resolution: {integrity: sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + resolution: {integrity: sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + resolution: {integrity: sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.56.0': + resolution: {integrity: sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.56.0': + resolution: {integrity: sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==, tarball: https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz} + cpu: [x64] + os: [win32] + + '@sentry-internal/browser-utils@10.32.0': + resolution: {integrity: sha512-LI83ZKv5ItRajfY7xmQpNs00nWNOXO+A6MCj8LNDpPouYA8m7VvqkCKG9Yh50a/5eIO9lbSWTrARO8rWR3c9jA==, tarball: https://registry.npmmirror.com/@sentry-internal/browser-utils/-/browser-utils-10.32.0.tgz} + engines: {node: '>=18'} + + '@sentry-internal/feedback@10.32.0': + resolution: {integrity: sha512-YjDdVR8Ep7lOGilRfSrioBKQlFzWP+j1ibL+9rfwYPWWeQSfK2mbg8+PmbWOcTIXZ/MpuZRMF6ZdkVi7VYBSJw==, tarball: https://registry.npmmirror.com/@sentry-internal/feedback/-/feedback-10.32.0.tgz} + engines: {node: '>=18'} + + '@sentry-internal/replay-canvas@10.32.0': + resolution: {integrity: sha512-GdhyRKywIP9IQc7RoTrBFjx2EFBjiRSndxeiW43qybO1xD2S5Cq/S7ZwkaJOXN8Ie1awm3/E6+mVct5jc6KKAg==, tarball: https://registry.npmmirror.com/@sentry-internal/replay-canvas/-/replay-canvas-10.32.0.tgz} + engines: {node: '>=18'} + + '@sentry-internal/replay@10.32.0': + resolution: {integrity: sha512-P6paw7bLDP72ZNJkcyKSXCXfd+/NKwRfnJpwgkRI9kjy9o0KZrIW2P/xTGftp5q1XxQ7tCt94j2gc1HelOpngw==, tarball: https://registry.npmmirror.com/@sentry-internal/replay/-/replay-10.32.0.tgz} + engines: {node: '>=18'} + + '@sentry/browser@10.32.0': + resolution: {integrity: sha512-NtQlXQybrWMbUOPENS4bvxzhMX1Oi+IUH9NXA/mZ06KbQntPLES7yfIKhLNpRipuCzeS16hCJp6HMao2bZB2Hg==, tarball: https://registry.npmmirror.com/@sentry/browser/-/browser-10.32.0.tgz} + engines: {node: '>=18'} + + '@sentry/core@10.32.0': + resolution: {integrity: sha512-E+ihb8+5PBfYMamnXHalgsmxkcG2YQqhRdgYf3yWJ5dJvi4njh1VWK3kNVj1GvsU6ktaielAx4Rg5dwEFMnbZg==, tarball: https://registry.npmmirror.com/@sentry/core/-/core-10.32.0.tgz} + engines: {node: '>=18'} + + '@sinclair/typebox@0.34.48': + resolution: {integrity: sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==, tarball: https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.34.48.tgz} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==, tarball: https://registry.npmmirror.com/@sinonjs/commons/-/commons-3.0.1.tgz} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==, tarball: https://registry.npmmirror.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==, tarball: https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==, tarball: https://registry.npmmirror.com/@standard-schema/utils/-/utils-0.3.0.tgz} + + '@tanstack/react-virtual@3.13.12': + resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==, tarball: https://registry.npmmirror.com/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.12': + resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==, tarball: https://registry.npmmirror.com/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==, tarball: https://registry.npmmirror.com/@testing-library/dom/-/dom-10.4.1.tgz} + engines: {node: '>=18'} + + '@testing-library/dom@8.20.1': + resolution: {integrity: sha512-/DiOQ5xBxgdYRC8LNk7U+RWat0S3qRLeIw3ZIkMQ9kkVlRmwD/Eg8k8CqIpD6GW7u20JIUOfMKbxtiLutpjQ4g==, tarball: https://registry.npmmirror.com/@testing-library/dom/-/dom-8.20.1.tgz} + engines: {node: '>=12'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==, tarball: https://registry.npmmirror.com/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/preact@3.2.4': + resolution: {integrity: sha512-F+kJ243LP6VmEK1M809unzTE/ijg+bsMNuiRN0JEDIJBELKKDNhdgC/WrUSZ7klwJvtlO3wQZ9ix+jhObG07Fg==, tarball: https://registry.npmmirror.com/@testing-library/preact/-/preact-3.2.4.tgz} + engines: {node: '>= 12'} + peerDependencies: + preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==, tarball: https://registry.npmmirror.com/@testing-library/react/-/react-16.3.0.tgz} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==, tarball: https://registry.npmmirror.com/@testing-library/user-event/-/user-event-14.6.1.tgz} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==, tarball: https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz} + engines: {node: '>=10.13.0'} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==, tarball: https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==, tarball: https://registry.npmmirror.com/@types/aria-query/-/aria-query-5.0.4.tgz} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==, tarball: https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==, tarball: https://registry.npmmirror.com/@types/babel__generator/-/babel__generator-7.27.0.tgz} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==, tarball: https://registry.npmmirror.com/@types/babel__template/-/babel__template-7.4.4.tgz} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==, tarball: https://registry.npmmirror.com/@types/babel__traverse/-/babel__traverse-7.28.0.tgz} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==, tarball: https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==, tarball: https://registry.npmmirror.com/@types/d3-array/-/d3-array-3.2.2.tgz} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==, tarball: https://registry.npmmirror.com/@types/d3-color/-/d3-color-3.1.3.tgz} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==, tarball: https://registry.npmmirror.com/@types/d3-ease/-/d3-ease-3.0.2.tgz} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==, tarball: https://registry.npmmirror.com/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==, tarball: https://registry.npmmirror.com/@types/d3-path/-/d3-path-3.1.1.tgz} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==, tarball: https://registry.npmmirror.com/@types/d3-scale/-/d3-scale-4.0.9.tgz} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==, tarball: https://registry.npmmirror.com/@types/d3-shape/-/d3-shape-3.1.8.tgz} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==, tarball: https://registry.npmmirror.com/@types/d3-time/-/d3-time-3.0.4.tgz} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==, tarball: https://registry.npmmirror.com/@types/d3-timer/-/d3-timer-3.0.2.tgz} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==, tarball: https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz} + + '@types/deep-freeze@0.1.5': + resolution: {integrity: sha512-KZtR+jtmgkCpgE0f+We/QEI2Fi0towBV/tTkvHVhMzx+qhUVGXMx7pWvAtDp6vEWIjdKLTKpqbI/sORRCo8TKg==, tarball: https://registry.npmmirror.com/@types/deep-freeze/-/deep-freeze-0.1.5.tgz} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==, tarball: https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz} + + '@types/history@4.7.11': + resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==, tarball: https://registry.npmmirror.com/@types/history/-/history-4.7.11.tgz} + + '@types/history@5.0.0': + resolution: {integrity: sha512-hy8b7Y1J8OGe6LbAjj3xniQrj3v6lsivCcrmf4TzSgPzLkhIeKgc5IZnT7ReIqmEuodjfO8EYAuoFvIrHi/+jQ==, tarball: https://registry.npmmirror.com/@types/history/-/history-5.0.0.tgz} + deprecated: This is a stub types definition. history provides its own type definitions, so you do not need this installed. + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==, tarball: https://registry.npmmirror.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==, tarball: https://registry.npmmirror.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==, tarball: https://registry.npmmirror.com/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz} + + '@types/jest@30.0.0': + resolution: {integrity: sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==, tarball: https://registry.npmmirror.com/@types/jest/-/jest-30.0.0.tgz} + + '@types/jsdom@21.1.7': + resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==, tarball: https://registry.npmmirror.com/@types/jsdom/-/jsdom-21.1.7.tgz} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==, tarball: https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz} + + '@types/lodash.debounce@4.0.9': + resolution: {integrity: sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==, tarball: https://registry.npmmirror.com/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz} + + '@types/lodash.orderby@4.6.9': + resolution: {integrity: sha512-T9o2wkIJOmxXwVTPTmwJ59W6eTi2FseiLR369fxszG649Po/xe9vqFNhf/MtnvT5jrbDiyWKxPFPZbpSVK0SVQ==, tarball: https://registry.npmmirror.com/@types/lodash.orderby/-/lodash.orderby-4.6.9.tgz} + + '@types/lodash.throttle@4.1.9': + resolution: {integrity: sha512-PCPVfpfueguWZQB7pJQK890F2scYKoDUL3iM522AptHWn7d5NQmeS/LTEHIcLr5PaTzl3dK2Z0xSUHHTHwaL5g==, tarball: https://registry.npmmirror.com/@types/lodash.throttle/-/lodash.throttle-4.1.9.tgz} + + '@types/lodash@4.17.21': + resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==, tarball: https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.21.tgz} + + '@types/node@24.10.9': + resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==, tarball: https://registry.npmmirror.com/@types/node/-/node-24.10.9.tgz} + + '@types/object-hash@3.0.6': + resolution: {integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==, tarball: https://registry.npmmirror.com/@types/object-hash/-/object-hash-3.0.6.tgz} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==, tarball: https://registry.npmmirror.com/@types/qs/-/qs-6.14.0.tgz} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==, tarball: https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-input-mask@3.0.6': + resolution: {integrity: sha512-+5I18WKyG3eWIj7TVPWfK1VitI9mPpS9y6jE/BfmTCe+iL27NfBw/yzKRvCFp1DRBvlvvcsiZf05bub0YC1k8A==, tarball: https://registry.npmmirror.com/@types/react-input-mask/-/react-input-mask-3.0.6.tgz} + + '@types/react-router-dom@5.3.3': + resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==, tarball: https://registry.npmmirror.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz} + + '@types/react-router@5.1.20': + resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==, tarball: https://registry.npmmirror.com/@types/react-router/-/react-router-5.1.20.tgz} + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==, tarball: https://registry.npmmirror.com/@types/react/-/react-19.2.7.tgz} + + '@types/redux-actions@2.2.1': + resolution: {integrity: sha512-hOQsydO5XSQtt/WIElYAPoFu6D8H0IpAsHFhmUvEvWGFGuKD7ZiWkI2vX9is3ZqmQSVTiQ0/SrBzlrwYHICi3A==, tarball: https://registry.npmmirror.com/@types/redux-actions/-/redux-actions-2.2.1.tgz} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==, tarball: https://registry.npmmirror.com/@types/stack-utils/-/stack-utils-2.0.3.tgz} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==, tarball: https://registry.npmmirror.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==, tarball: https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==, tarball: https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==, tarball: https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.35.tgz} + + '@typescript-eslint/eslint-plugin@8.53.1': + resolution: {integrity: sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==, tarball: https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.53.1 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.53.1': + resolution: {integrity: sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==, tarball: https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.53.1': + resolution: {integrity: sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==, tarball: https://registry.npmmirror.com/@typescript-eslint/project-service/-/project-service-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.53.1': + resolution: {integrity: sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==, tarball: https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.53.1': + resolution: {integrity: sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==, tarball: https://registry.npmmirror.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.53.1': + resolution: {integrity: sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==, tarball: https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.53.1': + resolution: {integrity: sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==, tarball: https://registry.npmmirror.com/@typescript-eslint/types/-/types-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.53.1': + resolution: {integrity: sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==, tarball: https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.53.1': + resolution: {integrity: sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==, tarball: https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.53.1': + resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==, tarball: https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==, tarball: https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==, tarball: https://registry.npmmirror.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-legacy@7.2.1': + resolution: {integrity: sha512-CaXb/y0mlfu7jQRELEJJc2/5w2bX2m1JraARgFnvSB2yfvnCNJVWWlqAo6WjnKoepOwKx8gs0ugJThPLKCOXIg==, tarball: https://registry.npmmirror.com/@vitejs/plugin-legacy/-/plugin-legacy-7.2.1.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + terser: ^5.16.0 + vite: ^7.0.0 + + '@vitejs/plugin-react@5.1.1': + resolution: {integrity: sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==, tarball: https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitejs/plugin-react@5.1.2': + resolution: {integrity: sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==, tarball: https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==, tarball: https://registry.npmmirror.com/@vitest/expect/-/expect-3.2.4.tgz} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==, tarball: https://registry.npmmirror.com/@vitest/mocker/-/mocker-3.2.4.tgz} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==, tarball: https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-3.2.4.tgz} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==, tarball: https://registry.npmmirror.com/@vitest/runner/-/runner-3.2.4.tgz} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==, tarball: https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-3.2.4.tgz} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==, tarball: https://registry.npmmirror.com/@vitest/spy/-/spy-3.2.4.tgz} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==, tarball: https://registry.npmmirror.com/@vitest/utils/-/utils-3.2.4.tgz} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==, tarball: https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==, tarball: https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==, tarball: https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==, tarball: https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==, tarball: https://registry.npmmirror.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz} + engines: {node: '>=8'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-5.0.1.tgz} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, tarball: https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-4.3.0.tgz} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-5.2.0.tgz} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, tarball: https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz} + engines: {node: '>=12'} + + antd@6.1.1: + resolution: {integrity: sha512-GBVxq3ShcYN/lEALvLPQDFN0bWjp2gQaFvRIXu4cwvXQcbV/D2FhShhTiNWhXxUk6nVkODBi4Uzo9SfLbDGsng==, tarball: https://registry.npmmirror.com/antd/-/antd-6.1.1.tgz} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==, tarball: https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==, tarball: https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==, tarball: https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz} + + aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==, tarball: https://registry.npmmirror.com/aria-query/-/aria-query-5.1.3.tgz} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==, tarball: https://registry.npmmirror.com/aria-query/-/aria-query-5.3.0.tgz} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==, tarball: https://registry.npmmirror.com/aria-query/-/aria-query-5.3.2.tgz} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==, tarball: https://registry.npmmirror.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==, tarball: https://registry.npmmirror.com/array-includes/-/array-includes-3.1.9.tgz} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==, tarball: https://registry.npmmirror.com/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==, tarball: https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==, tarball: https://registry.npmmirror.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==, tarball: https://registry.npmmirror.com/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==, tarball: https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==, tarball: https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz} + engines: {node: '>=12'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==, tarball: https://registry.npmmirror.com/async-function/-/async-function-1.0.0.tgz} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==, tarball: https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz} + engines: {node: '>= 0.4'} + + babel-jest@30.2.0: + resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==, tarball: https://registry.npmmirror.com/babel-jest/-/babel-jest-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-0 + + babel-plugin-inline-react-svg@2.0.2: + resolution: {integrity: sha512-iM9obPpCcdPE1EJE+UF+tni7CZ4q/OvdDm/TeBBHAYAEOqDcFd7fdnmym6OYAQMYfEpUnRYUYx2KxSUyo4cQxQ==, tarball: https://registry.npmmirror.com/babel-plugin-inline-react-svg/-/babel-plugin-inline-react-svg-2.0.2.tgz} + engines: {node: '>=10.13'} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-plugin-istanbul@7.0.1: + resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==, tarball: https://registry.npmmirror.com/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@30.2.0: + resolution: {integrity: sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==, tarball: https://registry.npmmirror.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-plugin-polyfill-corejs2@0.4.15: + resolution: {integrity: sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw==, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.15.tgz} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.6: + resolution: {integrity: sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A==, tarball: https://registry.npmmirror.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.6.tgz} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-react-remove-properties@0.3.1: + resolution: {integrity: sha512-JQ0oEgC4P6TZxSqEaoPZdbgE2L38Kz7RgTgGcOUUR24Z19OiGhnhdXkXkwwsYyx6LOr/bMY5ljDADhxDr/JQug==, tarball: https://registry.npmmirror.com/babel-plugin-react-remove-properties/-/babel-plugin-react-remove-properties-0.3.1.tgz} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==, tarball: https://registry.npmmirror.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@30.2.0: + resolution: {integrity: sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==, tarball: https://registry.npmmirror.com/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 || ^8.0.0-beta.1 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==, tarball: https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz} + + baseline-browser-mapping@2.9.18: + resolution: {integrity: sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==, tarball: https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz} + hasBin: true + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==, tarball: https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.12.tgz} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==, tarball: https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==, tarball: https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz} + engines: {node: '>=8'} + + browserslist-to-esbuild@2.1.1: + resolution: {integrity: sha512-KN+mty6C3e9AN8Z5dI1xeN15ExcRNeISoC3g7V0Kax/MMF9MSoYA2G7lkTTcVUFntiEjkpI0HNgqJC1NjdyNUw==, tarball: https://registry.npmmirror.com/browserslist-to-esbuild/-/browserslist-to-esbuild-2.1.1.tgz} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + browserslist: '*' + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==, tarball: https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==, tarball: https://registry.npmmirror.com/bser/-/bser-2.1.1.tgz} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==, tarball: https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, tarball: https://registry.npmmirror.com/cac/-/cac-6.7.14.tgz} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==, tarball: https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==, tarball: https://registry.npmmirror.com/call-bind/-/call-bind-1.0.8.tgz} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==, tarball: https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==, tarball: https://registry.npmmirror.com/callsites/-/callsites-3.1.0.tgz} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==, tarball: https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==, tarball: https://registry.npmmirror.com/camelcase/-/camelcase-6.3.0.tgz} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001766: + resolution: {integrity: sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==, tarball: https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==, tarball: https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==, tarball: https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz} + engines: {node: '>=10'} + + chance@1.1.13: + resolution: {integrity: sha512-V6lQCljcLznE7tUYUM9EOAnnKXbctE6j/rdQkYOHIWbfGQbrzTsAXNW9CdU5XCo4ArXQCj/rb6HgxPlmGJcaUg==, tarball: https://registry.npmmirror.com/chance/-/chance-1.1.13.tgz} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==, tarball: https://registry.npmmirror.com/char-regex/-/char-regex-1.0.2.tgz} + engines: {node: '>=10'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==, tarball: https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==, tarball: https://registry.npmmirror.com/chokidar/-/chokidar-4.0.3.tgz} + engines: {node: '>= 14.16.0'} + + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==, tarball: https://registry.npmmirror.com/ci-info/-/ci-info-4.3.1.tgz} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==, tarball: https://registry.npmmirror.com/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz} + + classnames@2.5.1: + resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==, tarball: https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==, tarball: https://registry.npmmirror.com/cliui/-/cliui-8.0.1.tgz} + engines: {node: '>=12'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==, tarball: https://registry.npmmirror.com/clsx/-/clsx-2.1.1.tgz} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==, tarball: https://registry.npmmirror.com/co/-/co-4.6.0.tgz} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==, tarball: https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==, tarball: https://registry.npmmirror.com/color-convert/-/color-convert-2.0.1.tgz} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==, tarball: https://registry.npmmirror.com/color-name/-/color-name-1.1.4.tgz} + + colorjs.io@0.5.2: + resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==, tarball: https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.5.2.tgz} + + combokeys@3.0.1: + resolution: {integrity: sha512-5nAfaLZ3oO3kA+/xdoL7t197UJTz2WWidyH3BBeU6hqHtvyFERICd0y3DQFrQkJFTKBrtUDck/xCLLoFpnjaCw==, tarball: https://registry.npmmirror.com/combokeys/-/combokeys-3.0.1.tgz} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==, tarball: https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==, tarball: https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz} + engines: {node: '>= 10'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==, tarball: https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==, tarball: https://registry.npmmirror.com/concat-map/-/concat-map-0.0.1.tgz} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==, tarball: https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz} + + copy-anything@2.0.6: + resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==, tarball: https://registry.npmmirror.com/copy-anything/-/copy-anything-2.0.6.tgz} + + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==, tarball: https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz} + + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==, tarball: https://registry.npmmirror.com/core-js-compat/-/core-js-compat-3.48.0.tgz} + + core-js-pure@3.48.0: + resolution: {integrity: sha512-1slJgk89tWC51HQ1AEqG+s2VuwpTRr8ocu4n20QUcH1v9lAN0RXen0Q0AABa/DK1I7RrNWLucplOHMx8hfTGTw==, tarball: https://registry.npmmirror.com/core-js-pure/-/core-js-pure-3.48.0.tgz} + + core-js@3.48.0: + resolution: {integrity: sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==, tarball: https://registry.npmmirror.com/core-js/-/core-js-3.48.0.tgz} + + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==, tarball: https://registry.npmmirror.com/cross-env/-/cross-env-7.0.3.tgz} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==, tarball: https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz} + engines: {node: '>= 8'} + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==, tarball: https://registry.npmmirror.com/css-select/-/css-select-4.3.0.tgz} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==, tarball: https://registry.npmmirror.com/css-tree/-/css-tree-1.1.3.tgz} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==, tarball: https://registry.npmmirror.com/css-what/-/css-what-6.2.2.tgz} + engines: {node: '>= 6'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==, tarball: https://registry.npmmirror.com/css.escape/-/css.escape-1.5.1.tgz} + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==, tarball: https://registry.npmmirror.com/csso/-/csso-4.2.0.tgz} + engines: {node: '>=8.0.0'} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==, tarball: https://registry.npmmirror.com/cssstyle/-/cssstyle-4.6.0.tgz} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==, tarball: https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==, tarball: https://registry.npmmirror.com/d3-array/-/d3-array-3.2.4.tgz} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==, tarball: https://registry.npmmirror.com/d3-color/-/d3-color-3.1.0.tgz} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==, tarball: https://registry.npmmirror.com/d3-ease/-/d3-ease-3.0.1.tgz} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==, tarball: https://registry.npmmirror.com/d3-format/-/d3-format-3.1.2.tgz} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==, tarball: https://registry.npmmirror.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==, tarball: https://registry.npmmirror.com/d3-path/-/d3-path-3.1.0.tgz} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==, tarball: https://registry.npmmirror.com/d3-scale/-/d3-scale-4.0.2.tgz} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==, tarball: https://registry.npmmirror.com/d3-shape/-/d3-shape-3.2.0.tgz} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==, tarball: https://registry.npmmirror.com/d3-time-format/-/d3-time-format-4.1.0.tgz} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==, tarball: https://registry.npmmirror.com/d3-time/-/d3-time-3.1.0.tgz} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==, tarball: https://registry.npmmirror.com/d3-timer/-/d3-timer-3.0.1.tgz} + engines: {node: '>=12'} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==, tarball: https://registry.npmmirror.com/data-urls/-/data-urls-5.0.0.tgz} + engines: {node: '>=18'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==, tarball: https://registry.npmmirror.com/data-view-buffer/-/data-view-buffer-1.0.2.tgz} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==, tarball: https://registry.npmmirror.com/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==, tarball: https://registry.npmmirror.com/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz} + engines: {node: '>= 0.4'} + + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==, tarball: https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==, tarball: https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==, tarball: https://registry.npmmirror.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==, tarball: https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz} + + decode-uri-component@0.4.1: + resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, tarball: https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz} + engines: {node: '>=14.16'} + + dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==, tarball: https://registry.npmmirror.com/dedent/-/dedent-1.7.1.tgz} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==, tarball: https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz} + engines: {node: '>=6'} + + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==, tarball: https://registry.npmmirror.com/deep-equal/-/deep-equal-2.2.3.tgz} + engines: {node: '>= 0.4'} + + deep-freeze@0.0.1: + resolution: {integrity: sha512-Z+z8HiAvsGwmjqlphnHW5oz6yWlOwu6EQfFTjmeTWlDeda3FS2yv3jhq35TX/ewmsnqB+RX2IdsIOyjJCQN5tg==, tarball: https://registry.npmmirror.com/deep-freeze/-/deep-freeze-0.0.1.tgz} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==, tarball: https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==, tarball: https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz} + engines: {node: '>=0.10.0'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.4.tgz} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==, tarball: https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==, tarball: https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==, tarball: https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==, tarball: https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz} + engines: {node: '>=8'} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==, tarball: https://registry.npmmirror.com/detect-newline/-/detect-newline-3.1.0.tgz} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==, tarball: https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz} + engines: {node: '>=0.10.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==, tarball: https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==, tarball: https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==, tarball: https://registry.npmmirror.com/dom-serializer/-/dom-serializer-1.4.1.tgz} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==, tarball: https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==, tarball: https://registry.npmmirror.com/domhandler/-/domhandler-4.3.1.tgz} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==, tarball: https://registry.npmmirror.com/domutils/-/domutils-2.8.0.tgz} + + drange@2.0.1: + resolution: {integrity: sha512-s/Gs2VG0zdUXxWz4cK3vcwniF914RKWLNYY6YAVTTLo2HjqsynfUzUcSvNFEdktQXKDbJMKF9wmg67Rv32VhEQ==, tarball: https://registry.npmmirror.com/drange/-/drange-2.0.1.tgz} + engines: {node: '>=6'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==, tarball: https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==, tarball: https://registry.npmmirror.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz} + + electron-to-chromium@1.5.278: + resolution: {integrity: sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==, tarball: https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==, tarball: https://registry.npmmirror.com/emittery/-/emittery-0.13.1.tgz} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==, tarball: https://registry.npmmirror.com/emoji-regex/-/emoji-regex-9.2.2.tgz} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==, tarball: https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==, tarball: https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz} + engines: {node: '>=0.12'} + + errno@0.1.8: + resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==, tarball: https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==, tarball: https://registry.npmmirror.com/error-ex/-/error-ex-1.3.4.tgz} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==, tarball: https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==, tarball: https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==, tarball: https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz} + engines: {node: '>= 0.4'} + + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==, tarball: https://registry.npmmirror.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz} + + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==, tarball: https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==, tarball: https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==, tarball: https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==, tarball: https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==, tarball: https://registry.npmmirror.com/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==, tarball: https://registry.npmmirror.com/es-to-primitive/-/es-to-primitive-1.3.0.tgz} + engines: {node: '>= 0.4'} + + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==, tarball: https://registry.npmmirror.com/es-toolkit/-/es-toolkit-1.44.0.tgz} + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==, tarball: https://registry.npmmirror.com/esbuild/-/esbuild-0.27.2.tgz} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==, tarball: https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz} + engines: {node: '>=6'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==, tarball: https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==, tarball: https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz} + engines: {node: '>=10'} + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==, tarball: https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-plugin-unused-imports@4.3.0: + resolution: {integrity: sha512-ZFBmXMGBYfHttdRtOG9nFFpmUvMtbHSjsKrS20vdWdbfiVYsO3yA2SGYy9i9XmZJDfMGBflZGBCm70SEnFQtOA==, tarball: https://registry.npmmirror.com/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.3.0.tgz} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==, tarball: https://registry.npmmirror.com/eslint-scope/-/eslint-scope-8.4.0.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==, tarball: https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==, tarball: https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==, tarball: https://registry.npmmirror.com/eslint/-/eslint-9.39.2.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==, tarball: https://registry.npmmirror.com/espree/-/espree-10.4.0.tgz} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==, tarball: https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz} + engines: {node: '>=4'} + hasBin: true + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==, tarball: https://registry.npmmirror.com/esquery/-/esquery-1.7.0.tgz} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==, tarball: https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==, tarball: https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==, tarball: https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==, tarball: https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, tarball: https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==, tarball: https://registry.npmmirror.com/execa/-/execa-5.1.1.tgz} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==, tarball: https://registry.npmmirror.com/exit-x/-/exit-x-0.2.2.tgz} + engines: {node: '>= 0.8.0'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==, tarball: https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz} + engines: {node: '>=12.0.0'} + + expect@30.2.0: + resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==, tarball: https://registry.npmmirror.com/expect/-/expect-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==, tarball: https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==, tarball: https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==, tarball: https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==, tarball: https://registry.npmmirror.com/fb-watchman/-/fb-watchman-2.0.2.tgz} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==, tarball: https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==, tarball: https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==, tarball: https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz} + engines: {node: '>=8'} + + filter-obj@5.1.0: + resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, tarball: https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz} + engines: {node: '>=14.16'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==, tarball: https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==, tarball: https://registry.npmmirror.com/find-up/-/find-up-5.0.0.tgz} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==, tarball: https://registry.npmmirror.com/flat-cache/-/flat-cache-4.0.1.tgz} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==, tarball: https://registry.npmmirror.com/flatted/-/flatted-3.3.3.tgz} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==, tarball: https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==, tarball: https://registry.npmmirror.com/foreground-child/-/foreground-child-3.3.1.tgz} + engines: {node: '>=14'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==, tarball: https://registry.npmmirror.com/fs.realpath/-/fs.realpath-1.0.0.tgz} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==, tarball: https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==, tarball: https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==, tarball: https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.8.tgz} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==, tarball: https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==, tarball: https://registry.npmmirror.com/generator-function/-/generator-function-2.0.1.tgz} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==, tarball: https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==, tarball: https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==, tarball: https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==, tarball: https://registry.npmmirror.com/get-package-type/-/get-package-type-0.1.0.tgz} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==, tarball: https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==, tarball: https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==, tarball: https://registry.npmmirror.com/get-symbol-description/-/get-symbol-description-1.1.0.tgz} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==, tarball: https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==, tarball: https://registry.npmmirror.com/glob/-/glob-10.5.0.tgz} + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==, tarball: https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz} + deprecated: Glob versions prior to v9 are no longer supported + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==, tarball: https://registry.npmmirror.com/globals/-/globals-14.0.0.tgz} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==, tarball: https://registry.npmmirror.com/globals/-/globals-16.5.0.tgz} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==, tarball: https://registry.npmmirror.com/globalthis/-/globalthis-1.0.4.tgz} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==, tarball: https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==, tarball: https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==, tarball: https://registry.npmmirror.com/has-bigints/-/has-bigints-1.1.0.tgz} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==, tarball: https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==, tarball: https://registry.npmmirror.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==, tarball: https://registry.npmmirror.com/has-proto/-/has-proto-1.2.0.tgz} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==, tarball: https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==, tarball: https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==, tarball: https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz} + engines: {node: '>= 0.4'} + + history@4.10.1: + resolution: {integrity: sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==, tarball: https://registry.npmmirror.com/history/-/history-4.10.1.tgz} + + history@5.3.0: + resolution: {integrity: sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==, tarball: https://registry.npmmirror.com/history/-/history-5.3.0.tgz} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==, tarball: https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==, tarball: https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz} + engines: {node: '>=18'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==, tarball: https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==, tarball: https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==, tarball: https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz} + engines: {node: '>= 14'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==, tarball: https://registry.npmmirror.com/human-signals/-/human-signals-2.1.0.tgz} + engines: {node: '>=10.17.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==, tarball: https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.6.3.tgz} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==, tarball: https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==, tarball: https://registry.npmmirror.com/ignore/-/ignore-7.0.5.tgz} + engines: {node: '>= 4'} + + image-size@0.5.5: + resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==, tarball: https://registry.npmmirror.com/image-size/-/image-size-0.5.5.tgz} + engines: {node: '>=0.10.0'} + hasBin: true + + imask@7.6.1: + resolution: {integrity: sha512-sJlIFM7eathUEMChTh9Mrfw/IgiWgJqBKq2VNbyXvBZ7ev/IlO6/KQTKlV/Fm+viQMLrFLG/zCuudrLIwgK2dg==, tarball: https://registry.npmmirror.com/imask/-/imask-7.6.1.tgz} + engines: {npm: '>=4.0.0'} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==, tarball: https://registry.npmmirror.com/immer/-/immer-10.2.0.tgz} + + immer@11.1.3: + resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==, tarball: https://registry.npmmirror.com/immer/-/immer-11.1.3.tgz} + + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==, tarball: https://registry.npmmirror.com/immutable/-/immutable-5.1.4.tgz} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==, tarball: https://registry.npmmirror.com/import-fresh/-/import-fresh-3.3.1.tgz} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==, tarball: https://registry.npmmirror.com/import-local/-/import-local-3.2.0.tgz} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==, tarball: https://registry.npmmirror.com/imurmurhash/-/imurmurhash-0.1.4.tgz} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==, tarball: https://registry.npmmirror.com/indent-string/-/indent-string-4.0.0.tgz} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==, tarball: https://registry.npmmirror.com/inflight/-/inflight-1.0.6.tgz} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==, tarball: https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==, tarball: https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz} + engines: {node: '>= 0.4'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==, tarball: https://registry.npmmirror.com/internmap/-/internmap-2.0.3.tgz} + engines: {node: '>=12'} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==, tarball: https://registry.npmmirror.com/is-arguments/-/is-arguments-1.2.0.tgz} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==, tarball: https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==, tarball: https://registry.npmmirror.com/is-arrayish/-/is-arrayish-0.2.1.tgz} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==, tarball: https://registry.npmmirror.com/is-async-function/-/is-async-function-2.1.1.tgz} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==, tarball: https://registry.npmmirror.com/is-bigint/-/is-bigint-1.1.0.tgz} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==, tarball: https://registry.npmmirror.com/is-boolean-object/-/is-boolean-object-1.2.2.tgz} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==, tarball: https://registry.npmmirror.com/is-callable/-/is-callable-1.2.7.tgz} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==, tarball: https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==, tarball: https://registry.npmmirror.com/is-data-view/-/is-data-view-1.0.2.tgz} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==, tarball: https://registry.npmmirror.com/is-date-object/-/is-date-object-1.1.0.tgz} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==, tarball: https://registry.npmmirror.com/is-docker/-/is-docker-2.2.1.tgz} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==, tarball: https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==, tarball: https://registry.npmmirror.com/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==, tarball: https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==, tarball: https://registry.npmmirror.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz} + engines: {node: '>=6'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==, tarball: https://registry.npmmirror.com/is-generator-function/-/is-generator-function-1.1.2.tgz} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==, tarball: https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==, tarball: https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz} + engines: {node: '>= 0.4'} + + is-mobile@5.0.0: + resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==, tarball: https://registry.npmmirror.com/is-mobile/-/is-mobile-5.0.0.tgz} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==, tarball: https://registry.npmmirror.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==, tarball: https://registry.npmmirror.com/is-number-object/-/is-number-object-1.1.1.tgz} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==, tarball: https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==, tarball: https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==, tarball: https://registry.npmmirror.com/is-regex/-/is-regex-1.2.1.tgz} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==, tarball: https://registry.npmmirror.com/is-set/-/is-set-2.0.3.tgz} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==, tarball: https://registry.npmmirror.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==, tarball: https://registry.npmmirror.com/is-stream/-/is-stream-2.0.1.tgz} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==, tarball: https://registry.npmmirror.com/is-string/-/is-string-1.1.1.tgz} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==, tarball: https://registry.npmmirror.com/is-symbol/-/is-symbol-1.1.1.tgz} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==, tarball: https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.15.tgz} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==, tarball: https://registry.npmmirror.com/is-weakmap/-/is-weakmap-2.0.2.tgz} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==, tarball: https://registry.npmmirror.com/is-weakref/-/is-weakref-1.1.1.tgz} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==, tarball: https://registry.npmmirror.com/is-weakset/-/is-weakset-2.0.4.tgz} + engines: {node: '>= 0.4'} + + is-what@3.14.1: + resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==, tarball: https://registry.npmmirror.com/is-what/-/is-what-3.14.1.tgz} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==, tarball: https://registry.npmmirror.com/is-wsl/-/is-wsl-2.2.0.tgz} + engines: {node: '>=8'} + + isarray@0.0.1: + resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==, tarball: https://registry.npmmirror.com/isarray/-/isarray-0.0.1.tgz} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==, tarball: https://registry.npmmirror.com/isarray/-/isarray-2.0.5.tgz} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==, tarball: https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz} + + isomorphic-fetch@3.0.0: + resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==, tarball: https://registry.npmmirror.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==, tarball: https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==, tarball: https://registry.npmmirror.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==, tarball: https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==, tarball: https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==, tarball: https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz} + engines: {node: '>=8'} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==, tarball: https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==, tarball: https://registry.npmmirror.com/jackspeak/-/jackspeak-3.4.3.tgz} + + jest-changed-files@30.2.0: + resolution: {integrity: sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==, tarball: https://registry.npmmirror.com/jest-changed-files/-/jest-changed-files-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.2.0: + resolution: {integrity: sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==, tarball: https://registry.npmmirror.com/jest-circus/-/jest-circus-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.2.0: + resolution: {integrity: sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==, tarball: https://registry.npmmirror.com/jest-cli/-/jest-cli-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.2.0: + resolution: {integrity: sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==, tarball: https://registry.npmmirror.com/jest-config/-/jest-config-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@30.2.0: + resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==, tarball: https://registry.npmmirror.com/jest-diff/-/jest-diff-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.2.0: + resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==, tarball: https://registry.npmmirror.com/jest-docblock/-/jest-docblock-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.2.0: + resolution: {integrity: sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==, tarball: https://registry.npmmirror.com/jest-each/-/jest-each-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-jsdom@30.2.0: + resolution: {integrity: sha512-zbBTiqr2Vl78pKp/laGBREYzbZx9ZtqPjOK4++lL4BNDhxRnahg51HtoDrk9/VjIy9IthNEWdKVd7H5bqBhiWQ==, tarball: https://registry.npmmirror.com/jest-environment-jsdom/-/jest-environment-jsdom-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@30.2.0: + resolution: {integrity: sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==, tarball: https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-haste-map@30.2.0: + resolution: {integrity: sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==, tarball: https://registry.npmmirror.com/jest-haste-map/-/jest-haste-map-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-junit@16.0.0: + resolution: {integrity: sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==, tarball: https://registry.npmmirror.com/jest-junit/-/jest-junit-16.0.0.tgz} + engines: {node: '>=10.12.0'} + + jest-leak-detector@30.2.0: + resolution: {integrity: sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==, tarball: https://registry.npmmirror.com/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@30.2.0: + resolution: {integrity: sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==, tarball: https://registry.npmmirror.com/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@30.2.0: + resolution: {integrity: sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==, tarball: https://registry.npmmirror.com/jest-message-util/-/jest-message-util-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@30.2.0: + resolution: {integrity: sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==, tarball: https://registry.npmmirror.com/jest-mock/-/jest-mock-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==, tarball: https://registry.npmmirror.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==, tarball: https://registry.npmmirror.com/jest-regex-util/-/jest-regex-util-30.0.1.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.2.0: + resolution: {integrity: sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==, tarball: https://registry.npmmirror.com/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.2.0: + resolution: {integrity: sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==, tarball: https://registry.npmmirror.com/jest-resolve/-/jest-resolve-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.2.0: + resolution: {integrity: sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==, tarball: https://registry.npmmirror.com/jest-runner/-/jest-runner-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.2.0: + resolution: {integrity: sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==, tarball: https://registry.npmmirror.com/jest-runtime/-/jest-runtime-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.2.0: + resolution: {integrity: sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==, tarball: https://registry.npmmirror.com/jest-snapshot/-/jest-snapshot-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@30.2.0: + resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==, tarball: https://registry.npmmirror.com/jest-util/-/jest-util-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.2.0: + resolution: {integrity: sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==, tarball: https://registry.npmmirror.com/jest-validate/-/jest-validate-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.2.0: + resolution: {integrity: sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==, tarball: https://registry.npmmirror.com/jest-watcher/-/jest-watcher-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@30.2.0: + resolution: {integrity: sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==, tarball: https://registry.npmmirror.com/jest-worker/-/jest-worker-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.2.0: + resolution: {integrity: sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==, tarball: https://registry.npmmirror.com/jest/-/jest-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==, tarball: https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==, tarball: https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==, tarball: https://registry.npmmirror.com/js-yaml/-/js-yaml-3.14.2.tgz} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==, tarball: https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.1.tgz} + hasBin: true + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==, tarball: https://registry.npmmirror.com/jsdom/-/jsdom-26.1.0.tgz} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==, tarball: https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==, tarball: https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==, tarball: https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==, tarball: https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, tarball: https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz} + + json2mq@0.2.0: + resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==, tarball: https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==, tarball: https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==, tarball: https://registry.npmmirror.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz} + engines: {node: '>=4.0'} + + just-curry-it@5.3.0: + resolution: {integrity: sha512-silMIRiFjUWlfaDhkgSzpuAyQ6EX/o09Eu8ZBfmFwQMbax7+LQzeIU2CBrICT6Ne4l86ITCGvUCBpCubWYy0Yw==, tarball: https://registry.npmmirror.com/just-curry-it/-/just-curry-it-5.3.0.tgz} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==, tarball: https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz} + + less@4.5.1: + resolution: {integrity: sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==, tarball: https://registry.npmmirror.com/less/-/less-4.5.1.tgz} + engines: {node: '>=14'} + hasBin: true + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==, tarball: https://registry.npmmirror.com/leven/-/leven-3.1.0.tgz} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==, tarball: https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz} + engines: {node: '>= 0.8.0'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, tarball: https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==, tarball: https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==, tarball: https://registry.npmmirror.com/locate-path/-/locate-path-6.0.0.tgz} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==, tarball: https://registry.npmmirror.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==, tarball: https://registry.npmmirror.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==, tarball: https://registry.npmmirror.com/lodash.merge/-/lodash.merge-4.6.2.tgz} + + lodash.orderby@4.6.0: + resolution: {integrity: sha512-T0rZxKmghOOf5YPnn8EY5iLYeWCpZq8G41FfqoVHH5QDTAFaghJRmAdLiadEDq+ztgM2q5PjA+Z1fOwGrLgmtg==, tarball: https://registry.npmmirror.com/lodash.orderby/-/lodash.orderby-4.6.0.tgz} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==, tarball: https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, tarball: https://registry.npmmirror.com/lodash/-/lodash-4.17.21.tgz} + + logfmt@1.4.0: + resolution: {integrity: sha512-p1Ow0C2dDJYaQBhRHt+HVMP6ELuBm4jYSYNHPMfz0J5wJ9qA6/7oBOlBZBfT1InqguTYcvJzNea5FItDxTcbyw==, tarball: https://registry.npmmirror.com/logfmt/-/logfmt-1.4.0.tgz} + hasBin: true + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==, tarball: https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==, tarball: https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==, tarball: https://registry.npmmirror.com/lru-cache/-/lru-cache-10.4.3.tgz} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==, tarball: https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz} + + lru-memoize@1.1.0: + resolution: {integrity: sha512-klI4QO8wE8V6rl8EwQTZP6vgM0URWN/0OMFdG9E2fJTaWc6Ox+nSTs8cgJ8QNd0We0vX40n+sd6K4cgb+BzXuA==, tarball: https://registry.npmmirror.com/lru-memoize/-/lru-memoize-1.1.0.tgz} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==, tarball: https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==, tarball: https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz} + + make-dir@2.1.0: + resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==, tarball: https://registry.npmmirror.com/make-dir/-/make-dir-2.1.0.tgz} + engines: {node: '>=6'} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==, tarball: https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz} + engines: {node: '>=10'} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==, tarball: https://registry.npmmirror.com/makeerror/-/makeerror-1.0.12.tgz} + + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==, tarball: https://registry.npmmirror.com/marked/-/marked-16.4.2.tgz} + engines: {node: '>= 20'} + hasBin: true + + match-sorter@8.2.0: + resolution: {integrity: sha512-qRVB7wYMJXizAWR4TKo5UYwgW7oAVzA8V9jve0wGzRvV91ou9dcqL+/2gJtD0PZ/Pm2Fq6cVT4VHXHmDFVMGRA==, tarball: https://registry.npmmirror.com/match-sorter/-/match-sorter-8.2.0.tgz} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==, tarball: https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz} + engines: {node: '>= 0.4'} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==, tarball: https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.14.tgz} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==, tarball: https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==, tarball: https://registry.npmmirror.com/meow/-/meow-13.2.0.tgz} + engines: {node: '>=18'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==, tarball: https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==, tarball: https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz} + engines: {node: '>=8.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==, tarball: https://registry.npmmirror.com/mime/-/mime-1.6.0.tgz} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==, tarball: https://registry.npmmirror.com/mimic-fn/-/mimic-fn-2.1.0.tgz} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==, tarball: https://registry.npmmirror.com/min-indent/-/min-indent-1.0.1.tgz} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==, tarball: https://registry.npmmirror.com/minimatch/-/minimatch-9.0.5.tgz} + engines: {node: '>=16 || 14 >=14.17'} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==, tarball: https://registry.npmmirror.com/minipass/-/minipass-7.1.2.tgz} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==, tarball: https://registry.npmmirror.com/mkdirp/-/mkdirp-1.0.4.tgz} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==, tarball: https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==, tarball: https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==, tarball: https://registry.npmmirror.com/napi-postinstall/-/napi-postinstall-0.3.4.tgz} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==, tarball: https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz} + + needle@3.3.1: + resolution: {integrity: sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==, tarball: https://registry.npmmirror.com/needle/-/needle-3.3.1.tgz} + engines: {node: '>= 4.4.x'} + hasBin: true + + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==, tarball: https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==, tarball: https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==, tarball: https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==, tarball: https://registry.npmmirror.com/normalize-path/-/normalize-path-3.0.0.tgz} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==, tarball: https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz} + engines: {node: '>=8'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==, tarball: https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==, tarball: https://registry.npmmirror.com/nwsapi/-/nwsapi-2.2.23.tgz} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==, tarball: https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==, tarball: https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==, tarball: https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz} + engines: {node: '>= 0.4'} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==, tarball: https://registry.npmmirror.com/object-is/-/object-is-1.1.6.tgz} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==, tarball: https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==, tarball: https://registry.npmmirror.com/object.assign/-/object.assign-4.1.7.tgz} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==, tarball: https://registry.npmmirror.com/object.entries/-/object.entries-1.1.9.tgz} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==, tarball: https://registry.npmmirror.com/object.fromentries/-/object.fromentries-2.0.8.tgz} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==, tarball: https://registry.npmmirror.com/object.values/-/object.values-1.2.1.tgz} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, tarball: https://registry.npmmirror.com/once/-/once-1.4.0.tgz} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==, tarball: https://registry.npmmirror.com/onetime/-/onetime-5.1.2.tgz} + engines: {node: '>=6'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==, tarball: https://registry.npmmirror.com/open/-/open-8.4.2.tgz} + engines: {node: '>=12'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==, tarball: https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==, tarball: https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz} + engines: {node: '>= 0.4'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==, tarball: https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==, tarball: https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz} + engines: {node: '>=10'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==, tarball: https://registry.npmmirror.com/p-locate/-/p-locate-4.1.0.tgz} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==, tarball: https://registry.npmmirror.com/p-locate/-/p-locate-5.0.0.tgz} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==, tarball: https://registry.npmmirror.com/p-try/-/p-try-2.2.0.tgz} + engines: {node: '>=6'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==, tarball: https://registry.npmmirror.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==, tarball: https://registry.npmmirror.com/parent-module/-/parent-module-1.0.1.tgz} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==, tarball: https://registry.npmmirror.com/parse-json/-/parse-json-5.2.0.tgz} + engines: {node: '>=8'} + + parse-node-version@1.0.1: + resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==, tarball: https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz} + engines: {node: '>= 0.10'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==, tarball: https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==, tarball: https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==, tarball: https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==, tarball: https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==, tarball: https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==, tarball: https://registry.npmmirror.com/path-scurry/-/path-scurry-1.11.1.tgz} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@1.9.0: + resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==, tarball: https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, tarball: https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==, tarball: https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==, tarball: https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==, tarball: https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==, tarball: https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz} + engines: {node: '>=12'} + + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==, tarball: https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz} + engines: {node: '>=6'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==, tarball: https://registry.npmmirror.com/pirates/-/pirates-4.0.7.tgz} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==, tarball: https://registry.npmmirror.com/pkg-dir/-/pkg-dir-4.2.0.tgz} + engines: {node: '>=8'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==, tarball: https://registry.npmmirror.com/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz} + engines: {node: '>= 0.4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==, tarball: https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz} + engines: {node: ^10 || ^12 || >=14} + + preact@10.28.2: + resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==, tarball: https://registry.npmmirror.com/preact/-/preact-10.28.2.tgz} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==, tarball: https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz} + engines: {node: '>= 0.8.0'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==, tarball: https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@30.2.0: + resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==, tarball: https://registry.npmmirror.com/pretty-format/-/pretty-format-30.2.0.tgz} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==, tarball: https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz} + + prr@1.0.1: + resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==, tarball: https://registry.npmmirror.com/prr/-/prr-1.0.1.tgz} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==, tarball: https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz} + engines: {node: '>=6'} + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==, tarball: https://registry.npmmirror.com/pure-rand/-/pure-rand-7.0.1.tgz} + + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==, tarball: https://registry.npmmirror.com/qs/-/qs-6.14.1.tgz} + engines: {node: '>=0.6'} + + query-string@9.3.1: + resolution: {integrity: sha512-5fBfMOcDi5SA9qj5jZhWAcTtDfKF5WFdd2uD9nVNlbxVv1baq65aALy6qofpNEGELHvisjjasxQp7BlM9gvMzw==, tarball: https://registry.npmmirror.com/query-string/-/query-string-9.3.1.tgz} + engines: {node: '>=18'} + + react-circular-progressbar@2.2.0: + resolution: {integrity: sha512-cgyqEHOzB0nWMZjKfWN3MfSa1LV3OatcDjPz68lchXQUEiBD5O1WsAtoVK4/DSL0B4USR//cTdok4zCBkq8X5g==, tarball: https://registry.npmmirror.com/react-circular-progressbar/-/react-circular-progressbar-2.2.0.tgz} + peerDependencies: + react: '>=0.14.0' + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==, tarball: https://registry.npmmirror.com/react-dom/-/react-dom-19.2.0.tgz} + peerDependencies: + react: ^19.2.0 + + react-icons@5.5.0: + resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==, tarball: https://registry.npmmirror.com/react-icons/-/react-icons-5.5.0.tgz} + peerDependencies: + react: '*' + + react-imask@7.6.1: + resolution: {integrity: sha512-vLNfzcCz62Yzx/GRGh5tiCph9Gbh2cZu+Tz8OiO5it2eNuuhpA0DWhhSlOtVtSJ80+Bx+vFK5De8eQ9AmbkXzA==, tarball: https://registry.npmmirror.com/react-imask/-/react-imask-7.6.1.tgz} + engines: {npm: '>=4.0.0'} + peerDependencies: + react: '>=0.14.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==, tarball: https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==, tarball: https://registry.npmmirror.com/react-is/-/react-is-17.0.2.tgz} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==, tarball: https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz} + + react-is@19.2.0: + resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==, tarball: https://registry.npmmirror.com/react-is/-/react-is-19.2.0.tgz} + + react-json-view-lite@2.5.0: + resolution: {integrity: sha512-tk7o7QG9oYyELWHL8xiMQ8x4WzjCzbWNyig3uexmkLb54r8jO0yH3WCWx8UZS0c49eSA4QUmG5caiRJ8fAn58g==, tarball: https://registry.npmmirror.com/react-json-view-lite/-/react-json-view-lite-2.5.0.tgz} + engines: {node: '>=18'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==, tarball: https://registry.npmmirror.com/react-redux/-/react-redux-9.2.0.tgz} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.18.0: + resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==, tarball: https://registry.npmmirror.com/react-refresh/-/react-refresh-0.18.0.tgz} + engines: {node: '>=0.10.0'} + + react-router-dom-v5-compat@6.30.0: + resolution: {integrity: sha512-MAVRASbdQ3+ZOTPPjAa7jKcF0F9LkHWKB/iib3hf+jzzIazL4GEpMDDdTswCsqRQNU+zNnT3qD0WiNbzJ6ncPw==, tarball: https://registry.npmmirror.com/react-router-dom-v5-compat/-/react-router-dom-v5-compat-6.30.0.tgz} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + react-router-dom: 4 || 5 + + react-router-dom@5.3.4: + resolution: {integrity: sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==, tarball: https://registry.npmmirror.com/react-router-dom/-/react-router-dom-5.3.4.tgz} + peerDependencies: + react: '>=15' + + react-router@5.3.4: + resolution: {integrity: sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==, tarball: https://registry.npmmirror.com/react-router/-/react-router-5.3.4.tgz} + peerDependencies: + react: '>=15' + + react-router@6.30.0: + resolution: {integrity: sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==, tarball: https://registry.npmmirror.com/react-router/-/react-router-6.30.0.tgz} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==, tarball: https://registry.npmmirror.com/react/-/react-19.2.0.tgz} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==, tarball: https://registry.npmmirror.com/readdirp/-/readdirp-4.1.2.tgz} + engines: {node: '>= 14.18.0'} + + recharts@3.6.0: + resolution: {integrity: sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==, tarball: https://registry.npmmirror.com/recharts/-/recharts-3.6.0.tgz} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==, tarball: https://registry.npmmirror.com/redent/-/redent-3.0.0.tgz} + engines: {node: '>=8'} + + reduce-reducers@1.0.4: + resolution: {integrity: sha512-Mb2WZ2bJF597exiqX7owBzrqJ74DHLK3yOQjCyPAaNifRncE8OD0wFIuoMhXxTnHK07+8zZ2SJEKy/qtiyR7vw==, tarball: https://registry.npmmirror.com/reduce-reducers/-/reduce-reducers-1.0.4.tgz} + + redux-actions@3.0.3: + resolution: {integrity: sha512-ca+ySXmXWrxDqtauRA6lQtjmRO8Z9Ruog3wbb8wDq4RVW9Y677Nl/AXoJPlxJqH0mpY9QGkg03NkAJwTcyN2pQ==, tarball: https://registry.npmmirror.com/redux-actions/-/redux-actions-3.0.3.tgz} + + redux-first-history@5.2.0: + resolution: {integrity: sha512-7kLqtSXGPZIgvEhl3B+3wRvzePvvZggpVqg+jpR2ZVqu2ESGj9DF6hMHpoEP7bGHqddljjKYjnRmtSetYEiG2Q==, tarball: https://registry.npmmirror.com/redux-first-history/-/redux-first-history-5.2.0.tgz} + peerDependencies: + history: ^4.7.2 || ^5.0 + redux: ^3.6.0 || ^4.0.0 || ^5.0.0 + + redux-promise-middleware@6.2.0: + resolution: {integrity: sha512-TEzfMeLX63gju2WqkdFQlQMvUGYzFvJNePIJJsBlbPHs3Txsbc/5Rjhmtha1XdMU6lkeiIlp1Qx7AR3Zo9he9g==, tarball: https://registry.npmmirror.com/redux-promise-middleware/-/redux-promise-middleware-6.2.0.tgz} + peerDependencies: + redux: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==, tarball: https://registry.npmmirror.com/redux-thunk/-/redux-thunk-3.1.0.tgz} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==, tarball: https://registry.npmmirror.com/redux/-/redux-5.0.1.tgz} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==, tarball: https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==, tarball: https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==, tarball: https://registry.npmmirror.com/regenerate/-/regenerate-1.4.2.tgz} + + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==, tarball: https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==, tarball: https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz} + engines: {node: '>= 0.4'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==, tarball: https://registry.npmmirror.com/regexpu-core/-/regexpu-core-6.4.0.tgz} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==, tarball: https://registry.npmmirror.com/regjsgen/-/regjsgen-0.8.0.tgz} + + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==, tarball: https://registry.npmmirror.com/regjsparser/-/regjsparser-0.13.0.tgz} + hasBin: true + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==, tarball: https://registry.npmmirror.com/remove-accents/-/remove-accents-0.5.0.tgz} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, tarball: https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz} + engines: {node: '>=0.10.0'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==, tarball: https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==, tarball: https://registry.npmmirror.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==, tarball: https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==, tarball: https://registry.npmmirror.com/resolve-from/-/resolve-from-5.0.0.tgz} + engines: {node: '>=8'} + + resolve-pathname@3.0.0: + resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==, tarball: https://registry.npmmirror.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==, tarball: https://registry.npmmirror.com/resolve/-/resolve-1.22.11.tgz} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==, tarball: https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.5.tgz} + hasBin: true + + rollup-plugin-visualizer@6.0.5: + resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==, tarball: https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-6.0.5.tgz} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + rolldown: 1.x || ^1.0.0-beta + rollup: 2.x || 3.x || 4.x + peerDependenciesMeta: + rolldown: + optional: true + rollup: + optional: true + + rollup@4.56.0: + resolution: {integrity: sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==, tarball: https://registry.npmmirror.com/rollup/-/rollup-4.56.0.tgz} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==, tarball: https://registry.npmmirror.com/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz} + + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==, tarball: https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==, tarball: https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.3.tgz} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==, tarball: https://registry.npmmirror.com/safe-push-apply/-/safe-push-apply-1.0.0.tgz} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==, tarball: https://registry.npmmirror.com/safe-regex-test/-/safe-regex-test-1.1.0.tgz} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==, tarball: https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz} + + sass-embedded-all-unknown@1.97.3: + resolution: {integrity: sha512-t6N46NlPuXiY3rlmG6/+1nwebOBOaLFOOVqNQOC2cJhghOD4hh2kHNQQTorCsbY9S1Kir2la1/XLBwOJfui0xg==, tarball: https://registry.npmmirror.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.97.3.tgz} + cpu: ['!arm', '!arm64', '!riscv64', '!x64'] + + sass-embedded-android-arm64@1.97.3: + resolution: {integrity: sha512-aiZ6iqiHsUsaDx0EFbbmmA0QgxicSxVVN3lnJJ0f1RStY0DthUkquGT5RJ4TPdaZ6ebeJWkboV4bra+CP766eA==, tarball: https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [android] + + sass-embedded-android-arm@1.97.3: + resolution: {integrity: sha512-cRTtf/KV/q0nzGZoUzVkeIVVFv3L/tS1w4WnlHapphsjTXF/duTxI8JOU1c/9GhRPiMdfeXH7vYNcMmtjwX7jg==, tarball: https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [android] + + sass-embedded-android-riscv64@1.97.3: + resolution: {integrity: sha512-zVEDgl9JJodofGHobaM/q6pNETG69uuBIGQHRo789jloESxxZe82lI3AWJQuPmYCOG5ElfRthqgv89h3gTeLYA==, tarball: https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [android] + + sass-embedded-android-x64@1.97.3: + resolution: {integrity: sha512-3ke0le7ZKepyXn/dKKspYkpBC0zUk/BMciyP5ajQUDy4qJwobd8zXdAq6kOkdiMB+d9UFJOmEkvgFJHl3lqwcw==, tarball: https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [android] + + sass-embedded-darwin-arm64@1.97.3: + resolution: {integrity: sha512-fuqMTqO4gbOmA/kC5b9y9xxNYw6zDEyfOtMgabS7Mz93wimSk2M1quQaTJnL98Mkcsl2j+7shNHxIS/qpcIDDA==, tarball: https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [darwin] + + sass-embedded-darwin-x64@1.97.3: + resolution: {integrity: sha512-b/2RBs/2bZpP8lMkyZ0Px0vkVkT8uBd0YXpOwK7iOwYkAT8SsO4+WdVwErsqC65vI5e1e5p1bb20tuwsoQBMVA==, tarball: https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [darwin] + + sass-embedded-linux-arm64@1.97.3: + resolution: {integrity: sha512-IP1+2otCT3DuV46ooxPaOKV1oL5rLjteRzf8ldZtfIEcwhSgSsHgA71CbjYgLEwMY9h4jeal8Jfv3QnedPvSjg==, tarball: https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: glibc + + sass-embedded-linux-arm@1.97.3: + resolution: {integrity: sha512-2lPQ7HQQg4CKsH18FTsj2hbw5GJa6sBQgDsls+cV7buXlHjqF8iTKhAQViT6nrpLK/e8nFCoaRgSqEC8xMnXuA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: glibc + + sass-embedded-linux-musl-arm64@1.97.3: + resolution: {integrity: sha512-Lij0SdZCsr+mNRSyDZ7XtJpXEITrYsaGbOTz5e6uFLJ9bmzUbV7M8BXz2/cA7bhfpRPT7/lwRKPdV4+aR9Ozcw==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-arm@1.97.3: + resolution: {integrity: sha512-cBTMU68X2opBpoYsSZnI321gnoaiMBEtc+60CKCclN6PCL3W3uXm8g4TLoil1hDD6mqU9YYNlVG6sJ+ZNef6Lg==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm] + os: [linux] + libc: musl + + sass-embedded-linux-musl-riscv64@1.97.3: + resolution: {integrity: sha512-sBeLFIzMGshR4WmHAD4oIM7WJVkSoCIEwutzptFtGlSlwfNiijULp+J5hA2KteGvI6Gji35apR5aWj66wEn/iA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: musl + + sass-embedded-linux-musl-x64@1.97.3: + resolution: {integrity: sha512-/oWJ+OVrDg7ADDQxRLC/4g1+Nsz1g4mkYS2t6XmyMJKFTFK50FVI2t5sOdFH+zmMp+nXHKM036W94y9m4jjEcw==, tarball: https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: musl + + sass-embedded-linux-riscv64@1.97.3: + resolution: {integrity: sha512-l3IfySApLVYdNx0Kjm7Zehte1CDPZVcldma3dZt+TfzvlAEerM6YDgsk5XEj3L8eHBCgHgF4A0MJspHEo2WNfA==, tarball: https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [riscv64] + os: [linux] + libc: glibc + + sass-embedded-linux-x64@1.97.3: + resolution: {integrity: sha512-Kwqwc/jSSlcpRjULAOVbndqEy2GBzo6OBmmuBVINWUaJLJ8Kczz3vIsDUWLfWz/kTEw9FHBSiL0WCtYLVAXSLg==, tarball: https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [linux] + libc: glibc + + sass-embedded-unknown-all@1.97.3: + resolution: {integrity: sha512-/GHajyYJmvb0IABUQHbVHf1nuHPtIDo/ClMZ81IDr59wT5CNcMe7/dMNujXwWugtQVGI5UGmqXWZQCeoGnct8Q==, tarball: https://registry.npmmirror.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.97.3.tgz} + os: ['!android', '!darwin', '!linux', '!win32'] + + sass-embedded-win32-arm64@1.97.3: + resolution: {integrity: sha512-RDGtRS1GVvQfMGAmVXNxYiUOvPzn9oO1zYB/XUM9fudDRnieYTcUytpNTQZLs6Y1KfJxgt5Y+giRceC92fT8Uw==, tarball: https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [arm64] + os: [win32] + + sass-embedded-win32-x64@1.97.3: + resolution: {integrity: sha512-SFRa2lED9UEwV6vIGeBXeBOLKF+rowF3WmNfb/BzhxmdAsKofCXrJ8ePW7OcDVrvNEbTOGwhsReIsF5sH8fVaw==, tarball: https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.97.3.tgz} + engines: {node: '>=14.0.0'} + cpu: [x64] + os: [win32] + + sass-embedded@1.97.3: + resolution: {integrity: sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==, tarball: https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.97.3.tgz} + engines: {node: '>=16.0.0'} + hasBin: true + + sass@1.97.3: + resolution: {integrity: sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==, tarball: https://registry.npmmirror.com/sass/-/sass-1.97.3.tgz} + engines: {node: '>=14.0.0'} + hasBin: true + + sax@1.4.4: + resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==, tarball: https://registry.npmmirror.com/sax/-/sax-1.4.4.tgz} + engines: {node: '>=11.0.0'} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==, tarball: https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==, tarball: https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==, tarball: https://registry.npmmirror.com/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz} + + semver@5.7.2: + resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==, tarball: https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==, tarball: https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==, tarball: https://registry.npmmirror.com/semver/-/semver-7.7.3.tgz} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==, tarball: https://registry.npmmirror.com/set-function-length/-/set-function-length-1.2.2.tgz} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==, tarball: https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.2.tgz} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==, tarball: https://registry.npmmirror.com/set-proto/-/set-proto-1.0.0.tgz} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==, tarball: https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==, tarball: https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==, tarball: https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.0.tgz} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==, tarball: https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==, tarball: https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==, tarball: https://registry.npmmirror.com/side-channel/-/side-channel-1.1.0.tgz} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, tarball: https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==, tarball: https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, tarball: https://registry.npmmirror.com/signal-exit/-/signal-exit-4.1.0.tgz} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==, tarball: https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==, tarball: https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.13.tgz} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==, tarball: https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.21.tgz} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==, tarball: https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz} + engines: {node: '>= 12'} + + split-on-first@3.0.0: + resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, tarball: https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz} + engines: {node: '>=12'} + + split@0.2.10: + resolution: {integrity: sha512-e0pKq+UUH2Xq/sXbYpZBZc3BawsfDZ7dgv+JtRTUPNcvF5CMR4Y9cvJqkMY0MoxWzTHvZuz1beg6pNEKlszPiQ==, tarball: https://registry.npmmirror.com/split/-/split-0.2.10.tgz} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, tarball: https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==, tarball: https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==, tarball: https://registry.npmmirror.com/stack-utils/-/stack-utils-2.0.6.tgz} + engines: {node: '>=10'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==, tarball: https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, tarball: https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==, tarball: https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz} + engines: {node: '>= 0.4'} + + store@2.0.12: + resolution: {integrity: sha512-eO9xlzDpXLiMr9W1nQ3Nfp9EzZieIQc10zPPMP5jsVV7bLOziSFFBP0XoDXACEIFtdI+rIz0NwWVA/QVJ8zJtw==, tarball: https://registry.npmmirror.com/store/-/store-2.0.12.tgz} + + string-convert@0.2.1: + resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==, tarball: https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==, tarball: https://registry.npmmirror.com/string-length/-/string-length-4.0.2.tgz} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==, tarball: https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==, tarball: https://registry.npmmirror.com/string-width/-/string-width-5.1.2.tgz} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==, tarball: https://registry.npmmirror.com/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==, tarball: https://registry.npmmirror.com/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==, tarball: https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==, tarball: https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==, tarball: https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz} + engines: {node: '>= 0.4'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==, tarball: https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz} + engines: {node: '>=12'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==, tarball: https://registry.npmmirror.com/strip-bom/-/strip-bom-4.0.0.tgz} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==, tarball: https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==, tarball: https://registry.npmmirror.com/strip-indent/-/strip-indent-3.0.0.tgz} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==, tarball: https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz} + engines: {node: '>=8'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==, tarball: https://registry.npmmirror.com/strip-literal/-/strip-literal-3.1.0.tgz} + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==, tarball: https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==, tarball: https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==, tarball: https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz} + engines: {node: '>= 0.4'} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==, tarball: https://registry.npmmirror.com/svgo/-/svgo-2.8.0.tgz} + engines: {node: '>=10.13.0'} + hasBin: true + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==, tarball: https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz} + + sync-child-process@1.0.2: + resolution: {integrity: sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA==, tarball: https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz} + engines: {node: '>=16.0.0'} + + sync-message-port@1.1.3: + resolution: {integrity: sha512-GTt8rSKje5FilG+wEdfCkOcLL7LWqpMlr2c3LRuKt/YXxcJ52aGSbGBAdI4L3aaqfrBt6y711El53ItyH1NWzg==, tarball: https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.1.3.tgz} + engines: {node: '>=16.0.0'} + + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==, tarball: https://registry.npmmirror.com/synckit/-/synckit-0.11.12.tgz} + engines: {node: ^14.18.0 || >=16.0.0} + + systemjs@6.15.1: + resolution: {integrity: sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==, tarball: https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz} + + terser@5.44.1: + resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==, tarball: https://registry.npmmirror.com/terser/-/terser-5.44.1.tgz} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, tarball: https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz} + engines: {node: '>=8'} + + throttle-debounce@5.0.2: + resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==, tarball: https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz} + engines: {node: '>=12.22'} + + through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==, tarball: https://registry.npmmirror.com/through/-/through-2.3.8.tgz} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==, tarball: https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==, tarball: https://registry.npmmirror.com/tiny-warning/-/tiny-warning-1.0.3.tgz} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==, tarball: https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==, tarball: https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==, tarball: https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==, tarball: https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==, tarball: https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-2.0.0.tgz} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==, tarball: https://registry.npmmirror.com/tinyspy/-/tinyspy-4.0.4.tgz} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==, tarball: https://registry.npmmirror.com/tldts-core/-/tldts-core-6.1.86.tgz} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==, tarball: https://registry.npmmirror.com/tldts/-/tldts-6.1.86.tgz} + hasBin: true + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==, tarball: https://registry.npmmirror.com/tmpl/-/tmpl-1.0.5.tgz} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==, tarball: https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz} + engines: {node: '>=8.0'} + + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==, tarball: https://registry.npmmirror.com/toggle-selection/-/toggle-selection-1.0.6.tgz} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==, tarball: https://registry.npmmirror.com/tough-cookie/-/tough-cookie-5.1.2.tgz} + engines: {node: '>=16'} + + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==, tarball: https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==, tarball: https://registry.npmmirror.com/tr46/-/tr46-5.1.1.tgz} + engines: {node: '>=18'} + + true-myth@5.4.0: + resolution: {integrity: sha512-MonJqkJf+VOXZ2msbpvAI1uJWoyfCN7nirR8/fNK+IlFDWYNLb9iqeAc2uhIbAQBynHtM02azZgepQDQclOwAw==, tarball: https://registry.npmmirror.com/true-myth/-/true-myth-5.4.0.tgz} + engines: {node: 12.* || 14.* || 16.* || >= 18.*} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==, tarball: https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.4.0.tgz} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz} + + tween-functions@1.2.0: + resolution: {integrity: sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA==, tarball: https://registry.npmmirror.com/tween-functions/-/tween-functions-1.2.0.tgz} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==, tarball: https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==, tarball: https://registry.npmmirror.com/type-detect/-/type-detect-4.0.8.tgz} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==, tarball: https://registry.npmmirror.com/type-fest/-/type-fest-0.21.3.tgz} + engines: {node: '>=10'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==, tarball: https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==, tarball: https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==, tarball: https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==, tarball: https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.7.tgz} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==, tarball: https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz} + engines: {node: '>=14.17'} + hasBin: true + + u-basscss@2.0.1: + resolution: {integrity: sha512-wWgjMPRG7LxevfQpFOjQULP5GsL2hepBeoU0gbACT7nf3qJP7nZ+aXF9hyJkK3T+i2gdIF1YfZrIw+HLFoAYPw==, tarball: https://registry.npmmirror.com/u-basscss/-/u-basscss-2.0.1.tgz} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==, tarball: https://registry.npmmirror.com/unbox-primitive/-/unbox-primitive-1.1.0.tgz} + engines: {node: '>= 0.4'} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==, tarball: https://registry.npmmirror.com/undici-types/-/undici-types-7.16.0.tgz} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==, tarball: https://registry.npmmirror.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==, tarball: https://registry.npmmirror.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==, tarball: https://registry.npmmirror.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==, tarball: https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz} + engines: {node: '>=4'} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==, tarball: https://registry.npmmirror.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==, tarball: https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uplot@1.6.32: + resolution: {integrity: sha512-KIMVnG68zvu5XXUbC4LQEPnhwOxBuLyW1AHtpm6IKTXImkbLgkMy+jabjLgSLMasNuGGzQm/ep3tOkyTxpiQIw==, tarball: https://registry.npmmirror.com/uplot/-/uplot-1.6.32.tgz} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==, tarball: https://registry.npmmirror.com/uri-js/-/uri-js-4.4.1.tgz} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==, tarball: https://registry.npmmirror.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==, tarball: https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz} + hasBin: true + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==, tarball: https://registry.npmmirror.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz} + engines: {node: '>=10.12.0'} + + value-equal@1.0.1: + resolution: {integrity: sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==, tarball: https://registry.npmmirror.com/value-equal/-/value-equal-1.0.1.tgz} + + varint@6.0.0: + resolution: {integrity: sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg==, tarball: https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz} + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==, tarball: https://registry.npmmirror.com/victory-vendor/-/victory-vendor-37.3.6.tgz} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==, tarball: https://registry.npmmirror.com/vite-node/-/vite-node-3.2.4.tgz} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==, tarball: https://registry.npmmirror.com/vite/-/vite-7.3.0.tgz} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==, tarball: https://registry.npmmirror.com/vitest/-/vitest-3.2.4.tgz} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==, tarball: https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz} + engines: {node: '>=18'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==, tarball: https://registry.npmmirror.com/walker/-/walker-1.0.8.tgz} + + web-vitals@5.1.0: + resolution: {integrity: sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg==, tarball: https://registry.npmmirror.com/web-vitals/-/web-vitals-5.1.0.tgz} + + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==, tarball: https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==, tarball: https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==, tarball: https://registry.npmmirror.com/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz} + engines: {node: '>=18'} + + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==, tarball: https://registry.npmmirror.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==, tarball: https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==, tarball: https://registry.npmmirror.com/whatwg-url/-/whatwg-url-14.2.0.tgz} + engines: {node: '>=18'} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==, tarball: https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==, tarball: https://registry.npmmirror.com/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==, tarball: https://registry.npmmirror.com/which-builtin-type/-/which-builtin-type-1.2.1.tgz} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==, tarball: https://registry.npmmirror.com/which-collection/-/which-collection-1.0.2.tgz} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==, tarball: https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.20.tgz} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==, tarball: https://registry.npmmirror.com/which/-/which-2.0.2.tgz} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==, tarball: https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==, tarball: https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==, tarball: https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==, tarball: https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==, tarball: https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==, tarball: https://registry.npmmirror.com/write-file-atomic/-/write-file-atomic-5.0.1.tgz} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==, tarball: https://registry.npmmirror.com/ws/-/ws-8.19.0.tgz} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==, tarball: https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz} + engines: {node: '>=18'} + + xml@1.0.1: + resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==, tarball: https://registry.npmmirror.com/xml/-/xml-1.0.1.tgz} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==, tarball: https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, tarball: https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==, tarball: https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==, tarball: https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==, tarball: https://registry.npmmirror.com/yargs/-/yargs-17.7.2.tgz} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==, tarball: https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz} + engines: {node: '>=10'} + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@ant-design/colors@8.0.1': + dependencies: + '@ant-design/fast-color': 3.0.0 + + '@ant-design/cssinjs-utils@2.0.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@ant-design/cssinjs': 2.0.3(react-dom@19.2.0)(react@19.2.0) + '@babel/runtime': 7.28.6 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@ant-design/cssinjs@2.0.3(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + csstype: 3.2.3 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + stylis: 4.3.6 + + '@ant-design/fast-color@3.0.0': {} + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@6.1.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/icons-svg': 4.4.2 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@ant-design/react-slick@2.0.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + clsx: 2.1.1 + json2mq: 0.2.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + throttle-debounce: 5.0.2 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5)(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5)(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.28.6': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.6': {} + + '@babel/core@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.6': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.6 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.6 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.6 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/parser@7.28.6': + dependencies: + '@babel/types': 7.28.6 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/traverse': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/types': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.28.6) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/core': 7.28.6 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.6) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.6) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-async-generator-functions': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.6) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.6) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-regenerator': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.28.6) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.6) + babel-plugin-polyfill-corejs2: 0.4.15(@babel/core@7.28.6) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.6) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.28.6 + esutils: 2.0.3 + + '@babel/preset-react@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.28.6)': + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + '@babel/runtime-corejs3@7.28.6': + dependencies: + core-js-pure: 3.48.0 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@babel/traverse@7.28.6': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.6 + '@babel/template': 7.28.6 + '@babel/types': 7.28.6 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@bufbuild/protobuf@2.11.0': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5)(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5)(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5)(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emotion/hash@0.8.0': {} + + '@emotion/unitless@0.7.5': {} + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2)': + dependencies: + eslint: 9.39.2 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@30.2.0': + dependencies: + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + chalk: 4.1.2 + jest-message-util: 30.2.0 + jest-util: 30.2.0 + slash: 3.0.0 + + '@jest/core@30.2.0': + dependencies: + '@jest/console': 30.2.0 + '@jest/pattern': 30.0.1 + '@jest/reporters': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.3.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.2.0 + jest-config: 30.2.0(@types/node@24.10.9) + jest-haste-map: 30.2.0 + jest-message-util: 30.2.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.2.0 + jest-resolve-dependencies: 30.2.0 + jest-runner: 30.2.0 + jest-runtime: 30.2.0 + jest-snapshot: 30.2.0 + jest-util: 30.2.0 + jest-validate: 30.2.0 + jest-watcher: 30.2.0 + micromatch: 4.0.8 + pretty-format: 30.2.0 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/diff-sequences@30.0.1': {} + + '@jest/environment-jsdom-abstract@30.2.0(jsdom@26.1.0)': + dependencies: + '@jest/environment': 30.2.0 + '@jest/fake-timers': 30.2.0 + '@jest/types': 30.2.0 + '@types/jsdom': 21.1.7 + '@types/node': 24.10.9 + jest-mock: 30.2.0 + jest-util: 30.2.0 + jsdom: 26.1.0 + + '@jest/environment@30.2.0': + dependencies: + '@jest/fake-timers': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + jest-mock: 30.2.0 + + '@jest/expect-utils@30.2.0': + dependencies: + '@jest/get-type': 30.1.0 + + '@jest/expect@30.2.0': + dependencies: + expect: 30.2.0 + jest-snapshot: 30.2.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@30.2.0': + dependencies: + '@jest/types': 30.2.0 + '@sinonjs/fake-timers': 13.0.5 + '@types/node': 24.10.9 + jest-message-util: 30.2.0 + jest-mock: 30.2.0 + jest-util: 30.2.0 + + '@jest/get-type@30.1.0': {} + + '@jest/globals@30.2.0': + dependencies: + '@jest/environment': 30.2.0 + '@jest/expect': 30.2.0 + '@jest/types': 30.2.0 + jest-mock: 30.2.0 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 24.10.9 + jest-regex-util: 30.0.1 + + '@jest/reporters@30.2.0': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 24.10.9 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit-x: 0.2.2 + glob: 10.5.0 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + jest-message-util: 30.2.0 + jest-util: 30.2.0 + jest-worker: 30.2.0 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.48 + + '@jest/snapshot-utils@30.2.0': + dependencies: + '@jest/types': 30.2.0 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.2.0': + dependencies: + '@jest/console': 30.2.0 + '@jest/types': 30.2.0 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@30.2.0': + dependencies: + '@jest/test-result': 30.2.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.2.0 + slash: 3.0.0 + + '@jest/transform@30.2.0': + dependencies: + '@babel/core': 7.28.6 + '@jest/types': 30.2.0 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 7.0.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.2.0 + jest-regex-util: 30.0.1 + jest-util: 30.2.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@30.2.0': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 24.10.9 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@parcel/watcher-android-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.6': + optional: true + + '@parcel/watcher-darwin-x64@2.5.6': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.6': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.6': + optional: true + + '@parcel/watcher-win32-arm64@2.5.6': + optional: true + + '@parcel/watcher-win32-ia32@2.5.6': + optional: true + + '@parcel/watcher-win32-x64@2.5.6': + optional: true + + '@parcel/watcher@2.5.6': + dependencies: + detect-libc: 2.1.2 + is-glob: 4.0.3 + node-addon-api: 7.1.1 + picomatch: 4.0.3 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.6 + '@parcel/watcher-darwin-arm64': 2.5.6 + '@parcel/watcher-darwin-x64': 2.5.6 + '@parcel/watcher-freebsd-x64': 2.5.6 + '@parcel/watcher-linux-arm-glibc': 2.5.6 + '@parcel/watcher-linux-arm-musl': 2.5.6 + '@parcel/watcher-linux-arm64-glibc': 2.5.6 + '@parcel/watcher-linux-arm64-musl': 2.5.6 + '@parcel/watcher-linux-x64-glibc': 2.5.6 + '@parcel/watcher-linux-x64-musl': 2.5.6 + '@parcel/watcher-win32-arm64': 2.5.6 + '@parcel/watcher-win32-ia32': 2.5.6 + '@parcel/watcher-win32-x64': 2.5.6 + optional: true + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.9': {} + + '@pyroscope/flamegraph@0.21.4(react-dom@19.2.0)(react@19.2.0)(true-myth@5.4.0)': + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + true-myth: 5.4.0 + + '@rc-component/async-validator@5.1.0': + dependencies: + '@babel/runtime': 7.28.6 + + '@rc-component/cascader@1.9.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/select': 1.3.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tree': 1.1.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/checkbox@1.0.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/collapse@1.1.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/color-picker@3.0.3(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@ant-design/fast-color': 3.0.0 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/context@2.0.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/dialog@1.5.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/portal': 2.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/drawer@1.3.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/portal': 2.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/dropdown@1.0.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/form@1.5.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/async-validator': 5.1.0 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/image@1.5.3(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/portal': 2.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/input-number@1.6.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/mini-decimal': 1.1.0 + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/input@1.1.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/mentions@1.6.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/menu': 1.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/textarea': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/menu@1.2.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/overflow': 1.0.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.28.6 + + '@rc-component/motion@1.1.6(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/mutate-observer@2.0.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/notification@1.2.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/overflow@1.0.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/pagination@1.2.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/picker@1.9.0(dayjs@1.11.19)(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + dayjs: 1.11.19 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/portal@2.2.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/progress@1.0.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/qrcode@1.1.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/rate@1.0.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/resize-observer@1.1.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/segmented@1.2.3(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/select@1.3.6(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/overflow': 1.0.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/slider@1.0.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/steps@1.2.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/switch@1.0.3(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/table@1.9.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/context': 2.0.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/tabs@1.7.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/dropdown': 1.0.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/menu': 1.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/textarea@1.1.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/input': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/tooltip@1.4.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/tour@2.2.1(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/portal': 2.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/tree-select@1.4.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/select': 1.3.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tree': 1.1.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/tree@1.1.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/virtual-list': 1.0.2(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/trigger@3.9.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/portal': 2.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/upload@1.1.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@rc-component/util@1.7.0(react-dom@19.2.0)(react@19.2.0)': + dependencies: + is-mobile: 5.0.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-is: 18.3.1 + + '@rc-component/virtual-list@1.0.2(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0)(react@19.2.0)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.3 + react: 19.2.0 + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.0)(redux@5.0.1) + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + + '@remix-run/router@1.23.0': {} + + '@rolldown/pluginutils@1.0.0-beta.47': {} + + '@rolldown/pluginutils@1.0.0-beta.53': {} + + '@rollup/rollup-android-arm-eabi@4.56.0': + optional: true + + '@rollup/rollup-android-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.56.0': + optional: true + + '@rollup/rollup-darwin-x64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.56.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.56.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.56.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.56.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.56.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.56.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.56.0': + optional: true + + '@sentry-internal/browser-utils@10.32.0': + dependencies: + '@sentry/core': 10.32.0 + + '@sentry-internal/feedback@10.32.0': + dependencies: + '@sentry/core': 10.32.0 + + '@sentry-internal/replay-canvas@10.32.0': + dependencies: + '@sentry-internal/replay': 10.32.0 + '@sentry/core': 10.32.0 + + '@sentry-internal/replay@10.32.0': + dependencies: + '@sentry-internal/browser-utils': 10.32.0 + '@sentry/core': 10.32.0 + + '@sentry/browser@10.32.0': + dependencies: + '@sentry-internal/browser-utils': 10.32.0 + '@sentry-internal/feedback': 10.32.0 + '@sentry-internal/replay': 10.32.0 + '@sentry-internal/replay-canvas': 10.32.0 + '@sentry/core': 10.32.0 + + '@sentry/core@10.32.0': {} + + '@sinclair/typebox@0.34.48': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@tanstack/react-virtual@3.13.12(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@tanstack/virtual-core': 3.13.12 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@tanstack/virtual-core@3.13.12': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/dom@8.20.1': + dependencies: + '@babel/code-frame': 7.28.6 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/preact@3.2.4(preact@10.28.2)': + dependencies: + '@testing-library/dom': 8.20.1 + preact: 10.28.2 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.1)(@types/react@19.2.7)(react-dom@19.2.0)(react@19.2.0)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + '@types/react': 19.2.7 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@trysound/sax@0.2.0': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.6 + '@babel/types': 7.28.6 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.6 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/deep-eql@4.0.2': {} + + '@types/deep-freeze@0.1.5': {} + + '@types/estree@1.0.8': {} + + '@types/history@4.7.11': {} + + '@types/history@5.0.0': + dependencies: + history: 4.10.1 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@30.0.0': + dependencies: + expect: 30.2.0 + pretty-format: 30.2.0 + + '@types/jsdom@21.1.7': + dependencies: + '@types/node': 24.10.9 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/json-schema@7.0.15': {} + + '@types/lodash.debounce@4.0.9': + dependencies: + '@types/lodash': 4.17.21 + + '@types/lodash.orderby@4.6.9': + dependencies: + '@types/lodash': 4.17.21 + + '@types/lodash.throttle@4.1.9': + dependencies: + '@types/lodash': 4.17.21 + + '@types/lodash@4.17.21': {} + + '@types/node@24.10.9': + dependencies: + undici-types: 7.16.0 + + '@types/object-hash@3.0.6': {} + + '@types/qs@6.14.0': {} + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react-input-mask@3.0.6': + dependencies: + '@types/react': 19.2.7 + + '@types/react-router-dom@5.3.3': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.7 + '@types/react-router': 5.1.20 + + '@types/react-router@5.1.20': + dependencies: + '@types/history': 4.7.11 + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@types/redux-actions@2.2.1': {} + + '@types/stack-utils@2.0.3': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@8.53.1(@typescript-eslint/parser@8.53.1)(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/type-utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.53.1 + eslint: 9.39.2 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.53.1 + debug: 4.4.3 + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.53.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) + '@typescript-eslint/types': 8.53.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.53.1': + dependencies: + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/visitor-keys': 8.53.1 + + '@typescript-eslint/tsconfig-utils@8.53.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.53.1(eslint@9.39.2)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.53.1': {} + + '@typescript-eslint/typescript-estree@8.53.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.53.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.53.1(typescript@5.9.3) + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/visitor-keys': 8.53.1 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.3 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.53.1(eslint@9.39.2)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@typescript-eslint/scope-manager': 8.53.1 + '@typescript-eslint/types': 8.53.1 + '@typescript-eslint/typescript-estree': 8.53.1(typescript@5.9.3) + eslint: 9.39.2 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.53.1': + dependencies: + '@typescript-eslint/types': 8.53.1 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@vitejs/plugin-legacy@7.2.1(terser@5.44.1)(vite@7.3.0)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-modules-systemjs': 7.28.5(@babel/core@7.28.6) + '@babel/preset-env': 7.28.5(@babel/core@7.28.6) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.6) + babel-plugin-polyfill-regenerator: 0.6.6(@babel/core@7.28.6) + browserslist: 4.28.1 + browserslist-to-esbuild: 2.1.1(browserslist@4.28.1) + core-js: 3.48.0 + magic-string: 0.30.21 + regenerator-runtime: 0.14.1 + systemjs: 6.15.1 + terser: 5.44.1 + vite: 7.3.0(less@4.5.1)(terser@5.44.1) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@5.1.1(vite@7.3.0)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.0(less@4.5.1)(terser@5.44.1) + transitivePeerDependencies: + - supports-color + + '@vitejs/plugin-react@5.1.2(vite@7.3.0)': + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6) + '@rolldown/pluginutils': 1.0.0-beta.53 + '@types/babel__core': 7.20.5 + react-refresh: 0.18.0 + vite: 7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.0)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + vite: 7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + antd@6.1.1(react-dom@19.2.0)(react@19.2.0): + dependencies: + '@ant-design/colors': 8.0.1 + '@ant-design/cssinjs': 2.0.3(react-dom@19.2.0)(react@19.2.0) + '@ant-design/cssinjs-utils': 2.0.2(react-dom@19.2.0)(react@19.2.0) + '@ant-design/fast-color': 3.0.0 + '@ant-design/icons': 6.1.0(react-dom@19.2.0)(react@19.2.0) + '@ant-design/react-slick': 2.0.0(react-dom@19.2.0)(react@19.2.0) + '@babel/runtime': 7.28.6 + '@rc-component/cascader': 1.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/checkbox': 1.0.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/collapse': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/color-picker': 3.0.3(react-dom@19.2.0)(react@19.2.0) + '@rc-component/dialog': 1.5.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/drawer': 1.3.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/dropdown': 1.0.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/form': 1.5.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/image': 1.5.3(react-dom@19.2.0)(react@19.2.0) + '@rc-component/input': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/input-number': 1.6.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/mentions': 1.6.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/menu': 1.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/motion': 1.1.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/notification': 1.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/pagination': 1.2.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/picker': 1.9.0(dayjs@1.11.19)(react-dom@19.2.0)(react@19.2.0) + '@rc-component/progress': 1.0.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/qrcode': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/rate': 1.0.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/resize-observer': 1.1.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/segmented': 1.2.3(react-dom@19.2.0)(react@19.2.0) + '@rc-component/select': 1.3.6(react-dom@19.2.0)(react@19.2.0) + '@rc-component/slider': 1.0.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/steps': 1.2.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/switch': 1.0.3(react-dom@19.2.0)(react@19.2.0) + '@rc-component/table': 1.9.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tabs': 1.7.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/textarea': 1.1.2(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tooltip': 1.4.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tour': 2.2.1(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tree': 1.1.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/tree-select': 1.4.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/trigger': 3.9.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/upload': 1.1.0(react-dom@19.2.0)(react@19.2.0) + '@rc-component/util': 1.7.0(react-dom@19.2.0)(react@19.2.0) + clsx: 2.1.1 + dayjs: 1.11.19 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.1.3: + dependencies: + deep-equal: 2.2.3 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-jest@30.2.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@jest/transform': 30.2.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.1 + babel-preset-jest: 30.2.0(@babel/core@7.28.6) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-inline-react-svg@2.0.2(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.28.6 + lodash.isplainobject: 4.0.6 + resolve: 2.0.0-next.5 + svgo: 2.8.0 + + babel-plugin-istanbul@7.0.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@30.2.0: + dependencies: + '@types/babel__core': 7.20.5 + + babel-plugin-polyfill-corejs2@0.4.15(@babel/core@7.28.6): + dependencies: + '@babel/compat-data': 7.28.6 + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.6(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/helper-define-polyfill-provider': 0.6.6(@babel/core@7.28.6) + transitivePeerDependencies: + - supports-color + + babel-plugin-react-remove-properties@0.3.1: {} + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.6) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.6) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.6) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.6) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.6) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.6) + + babel-preset-jest@30.2.0(@babel/core@7.28.6): + dependencies: + '@babel/core': 7.28.6 + babel-plugin-jest-hoist: 30.2.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + + balanced-match@1.0.2: {} + + baseline-browser-mapping@2.9.18: {} + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist-to-esbuild@2.1.1(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + meow: 13.2.0 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.18 + caniuse-lite: 1.0.30001766 + electron-to-chromium: 1.5.278 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001766: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chance@1.1.13: {} + + char-regex@1.0.2: {} + + check-error@2.1.3: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + optional: true + + ci-info@4.3.1: {} + + cjs-module-lexer@2.2.0: {} + + classnames@2.5.1: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clsx@2.1.1: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorjs.io@0.5.2: {} + + combokeys@3.0.1: {} + + commander@2.20.3: {} + + commander@7.2.0: {} + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + copy-anything@2.0.6: + dependencies: + is-what: 3.14.1 + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + core-js-compat@3.48.0: + dependencies: + browserslist: 4.28.1 + + core-js-pure@3.48.0: {} + + core-js@3.48.0: {} + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + + css.escape@1.5.1: {} + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + dayjs@1.11.19: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + decode-uri-component@0.4.1: {} + + dedent@1.7.1: {} + + deep-eql@5.0.2: {} + + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + es-get-iterator: 1.1.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + deep-freeze@0.0.1: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + detect-libc@2.1.2: + optional: true + + detect-newline@3.1.0: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + drange@2.0.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.278: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + entities@2.2.0: {} + + entities@6.0.1: {} + + errno@0.1.8: + dependencies: + prr: 1.0.1 + optional: true + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.1.1 + isarray: 2.0.5 + stop-iteration-iterator: 1.1.0 + + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es-toolkit@1.44.0: {} + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react@7.37.5(eslint@9.39.2): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 9.39.2 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-plugin-unused-imports@4.3.0(@typescript-eslint/eslint-plugin@8.53.1)(eslint@9.39.2): + dependencies: + '@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1)(eslint@9.39.2)(typescript@5.9.3) + eslint: 9.39.2 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect-type@1.3.0: {} + + expect@30.2.0: + dependencies: + '@jest/expect-utils': 30.2.0 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.2.0 + jest-message-util: 30.2.0 + jest-mock: 30.2.0 + jest-util: 30.2.0 + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + dependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@5.1.0: {} + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@14.0.0: {} + + globals@16.5.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + history@4.10.1: + dependencies: + '@babel/runtime': 7.28.6 + loose-envify: 1.4.0 + resolve-pathname: 3.0.0 + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + value-equal: 1.0.1 + + history@5.3.0: + dependencies: + '@babel/runtime': 7.28.6 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + html-escaper@2.0.2: {} + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + image-size@0.5.5: + optional: true + + imask@7.6.1: + dependencies: + '@babel/runtime-corejs3': 7.28.6 + + immer@10.2.0: {} + + immer@11.1.3: {} + + immutable@5.1.4: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + internmap@2.0.3: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-mobile@5.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-what@3.14.1: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@0.0.1: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isomorphic-fetch@3.0.0: + dependencies: + node-fetch: 2.7.0 + whatwg-fetch: 3.6.20 + transitivePeerDependencies: + - encoding + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.6 + '@babel/parser': 7.28.6 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.3 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@30.2.0: + dependencies: + execa: 5.1.1 + jest-util: 30.2.0 + p-limit: 3.1.0 + + jest-circus@30.2.0: + dependencies: + '@jest/environment': 30.2.0 + '@jest/expect': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.1 + is-generator-fn: 2.1.0 + jest-each: 30.2.0 + jest-matcher-utils: 30.2.0 + jest-message-util: 30.2.0 + jest-runtime: 30.2.0 + jest-snapshot: 30.2.0 + jest-util: 30.2.0 + p-limit: 3.1.0 + pretty-format: 30.2.0 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.2.0: + dependencies: + '@jest/core': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.2.0(@types/node@24.10.9) + jest-util: 30.2.0 + jest-validate: 30.2.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.2.0(@types/node@24.10.9): + dependencies: + '@babel/core': 7.28.6 + '@jest/get-type': 30.1.0 + '@jest/pattern': 30.0.1 + '@jest/test-sequencer': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + babel-jest: 30.2.0(@babel/core@7.28.6) + chalk: 4.1.2 + ci-info: 4.3.1 + deepmerge: 4.3.1 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-circus: 30.2.0 + jest-docblock: 30.2.0 + jest-environment-node: 30.2.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.2.0 + jest-runner: 30.2.0 + jest-util: 30.2.0 + jest-validate: 30.2.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 30.2.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@30.2.0: + dependencies: + '@jest/diff-sequences': 30.0.1 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.2.0 + + jest-docblock@30.2.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.2.0: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.2.0 + chalk: 4.1.2 + jest-util: 30.2.0 + pretty-format: 30.2.0 + + jest-environment-jsdom@30.2.0: + dependencies: + '@jest/environment': 30.2.0 + '@jest/environment-jsdom-abstract': 30.2.0(jsdom@26.1.0) + '@types/jsdom': 21.1.7 + '@types/node': 24.10.9 + jsdom: 26.1.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jest-environment-node@30.2.0: + dependencies: + '@jest/environment': 30.2.0 + '@jest/fake-timers': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + jest-mock: 30.2.0 + jest-util: 30.2.0 + jest-validate: 30.2.0 + + jest-haste-map@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.2.0 + jest-worker: 30.2.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-junit@16.0.0: + dependencies: + mkdirp: 1.0.4 + strip-ansi: 6.0.1 + uuid: 8.3.2 + xml: 1.0.1 + + jest-leak-detector@30.2.0: + dependencies: + '@jest/get-type': 30.1.0 + pretty-format: 30.2.0 + + jest-matcher-utils@30.2.0: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.2.0 + pretty-format: 30.2.0 + + jest-message-util@30.2.0: + dependencies: + '@babel/code-frame': 7.28.6 + '@jest/types': 30.2.0 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 30.2.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + jest-util: 30.2.0 + + jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): + dependencies: + jest-resolve: 30.2.0 + + jest-regex-util@30.0.1: {} + + jest-resolve-dependencies@30.2.0: + dependencies: + jest-regex-util: 30.0.1 + jest-snapshot: 30.2.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.2.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.2.0 + jest-pnp-resolver: 1.2.3(jest-resolve@30.2.0) + jest-util: 30.2.0 + jest-validate: 30.2.0 + slash: 3.0.0 + unrs-resolver: 1.11.1 + + jest-runner@30.2.0: + dependencies: + '@jest/console': 30.2.0 + '@jest/environment': 30.2.0 + '@jest/test-result': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.2.0 + jest-environment-node: 30.2.0 + jest-haste-map: 30.2.0 + jest-leak-detector: 30.2.0 + jest-message-util: 30.2.0 + jest-resolve: 30.2.0 + jest-runtime: 30.2.0 + jest-util: 30.2.0 + jest-watcher: 30.2.0 + jest-worker: 30.2.0 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.2.0: + dependencies: + '@jest/environment': 30.2.0 + '@jest/fake-timers': 30.2.0 + '@jest/globals': 30.2.0 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + chalk: 4.1.2 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.2.0 + jest-message-util: 30.2.0 + jest-mock: 30.2.0 + jest-regex-util: 30.0.1 + jest-resolve: 30.2.0 + jest-snapshot: 30.2.0 + jest-util: 30.2.0 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.2.0: + dependencies: + '@babel/core': 7.28.6 + '@babel/generator': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.6) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.6) + '@babel/types': 7.28.6 + '@jest/expect-utils': 30.2.0 + '@jest/get-type': 30.1.0 + '@jest/snapshot-utils': 30.2.0 + '@jest/transform': 30.2.0 + '@jest/types': 30.2.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.6) + chalk: 4.1.2 + expect: 30.2.0 + graceful-fs: 4.2.11 + jest-diff: 30.2.0 + jest-matcher-utils: 30.2.0 + jest-message-util: 30.2.0 + jest-util: 30.2.0 + pretty-format: 30.2.0 + semver: 7.7.3 + synckit: 0.11.12 + transitivePeerDependencies: + - supports-color + + jest-util@30.2.0: + dependencies: + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + chalk: 4.1.2 + ci-info: 4.3.1 + graceful-fs: 4.2.11 + picomatch: 4.0.3 + + jest-validate@30.2.0: + dependencies: + '@jest/get-type': 30.1.0 + '@jest/types': 30.2.0 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.2.0 + + jest-watcher@30.2.0: + dependencies: + '@jest/test-result': 30.2.0 + '@jest/types': 30.2.0 + '@types/node': 24.10.9 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.2.0 + string-length: 4.0.2 + + jest-worker@30.2.0: + dependencies: + '@types/node': 24.10.9 + '@ungap/structured-clone': 1.3.0 + jest-util: 30.2.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.2.0: + dependencies: + '@jest/core': 30.2.0 + '@jest/types': 30.2.0 + import-local: 3.2.0 + jest-cli: 30.2.0 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + just-curry-it@5.3.0: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + less@4.5.1: + dependencies: + copy-anything: 2.0.6 + parse-node-version: 1.0.1 + tslib: 2.8.1 + optionalDependencies: + errno: 0.1.8 + graceful-fs: 4.2.11 + image-size: 0.5.5 + make-dir: 2.1.0 + mime: 1.6.0 + needle: 3.3.1 + source-map: 0.6.1 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lines-and-columns@1.2.4: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.isplainobject@4.0.6: {} + + lodash.merge@4.6.2: {} + + lodash.orderby@4.6.0: {} + + lodash.throttle@4.1.1: {} + + lodash@4.17.21: {} + + logfmt@1.4.0: + dependencies: + split: 0.2.10 + through: 2.3.8 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-memoize@1.1.0: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + make-dir@2.1.0: + dependencies: + pify: 4.0.1 + semver: 5.7.2 + optional: true + + make-dir@4.0.0: + dependencies: + semver: 7.7.3 + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marked@16.4.2: {} + + match-sorter@8.2.0: + dependencies: + '@babel/runtime': 7.28.6 + remove-accents: 0.5.0 + + math-intrinsics@1.1.0: {} + + mdn-data@2.0.14: {} + + memoize-one@6.0.0: {} + + meow@13.2.0: {} + + merge-stream@2.0.0: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime@1.6.0: + optional: true + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minipass@7.1.2: {} + + mkdirp@1.0.4: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + needle@3.3.1: + dependencies: + iconv-lite: 0.6.3 + sax: 1.4.4 + optional: true + + node-addon-api@7.1.1: + optional: true + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-int64@0.4.0: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nwsapi@2.2.23: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-try@2.2.0: {} + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.28.6 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse-node-version@1.0.1: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-to-regexp@1.9.0: + dependencies: + isarray: 0.0.1 + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@4.0.1: + optional: true + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + possible-typed-array-names@1.1.0: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.28.2: {} + + prelude-ls@1.2.1: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@30.2.0: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + prr@1.0.1: + optional: true + + punycode@2.3.1: {} + + pure-rand@7.0.1: {} + + qs@6.14.1: + dependencies: + side-channel: 1.1.0 + + query-string@9.3.1: + dependencies: + decode-uri-component: 0.4.1 + filter-obj: 5.1.0 + split-on-first: 3.0.0 + + react-circular-progressbar@2.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-icons@5.5.0(react@19.2.0): + dependencies: + react: 19.2.0 + + react-imask@7.6.1(react@19.2.0): + dependencies: + imask: 7.6.1 + prop-types: 15.8.1 + react: 19.2.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-is@19.2.0: {} + + react-json-view-lite@2.5.0(react@19.2.0): + dependencies: + react: 19.2.0 + + react-redux@9.2.0(@types/react@19.2.7)(react@19.2.0)(redux@5.0.1): + dependencies: + '@types/react': 19.2.7 + '@types/use-sync-external-store': 0.0.6 + react: 19.2.0 + redux: 5.0.1 + use-sync-external-store: 1.6.0(react@19.2.0) + + react-refresh@0.18.0: {} + + react-router-dom-v5-compat@6.30.0(react-dom@19.2.0)(react-router-dom@5.3.4)(react@19.2.0): + dependencies: + '@remix-run/router': 1.23.0 + history: 5.3.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-router: 6.30.0(react@19.2.0) + react-router-dom: 5.3.4(react@19.2.0) + + react-router-dom@5.3.4(react@19.2.0): + dependencies: + '@babel/runtime': 7.28.6 + history: 4.10.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.0 + react-router: 5.3.4(react@19.2.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + react-router@5.3.4(react@19.2.0): + dependencies: + '@babel/runtime': 7.28.6 + history: 4.10.1 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + path-to-regexp: 1.9.0 + prop-types: 15.8.1 + react: 19.2.0 + react-is: 16.13.1 + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + react-router@6.30.0(react@19.2.0): + dependencies: + '@remix-run/router': 1.23.0 + react: 19.2.0 + + react@19.2.0: {} + + readdirp@4.1.2: + optional: true + + recharts@3.6.0(@types/react@19.2.7)(react-dom@19.2.0)(react-is@19.2.0)(react@19.2.0)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0)(react@19.2.0) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.44.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + react-is: 19.2.0 + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.0)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.0) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reduce-reducers@1.0.4: {} + + redux-actions@3.0.3: + dependencies: + just-curry-it: 5.3.0 + reduce-reducers: 1.0.4 + + redux-first-history@5.2.0(history@4.10.1)(redux@5.0.1): + dependencies: + history: 4.10.1 + redux: 5.0.1 + + redux-promise-middleware@6.2.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.14.1: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + remove-accents@0.5.0: {} + + require-directory@2.1.1: {} + + reselect@5.1.1: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pathname@3.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup-plugin-visualizer@6.0.5: + dependencies: + open: 8.4.2 + picomatch: 4.0.3 + source-map: 0.7.6 + yargs: 17.7.2 + + rollup@4.56.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.56.0 + '@rollup/rollup-android-arm64': 4.56.0 + '@rollup/rollup-darwin-arm64': 4.56.0 + '@rollup/rollup-darwin-x64': 4.56.0 + '@rollup/rollup-freebsd-arm64': 4.56.0 + '@rollup/rollup-freebsd-x64': 4.56.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.56.0 + '@rollup/rollup-linux-arm-musleabihf': 4.56.0 + '@rollup/rollup-linux-arm64-gnu': 4.56.0 + '@rollup/rollup-linux-arm64-musl': 4.56.0 + '@rollup/rollup-linux-loong64-gnu': 4.56.0 + '@rollup/rollup-linux-loong64-musl': 4.56.0 + '@rollup/rollup-linux-ppc64-gnu': 4.56.0 + '@rollup/rollup-linux-ppc64-musl': 4.56.0 + '@rollup/rollup-linux-riscv64-gnu': 4.56.0 + '@rollup/rollup-linux-riscv64-musl': 4.56.0 + '@rollup/rollup-linux-s390x-gnu': 4.56.0 + '@rollup/rollup-linux-x64-gnu': 4.56.0 + '@rollup/rollup-linux-x64-musl': 4.56.0 + '@rollup/rollup-openbsd-x64': 4.56.0 + '@rollup/rollup-openharmony-arm64': 4.56.0 + '@rollup/rollup-win32-arm64-msvc': 4.56.0 + '@rollup/rollup-win32-ia32-msvc': 4.56.0 + '@rollup/rollup-win32-x64-gnu': 4.56.0 + '@rollup/rollup-win32-x64-msvc': 4.56.0 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + sass-embedded-all-unknown@1.97.3: + dependencies: + sass: 1.97.3 + optional: true + + sass-embedded-android-arm64@1.97.3: + optional: true + + sass-embedded-android-arm@1.97.3: + optional: true + + sass-embedded-android-riscv64@1.97.3: + optional: true + + sass-embedded-android-x64@1.97.3: + optional: true + + sass-embedded-darwin-arm64@1.97.3: + optional: true + + sass-embedded-darwin-x64@1.97.3: + optional: true + + sass-embedded-linux-arm64@1.97.3: + optional: true + + sass-embedded-linux-arm@1.97.3: + optional: true + + sass-embedded-linux-musl-arm64@1.97.3: + optional: true + + sass-embedded-linux-musl-arm@1.97.3: + optional: true + + sass-embedded-linux-musl-riscv64@1.97.3: + optional: true + + sass-embedded-linux-musl-x64@1.97.3: + optional: true + + sass-embedded-linux-riscv64@1.97.3: + optional: true + + sass-embedded-linux-x64@1.97.3: + optional: true + + sass-embedded-unknown-all@1.97.3: + dependencies: + sass: 1.97.3 + optional: true + + sass-embedded-win32-arm64@1.97.3: + optional: true + + sass-embedded-win32-x64@1.97.3: + optional: true + + sass-embedded@1.97.3: + dependencies: + '@bufbuild/protobuf': 2.11.0 + colorjs.io: 0.5.2 + immutable: 5.1.4 + rxjs: 7.8.2 + supports-color: 8.1.1 + sync-child-process: 1.0.2 + varint: 6.0.0 + optionalDependencies: + sass-embedded-all-unknown: 1.97.3 + sass-embedded-android-arm: 1.97.3 + sass-embedded-android-arm64: 1.97.3 + sass-embedded-android-riscv64: 1.97.3 + sass-embedded-android-x64: 1.97.3 + sass-embedded-darwin-arm64: 1.97.3 + sass-embedded-darwin-x64: 1.97.3 + sass-embedded-linux-arm: 1.97.3 + sass-embedded-linux-arm64: 1.97.3 + sass-embedded-linux-musl-arm: 1.97.3 + sass-embedded-linux-musl-arm64: 1.97.3 + sass-embedded-linux-musl-riscv64: 1.97.3 + sass-embedded-linux-musl-x64: 1.97.3 + sass-embedded-linux-riscv64: 1.97.3 + sass-embedded-linux-x64: 1.97.3 + sass-embedded-unknown-all: 1.97.3 + sass-embedded-win32-arm64: 1.97.3 + sass-embedded-win32-x64: 1.97.3 + + sass@1.97.3: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.6 + optional: true + + sax@1.4.4: + optional: true + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + semver@5.7.2: + optional: true + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + split-on-first@3.0.0: {} + + split@0.2.10: + dependencies: + through: 2.3.8 + + sprintf-js@1.0.3: {} + + stable@0.1.8: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + store@2.0.12: {} + + string-convert@0.2.1: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + stylis@4.3.6: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + symbol-tree@3.2.4: {} + + sync-child-process@1.0.2: + dependencies: + sync-message-port: 1.1.3 + + sync-message-port@1.1.3: {} + + synckit@0.11.12: + dependencies: + '@pkgr/core': 0.2.9 + + systemjs@6.15.1: {} + + terser@5.44.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.15.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.2 + + throttle-debounce@5.0.2: {} + + through@2.3.8: {} + + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@0.0.3: {} + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + true-myth@5.4.0: {} + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + tween-functions@1.2.0: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.9.3: {} + + u-basscss@2.0.1: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.16.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uplot@1.6.32: {} + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.0): + dependencies: + react: 19.2.0 + + uuid@8.3.2: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + value-equal@1.0.1: {} + + varint@6.0.0: {} + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite-node@3.2.4(@types/node@24.10.9)(sass-embedded@1.97.3): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3): + dependencies: + '@types/node': 24.10.9 + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.56.0 + sass-embedded: 1.97.3 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vite@7.3.0(less@4.5.1)(terser@5.44.1): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + less: 4.5.1 + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.56.0 + terser: 5.44.1 + tinyglobby: 0.2.15 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.4(@types/node@24.10.9)(jsdom@26.1.0)(sass-embedded@1.97.3): + dependencies: + '@types/chai': 5.2.3 + '@types/node': 24.10.9 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.0) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + jsdom: 26.1.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.0(@types/node@24.10.9)(sass-embedded@1.97.3) + vite-node: 3.2.4(@types/node@24.10.9)(sass-embedded@1.97.3) + why-is-node-running: 2.3.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + web-vitals@5.1.0: {} + + webidl-conversions@3.0.1: {} + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-fetch@3.6.20: {} + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@8.19.0: {} + + xml-name-validator@5.0.0: {} + + xml@1.0.1: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} diff --git a/app/vmui/pnpm-workspace.yaml b/app/vmui/pnpm-workspace.yaml new file mode 100644 index 0000000000..a1ab84226a --- /dev/null +++ b/app/vmui/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - "packages/vmui" + - "packages/jaeger-ui-lite" \ No newline at end of file diff --git a/app/vmui/tsconfig.json b/app/vmui/tsconfig.json new file mode 100644 index 0000000000..ca108b9365 --- /dev/null +++ b/app/vmui/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es2016", + "lib": ["es2017", "dom", "dom.iterable", "webworker"], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "declaration": true, + "emitDeclarationOnly": true, + "jsx": "react-jsx" + }, + "include": [], + "files": [], + "references": [ + { + "path": "packages/jaeger-ui/tsconfig.lint.json" + }, + ] +}