@parca/profile 0.16.419 → 0.16.421
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +13 -3
- package/dist/SimpleMatchers/Select.d.ts +29 -0
- package/dist/SimpleMatchers/Select.d.ts.map +1 -0
- package/dist/SimpleMatchers/Select.js +115 -0
- package/dist/SimpleMatchers/index.d.ts +13 -0
- package/dist/SimpleMatchers/index.d.ts.map +1 -0
- package/dist/SimpleMatchers/index.js +206 -0
- package/dist/styles.css +1 -1
- package/package.json +3 -3
- package/src/ProfileSelector/index.tsx +53 -11
- package/src/SimpleMatchers/Select.tsx +253 -0
- package/src/SimpleMatchers/index.tsx +345 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.16.421](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.420...@parca/profile@0.16.421) (2024-08-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.16.420](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.417...@parca/profile@0.16.420) (2024-08-06)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.419](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.418...@parca/profile@0.16.419) (2024-07-30)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAGlD,OAAO,EAAQ,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAa9E,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAyB,gBAAgB,EAAC,MAAM,IAAI,CAAC;AAS5D,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,oBAAoB;IAC5B,WAAW,EAAE,kBAAkB,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,WAAY,kBAAkB,KAAG,mBAkB5D,CAAC;AAEF,QAAA,MAAM,eAAe,6IAUlB,oBAAoB,KAAG,GAAG,CAAC,OAoX7B,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -12,8 +12,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
15
|
+
import { Switch } from '@headlessui/react';
|
|
15
16
|
import Select from 'react-select';
|
|
16
|
-
import { Button, ButtonGroup, DateTimeRange, DateTimeRangePicker, IconButton, useGrpcMetadata, useParcaContext, } from '@parca/components';
|
|
17
|
+
import { Button, ButtonGroup, DateTimeRange, DateTimeRangePicker, IconButton, useGrpcMetadata, useParcaContext, useURLState, } from '@parca/components';
|
|
17
18
|
import { CloseIcon } from '@parca/icons';
|
|
18
19
|
import { Query } from '@parca/parser';
|
|
19
20
|
import { MergedProfileSelection } from '..';
|
|
@@ -21,6 +22,7 @@ import MatchersInput, { useLabelNames } from '../MatchersInput/index';
|
|
|
21
22
|
import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
|
|
22
23
|
import ProfileMetricsGraph, { ProfileMetricsEmptyState } from '../ProfileMetricsGraph';
|
|
23
24
|
import ProfileTypeSelector from '../ProfileTypeSelector/index';
|
|
25
|
+
import SimpleMatchers from '../SimpleMatchers';
|
|
24
26
|
import { useDefaultSumBy, useSumBySelection } from '../useSumBy';
|
|
25
27
|
import { useAutoQuerySelector } from './useAutoQuerySelector';
|
|
26
28
|
export const useProfileTypes = (client) => {
|
|
@@ -45,8 +47,10 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
45
47
|
const { heightStyle } = useMetricsGraphDimensions(comparing);
|
|
46
48
|
const { viewComponent } = useParcaContext();
|
|
47
49
|
const sumByRef = useRef(null);
|
|
50
|
+
const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
|
|
48
51
|
const [timeRangeSelection, setTimeRangeSelection] = useState(DateTimeRange.fromRangeKey(querySelection.timeSelection, querySelection.from, querySelection.to));
|
|
49
52
|
const [queryExpressionString, setQueryExpressionString] = useState(querySelection.expression);
|
|
53
|
+
const [advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser] = useState(queryBrowserMode === 'advanced');
|
|
50
54
|
const profileType = useMemo(() => {
|
|
51
55
|
return Query.parse(queryExpressionString).profileType();
|
|
52
56
|
}, [queryExpressionString]);
|
|
@@ -167,8 +171,14 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
167
171
|
const searchDisabled = queryExpressionString === undefined ||
|
|
168
172
|
queryExpressionString === '' ||
|
|
169
173
|
queryExpressionString === '{}';
|
|
170
|
-
|
|
171
|
-
|
|
174
|
+
const queryBrowserRef = useRef(null);
|
|
175
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex gap-2", children: [_jsxs("div", { className: "flex w-full flex-wrap content-start items-center gap-2", children: [_jsxs("div", { className: "pb-6", children: [_jsx("label", { className: "text-xs", children: "Profile type" }), _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: error, disabled: viewComponent?.disableProfileTypesDropdown })] }), _jsxs("div", { className: "w-full flex-1 flex flex-col pb-6 gap-1", ref: queryBrowserRef, children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("label", { className: "text-xs", children: "Query" }), _jsxs(Switch, { checked: advancedModeForQueryBrowser, onChange: () => {
|
|
176
|
+
setAdvancedModeForQueryBrowser(!advancedModeForQueryBrowser);
|
|
177
|
+
setQueryBrowserMode(advancedModeForQueryBrowser ? 'simple' : 'advanced');
|
|
178
|
+
}, className: `${advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-900'}
|
|
179
|
+
relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`, children: [_jsx("span", { className: "sr-only", children: "Use setting" }), _jsx("span", { "aria-hidden": "true", className: `${advancedModeForQueryBrowser ? 'translate-x-6' : 'translate-x-0'}
|
|
180
|
+
pointer-events-none inline-block h-[16px] w-[16px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out` })] }), _jsx("label", { className: "text-xs", children: "Advanced Mode" })] }), (query.matchers.length > 0 || query.inputMatcherString.length > 0) &&
|
|
181
|
+
viewComponent !== undefined && _jsx("div", { children: viewComponent?.createViewComponent })] }), advancedModeForQueryBrowser ? (_jsx(MatchersInput, { queryClient: queryClient, setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName })) : (_jsx(SimpleMatchers, { queryClient: queryClient, setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName, queryBrowserRef: queryBrowserRef }))] }), _jsxs("div", { className: "pb-6", children: [_jsx("div", { className: "mb-0.5 mt-1.5 flex items-center justify-between", children: _jsx("label", { className: "text-xs", children: "Sum by" }) }), _jsx(Select, { defaultValue: [], isMulti: true, name: "colors", options: labels.map(label => ({ label, value: label })), className: "parca-select-container text-sm w-full max-w-80", classNamePrefix: "parca-select", value: (sumBySelection ?? []).map(sumBy => ({ label: sumBy, value: sumBy })), onChange: selectedOptions => {
|
|
172
182
|
setUserSumBySelection(selectedOptions.map(option => option.value));
|
|
173
183
|
}, placeholder: "Labels...", styles: {
|
|
174
184
|
indicatorSeparator: () => ({ display: 'none' }),
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface SelectElement {
|
|
3
|
+
active: JSX.Element;
|
|
4
|
+
expanded: JSX.Element;
|
|
5
|
+
}
|
|
6
|
+
export interface SelectItem {
|
|
7
|
+
key: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
element: SelectElement;
|
|
10
|
+
}
|
|
11
|
+
interface CustomSelectProps {
|
|
12
|
+
items: SelectItem[];
|
|
13
|
+
selectedKey: string | undefined;
|
|
14
|
+
onSelection: (value: string) => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
className?: string;
|
|
18
|
+
loading?: boolean;
|
|
19
|
+
primary?: boolean;
|
|
20
|
+
disabled?: boolean;
|
|
21
|
+
icon?: JSX.Element;
|
|
22
|
+
id?: string;
|
|
23
|
+
optionsClassname?: string;
|
|
24
|
+
searchable?: boolean;
|
|
25
|
+
onButtonClick?: () => void;
|
|
26
|
+
}
|
|
27
|
+
declare const CustomSelect: React.FC<CustomSelectProps>;
|
|
28
|
+
export default CustomSelect;
|
|
29
|
+
//# sourceMappingURL=Select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/Select.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAoC,MAAM,OAAO,CAAC;AAOzD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC;IACpB,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,CA0M7C,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { useEffect, useRef, useState } from 'react';
|
|
15
|
+
import { Icon } from '@iconify/react';
|
|
16
|
+
import cx from 'classnames';
|
|
17
|
+
import { useParcaContext } from '@parca/components';
|
|
18
|
+
const CustomSelect = ({ items, selectedKey, onSelection, placeholder, width, className = '', loading, primary = false, disabled = false, icon, id, optionsClassname = '', searchable = false, onButtonClick, }) => {
|
|
19
|
+
const { loader } = useParcaContext();
|
|
20
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
21
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
22
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
23
|
+
const containerRef = useRef(null);
|
|
24
|
+
const optionsRef = useRef(null);
|
|
25
|
+
const searchInputRef = useRef(null);
|
|
26
|
+
const optionRefs = useRef([]);
|
|
27
|
+
const filteredItems = searchable
|
|
28
|
+
? items.filter(item => item.element.active.props.children
|
|
29
|
+
.toString()
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.includes(searchTerm.toLowerCase()))
|
|
32
|
+
: items;
|
|
33
|
+
const selection = items.find(v => v.key === selectedKey);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
const handleClickOutside = (event) => {
|
|
36
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target)) {
|
|
37
|
+
setIsOpen(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
41
|
+
return () => {
|
|
42
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
43
|
+
};
|
|
44
|
+
}, []);
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (isOpen && searchable) {
|
|
47
|
+
searchInputRef.current?.focus();
|
|
48
|
+
}
|
|
49
|
+
}, [isOpen, searchable]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (focusedIndex !== -1 &&
|
|
52
|
+
optionsRef.current !== null &&
|
|
53
|
+
optionRefs.current[focusedIndex] !== null) {
|
|
54
|
+
const optionElement = optionRefs.current[focusedIndex];
|
|
55
|
+
const optionsContainer = optionsRef.current;
|
|
56
|
+
if (optionElement !== null && optionsContainer !== null) {
|
|
57
|
+
const optionRect = optionElement.getBoundingClientRect();
|
|
58
|
+
const containerRect = optionsContainer.getBoundingClientRect();
|
|
59
|
+
if (optionRect.bottom > containerRect.bottom) {
|
|
60
|
+
optionsContainer.scrollTop += optionRect.bottom - containerRect.bottom;
|
|
61
|
+
}
|
|
62
|
+
else if (optionRect.top < containerRect.top) {
|
|
63
|
+
optionsContainer.scrollTop -= containerRect.top - optionRect.top;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, [focusedIndex]);
|
|
68
|
+
const handleKeyDown = (e) => {
|
|
69
|
+
if (e.key === 'Enter') {
|
|
70
|
+
if (!isOpen) {
|
|
71
|
+
setIsOpen(true);
|
|
72
|
+
}
|
|
73
|
+
else if (focusedIndex !== -1) {
|
|
74
|
+
onSelection(filteredItems[focusedIndex].key);
|
|
75
|
+
setIsOpen(false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else if (e.key === 'Escape') {
|
|
79
|
+
setIsOpen(false);
|
|
80
|
+
}
|
|
81
|
+
else if (e.key === 'Tab') {
|
|
82
|
+
if (isOpen) {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
if (e.shiftKey) {
|
|
85
|
+
// Shift+Tab: Move focus to the previous item
|
|
86
|
+
setFocusedIndex(prevIndex => (prevIndex <= 0 ? filteredItems.length - 1 : prevIndex - 1));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
// Tab: Move focus to the next item
|
|
90
|
+
setFocusedIndex(prevIndex => (prevIndex + 1) % filteredItems.length);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
else if (e.key === 'ArrowDown') {
|
|
95
|
+
e.preventDefault();
|
|
96
|
+
setFocusedIndex(prevIndex => (prevIndex + 1) % filteredItems.length);
|
|
97
|
+
}
|
|
98
|
+
else if (e.key === 'ArrowUp') {
|
|
99
|
+
e.preventDefault();
|
|
100
|
+
setFocusedIndex(prevIndex => (prevIndex - 1 + filteredItems.length) % filteredItems.length);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const styles = 'relative border rounded-md shadow-sm px-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 items-center focus:ring-indigo-500 focus:border-indigo-500 text-sm flex gap-2 flex items-center justify-between';
|
|
104
|
+
const defaultStyles = 'bg-white dark:bg-gray-900 dark:border-gray-600';
|
|
105
|
+
const primaryStyles = 'text-gray-100 dark:gray-900 bg-indigo-600 border-indigo-500 font-medium py-2 px-4';
|
|
106
|
+
return (_jsxs("div", { ref: containerRef, className: "relative", onKeyDown: handleKeyDown, onClick: onButtonClick, children: [_jsxs("div", { id: id, onClick: () => !disabled && setIsOpen(!isOpen), className: cx(styles, width !== undefined ? `w-${width}` : 'w-full', disabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : '', primary ? primaryStyles : defaultStyles, { [className]: className.length > 0 }), tabIndex: 0, role: "button", "aria-haspopup": "listbox", "aria-expanded": isOpen, children: [_jsx("div", { className: cx(icon != null ? '' : 'block overflow-x-hidden text-ellipsis whitespace-nowrap'), children: selection?.element.active ?? placeholder }), _jsx("div", { className: cx(icon != null ? '' : 'pointer-events-none text-gray-400'), children: icon ?? _jsx(Icon, { icon: "heroicons:chevron-up-down-20-solid", "aria-hidden": "true" }) })] }), isOpen && (_jsxs("div", { ref: optionsRef, className: cx('absolute z-50 mt-1 pt-0 max-h-[50vh] w-max overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm', { [optionsClassname]: optionsClassname.length > 0 }), role: "listbox", children: [searchable && (_jsx("div", { className: "sticky h-[45px] z-10 top-[-5px] border-b border-gray-200", children: _jsx("input", { ref: searchInputRef, type: "text", className: "w-full px-6 h-full text-sm border-none rounded-none ring-0 outline-none bg-gray-50 dark:bg-gray-800 dark:text-white", placeholder: "Search...", value: searchTerm, onChange: e => setSearchTerm(e.target.value) }) })), loading === true ? (_jsx("div", { className: "w-[270px]", children: loader })) : (filteredItems.map((item, index) => (_jsxs("div", { ref: el => (optionRefs.current[index] = el), className: cx('relative cursor-default select-none py-2 pl-3 pr-9', index === focusedIndex && 'bg-indigo-600 text-white', item.key === selectedKey && 'bg-indigo-100 dark:bg-indigo-700', item.disabled !== null &&
|
|
107
|
+
item.disabled === true &&
|
|
108
|
+
'opacity-50 cursor-not-allowed', 'focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 hover:bg-indigo-600 hover:text-white'), role: "option", "aria-selected": item.key === selectedKey, tabIndex: -1, onClick: () => {
|
|
109
|
+
if (!(item.disabled ?? false)) {
|
|
110
|
+
onSelection(item.key);
|
|
111
|
+
setIsOpen(false);
|
|
112
|
+
}
|
|
113
|
+
}, children: [item.element.expanded, item.key === selectedKey && (_jsx("span", { className: "absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600", children: _jsx(Icon, { icon: "heroicons:check-20-solid", "aria-hidden": "true" }) }))] }, item.key))))] }))] }));
|
|
114
|
+
};
|
|
115
|
+
export default CustomSelect;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { QueryServiceClient } from '@parca/client';
|
|
2
|
+
import { Query } from '@parca/parser';
|
|
3
|
+
interface Props {
|
|
4
|
+
queryClient: QueryServiceClient;
|
|
5
|
+
setMatchersString: (arg: string) => void;
|
|
6
|
+
runQuery: () => void;
|
|
7
|
+
currentQuery: Query;
|
|
8
|
+
profileType: string;
|
|
9
|
+
queryBrowserRef: React.RefObject<HTMLDivElement>;
|
|
10
|
+
}
|
|
11
|
+
declare const SimpleMatchers: ({ queryClient, setMatchersString, currentQuery, profileType, queryBrowserRef, }: Props) => JSX.Element;
|
|
12
|
+
export default SimpleMatchers;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/index.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAEjD,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAMpC,UAAU,KAAK;IACb,WAAW,EAAE,kBAAkB,CAAC;IAChC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,YAAY,EAAE,KAAK,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;CAClD;AAiED,QAAA,MAAM,cAAc,oFAOjB,KAAK,KAAG,GAAG,CAAC,OA6Od,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
15
|
+
import { Icon } from '@iconify/react';
|
|
16
|
+
import cx from 'classnames';
|
|
17
|
+
import { useGrpcMetadata } from '@parca/components';
|
|
18
|
+
import { sanitizeLabelValue } from '@parca/utilities';
|
|
19
|
+
import { useLabelNames } from '../MatchersInput';
|
|
20
|
+
import Select from './Select';
|
|
21
|
+
const transformLabelsForSelect = (labelNames) => {
|
|
22
|
+
return labelNames.map(labelName => ({
|
|
23
|
+
key: labelName,
|
|
24
|
+
element: { active: _jsx(_Fragment, { children: labelName }), expanded: _jsx(_Fragment, { children: labelName }) },
|
|
25
|
+
}));
|
|
26
|
+
};
|
|
27
|
+
const operatorOptions = [
|
|
28
|
+
{
|
|
29
|
+
key: '=',
|
|
30
|
+
element: {
|
|
31
|
+
active: _jsx(_Fragment, { children: "=" }),
|
|
32
|
+
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "=" }) })),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
key: '!=',
|
|
37
|
+
element: {
|
|
38
|
+
active: _jsx(_Fragment, { children: '!=' }),
|
|
39
|
+
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: '!=' }) })),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
// TODO: Implement these operators to work properly.
|
|
43
|
+
// {
|
|
44
|
+
// key: '=~',
|
|
45
|
+
// element: {
|
|
46
|
+
// active: <>{'=~'}</>,
|
|
47
|
+
// expanded: (
|
|
48
|
+
// <>
|
|
49
|
+
// <span>{'=~'}</span>
|
|
50
|
+
// </>
|
|
51
|
+
// ),
|
|
52
|
+
// },
|
|
53
|
+
// },
|
|
54
|
+
// {
|
|
55
|
+
// key: '!~',
|
|
56
|
+
// element: {
|
|
57
|
+
// active: <>{'!~'}</>,
|
|
58
|
+
// expanded: (
|
|
59
|
+
// <>
|
|
60
|
+
// <span>{'!~'}</span>
|
|
61
|
+
// </>
|
|
62
|
+
// ),
|
|
63
|
+
// },
|
|
64
|
+
// },
|
|
65
|
+
];
|
|
66
|
+
const SimpleMatchers = ({ queryClient, setMatchersString,
|
|
67
|
+
// runQuery,
|
|
68
|
+
currentQuery, profileType, queryBrowserRef, }) => {
|
|
69
|
+
const [queryRows, setQueryRows] = useState([
|
|
70
|
+
{ labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false },
|
|
71
|
+
]);
|
|
72
|
+
const metadata = useGrpcMetadata();
|
|
73
|
+
const { loading: labelNamesLoading, result } = useLabelNames(queryClient, profileType);
|
|
74
|
+
const { response: labelNamesResponse, error: labelNamesError } = result;
|
|
75
|
+
const [showAll, setShowAll] = useState(false);
|
|
76
|
+
const visibleRows = showAll ? queryRows : queryRows.slice(0, 3);
|
|
77
|
+
const hiddenRowsCount = queryRows.length - 3;
|
|
78
|
+
const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString()}px]`;
|
|
79
|
+
const currentMatchers = currentQuery.matchersString();
|
|
80
|
+
const fetchLabelValues = useCallback(async (labelName) => {
|
|
81
|
+
try {
|
|
82
|
+
const response = await queryClient.values({ labelName, match: [], profileType }, { meta: metadata }).response;
|
|
83
|
+
const sanitizedValues = sanitizeLabelValue(response.labelValues);
|
|
84
|
+
return sanitizedValues;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error('Error fetching label values:', error);
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
}, [queryClient, metadata, profileType]);
|
|
91
|
+
const updateMatchersString = useCallback((rows) => {
|
|
92
|
+
const matcherString = rows
|
|
93
|
+
.filter(row => row.labelName.length > 0 && row.labelValue)
|
|
94
|
+
.map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
|
|
95
|
+
.join(',');
|
|
96
|
+
setMatchersString(matcherString);
|
|
97
|
+
}, [setMatchersString]);
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (currentMatchers === '') {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const fetchAndSetQueryRows = async () => {
|
|
103
|
+
const newRows = await Promise.all(currentMatchers.split(',').map(async (matcher) => {
|
|
104
|
+
let [labelName, operator, labelValue] = matcher.split(/(=|!=|=~|!~)/);
|
|
105
|
+
labelName = labelName.trim();
|
|
106
|
+
if (labelName === '')
|
|
107
|
+
return null;
|
|
108
|
+
const labelValues = await fetchLabelValues(labelName);
|
|
109
|
+
const sanitizedLabelValue = labelValue.startsWith('"') && labelValue.endsWith('"')
|
|
110
|
+
? labelValue.slice(1, -1)
|
|
111
|
+
: labelValue;
|
|
112
|
+
return {
|
|
113
|
+
labelName,
|
|
114
|
+
operator,
|
|
115
|
+
labelValue: sanitizedLabelValue,
|
|
116
|
+
labelValues,
|
|
117
|
+
};
|
|
118
|
+
}));
|
|
119
|
+
const filteredRows = newRows.filter((row) => row !== null);
|
|
120
|
+
setQueryRows(filteredRows);
|
|
121
|
+
updateMatchersString(filteredRows);
|
|
122
|
+
};
|
|
123
|
+
void fetchAndSetQueryRows();
|
|
124
|
+
}, [currentMatchers, fetchLabelValues, updateMatchersString]);
|
|
125
|
+
const labelNames = useMemo(() => {
|
|
126
|
+
return (labelNamesError === undefined || labelNamesError == null) &&
|
|
127
|
+
labelNamesResponse !== undefined &&
|
|
128
|
+
labelNamesResponse != null
|
|
129
|
+
? labelNamesResponse.labelNames.filter(e => e !== '__name__')
|
|
130
|
+
: [];
|
|
131
|
+
}, [labelNamesError, labelNamesResponse]);
|
|
132
|
+
const labelNameOptions = useMemo(() => {
|
|
133
|
+
return transformLabelsForSelect(labelNames);
|
|
134
|
+
}, [labelNames]);
|
|
135
|
+
const updateRow = useCallback(async (index, field, value) => {
|
|
136
|
+
const updatedRows = [...queryRows];
|
|
137
|
+
const prevLabelName = updatedRows[index].labelName;
|
|
138
|
+
updatedRows[index] = { ...updatedRows[index], [field]: value };
|
|
139
|
+
if (field === 'labelName' && value !== prevLabelName) {
|
|
140
|
+
updatedRows[index].labelValues = [];
|
|
141
|
+
updatedRows[index].labelValue = '';
|
|
142
|
+
updatedRows[index].isLoading = true;
|
|
143
|
+
setQueryRows([...updatedRows]);
|
|
144
|
+
const labelValues = await fetchLabelValues(value);
|
|
145
|
+
updatedRows[index].labelValues = labelValues;
|
|
146
|
+
updatedRows[index].isLoading = false;
|
|
147
|
+
}
|
|
148
|
+
setQueryRows([...updatedRows]);
|
|
149
|
+
updateMatchersString(updatedRows);
|
|
150
|
+
}, [queryRows, fetchLabelValues, updateMatchersString]);
|
|
151
|
+
const handleUpdateRow = useCallback((index, field, value) => {
|
|
152
|
+
void updateRow(index, field, value);
|
|
153
|
+
}, [updateRow]);
|
|
154
|
+
const addNewRow = useCallback(() => {
|
|
155
|
+
const newRows = [
|
|
156
|
+
...queryRows,
|
|
157
|
+
{ labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false },
|
|
158
|
+
];
|
|
159
|
+
setQueryRows(newRows);
|
|
160
|
+
updateMatchersString(newRows);
|
|
161
|
+
}, [queryRows, updateMatchersString]);
|
|
162
|
+
const removeRow = useCallback((index) => {
|
|
163
|
+
if (queryRows.length === 1) {
|
|
164
|
+
// Reset the single row instead of removing it
|
|
165
|
+
const resetRow = {
|
|
166
|
+
labelName: '',
|
|
167
|
+
operator: '=',
|
|
168
|
+
labelValue: '',
|
|
169
|
+
labelValues: [],
|
|
170
|
+
isLoading: false,
|
|
171
|
+
};
|
|
172
|
+
setQueryRows([resetRow]);
|
|
173
|
+
updateMatchersString([resetRow]);
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
const updatedRows = queryRows.filter((_, i) => i !== index);
|
|
177
|
+
setQueryRows(updatedRows);
|
|
178
|
+
updateMatchersString(updatedRows);
|
|
179
|
+
}
|
|
180
|
+
}, [queryRows, updateMatchersString]);
|
|
181
|
+
const handleLabelValueClick = useCallback((index) => {
|
|
182
|
+
return async () => {
|
|
183
|
+
const updatedRows = [...queryRows];
|
|
184
|
+
if (updatedRows[index].labelValues.length === 0 && updatedRows[index].labelName !== '') {
|
|
185
|
+
updatedRows[index].isLoading = true;
|
|
186
|
+
setQueryRows([...updatedRows]);
|
|
187
|
+
try {
|
|
188
|
+
const labelValues = await fetchLabelValues(updatedRows[index].labelName);
|
|
189
|
+
updatedRows[index].labelValues = labelValues;
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error('Error fetching label values:', error);
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
updatedRows[index].isLoading = false;
|
|
196
|
+
setQueryRows([...updatedRows]);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
console.log(`Label values already present or empty label name`);
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
}, [queryRows, fetchLabelValues]);
|
|
204
|
+
return (_jsxs("div", { className: `flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`, children: [visibleRows.map((row, index) => (_jsxs("div", { className: "flex items-center", children: [_jsx(Select, { items: labelNameOptions, onSelection: value => handleUpdateRow(index, 'labelName', value), placeholder: "Select label name", selectedKey: row.labelName, className: "rounded-tr-none rounded-br-none ring-0 focus:ring-0 outline-none", loading: labelNamesLoading, searchable: true }), _jsx(Select, { items: operatorOptions, onSelection: value => handleUpdateRow(index, 'operator', value), selectedKey: row.operator, className: "rounded-none ring-0 focus:ring-0 outline-none" }), _jsx(Select, { items: transformLabelsForSelect(row.labelValues), onSelection: value => handleUpdateRow(index, 'labelValue', value), placeholder: "Select label value", selectedKey: row.labelValue, className: "rounded-none ring-0 focus:ring-0 outline-none max-w-48", optionsClassname: "max-w-[300px]", searchable: true, disabled: row.labelName === '', loading: row.isLoading, onButtonClick: () => handleLabelValueClick(index) }), _jsx("button", { onClick: () => removeRow(index), className: cx('p-2 border-gray-200 border rounded rounded-tl-none rounded-bl-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900'), children: _jsx(Icon, { icon: "carbon:close", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }, index))), queryRows.length > 3 && (_jsx("button", { onClick: () => setShowAll(!showAll), className: "mr-2 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900", children: showAll ? 'Show less' : `Show ${hiddenRowsCount} more` })), _jsx("button", { onClick: addNewRow, className: "p-2 border-gray-200 dark:bg-gray-900 dark:border-gray-600 border rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500", children: _jsx(Icon, { icon: "material-symbols:add", className: "h-5 w-5 text-gray-400", "aria-hidden": "true" }) })] }));
|
|
205
|
+
};
|
|
206
|
+
export default SimpleMatchers;
|
package/dist/styles.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.top-0{top:0}.top-\[-1px\]{top:-1px}.left-\[18px\]{left:18px}.bottom-0{bottom:0}.z-50{z-index:50}.z-10{z-index:10}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.mb-2{margin-bottom:.5rem}.mb-0\.5{margin-bottom:.125rem}.mt-1\.5{margin-top:.375rem}.mb-0{margin-bottom:0}.mb-4{margin-bottom:1rem}.ml-3{margin-left:.75rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-\[38px\]{height:38px}.h-auto{height:auto}.h-full{height:100%}.h-1{height:.25rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-\[700px\]{height:700px}.h-\[80vh\]{height:80vh}.h-5{height:1.25rem}.max-h-\[400px\]{max-height:400px}.max-h-\[300px\]{max-height:300px}.min-h-52{min-height:13rem}.min-h-\[38px\]{min-height:38px}.min-h-48{min-height:12rem}.min-h-\[78px\]{min-height:78px}.min-h-96{min-height:24rem}.min-h-\[700px\]{min-height:700px}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-max{width:-moz-max-content;width:max-content}.w-40{width:10rem}.w-80{width:20rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-4{width:1rem}.w-\[18px\]{width:18px}.w-8{width:2rem}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-\[19\.25\%\]{width:19.25%}.w-11\/12{width:91.666667%}.w-1\/12{width:8.333333%}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-\[300px\]{max-width:300px}.max-w-\[400px\]{max-width:400px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.origin-top-left{transform-origin:top left}.origin-bottom-left{transform-origin:bottom left}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.-rotate-90{--tw-rotate:-90deg}.-rotate-90,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-auto{cursor:auto}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-clip{overflow:clip}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-x{border-left-width:1px;border-right-width:1px}.border-y{border-bottom-width:1px;border-top-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.stroke-\[3\]{stroke-width:3}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1\.5{padding:.375rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.\!py-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-8{padding-left:2rem;padding-right:2rem}.pr-0{padding-right:0}.pt-2{padding-top:.5rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-6{padding-bottom:1.5rem}.pb-\[10px\]{padding-bottom:10px}.pl-1{padding-left:.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-5{padding-left:1.25rem}.pr-\[1\.7rem\]{padding-right:1.7rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[stroke-dasharray\:6\2c 4\]{stroke-dasharray:6,4}.\[stroke-linecap\:round\]{stroke-linecap:round}.\[stroke-linejoin\:round\]{stroke-linejoin:round}.hover\:whitespace-normal:hover{white-space:normal}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.hover\:bg-indigo-200:hover{--tw-bg-opacity:1;background-color:rgb(199 210 254/var(--tw-bg-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border{border-width:1px}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}[class~=theme-dark] .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}[class~=theme-dark] .dark\:hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}[class~=theme-dark] .hover\:dark\:text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex-row{flex-direction:row}.md\:items-end{align-items:flex-end}.md\:justify-end{justify-content:flex-end}}@media (min-width:1024px){.lg\:flex{display:flex}}
|
|
1
|
+
/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.top-0{top:0}.top-\[-5px\]{top:-5px}.top-\[-1px\]{top:-1px}.left-\[18px\]{left:18px}.bottom-0{bottom:0}.z-50{z-index:50}.z-10{z-index:10}.z-20{z-index:20}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.my-4{margin-bottom:1rem;margin-top:1rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.mb-2{margin-bottom:.5rem}.mb-0\.5{margin-bottom:.125rem}.mt-1\.5{margin-top:.375rem}.mb-0{margin-bottom:0}.mb-4{margin-bottom:1rem}.mr-2{margin-right:.5rem}.ml-3{margin-left:.75rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mb-1{margin-bottom:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-\[38px\]{height:38px}.h-auto{height:auto}.h-full{height:100%}.h-1{height:.25rem}.h-\[20px\]{height:20px}.h-\[16px\]{height:16px}.h-\[45px\]{height:45px}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-\[700px\]{height:700px}.h-\[80vh\]{height:80vh}.max-h-\[400px\]{max-height:400px}.max-h-\[50vh\]{max-height:50vh}.max-h-\[300px\]{max-height:300px}.min-h-52{min-height:13rem}.min-h-\[38px\]{min-height:38px}.min-h-48{min-height:12rem}.min-h-\[78px\]{min-height:78px}.min-h-96{min-height:24rem}.min-h-\[700px\]{min-height:700px}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-max{width:-moz-max-content;width:max-content}.w-40{width:10rem}.w-\[44px\]{width:44px}.w-\[16px\]{width:16px}.w-\[270px\]{width:270px}.w-5{width:1.25rem}.w-3{width:.75rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-4{width:1rem}.w-\[18px\]{width:18px}.w-8{width:2rem}.w-44{width:11rem}.w-\[460px\]{width:460px}.w-\[19\.25\%\]{width:19.25%}.w-11\/12{width:91.666667%}.w-1\/12{width:8.333333%}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-80{max-width:20rem}.max-w-\[300px\]{max-width:300px}.max-w-48{max-width:12rem}.max-w-\[400px\]{max-width:400px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.origin-top-left{transform-origin:top left}.origin-bottom-left{transform-origin:bottom left}.translate-x-6{--tw-translate-x:1.5rem}.translate-x-0,.translate-x-6{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x:0px}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.-rotate-90{--tw-rotate:-90deg}.-rotate-90,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-auto{cursor:auto}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-clip{overflow:clip}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-full{border-radius:9999px}.rounded-none{border-radius:0}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.rounded-tr-none{border-top-right-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-bl-none{border-bottom-left-radius:0}.border{border-width:1px}.border-2{border-width:2px}.border-x{border-left-width:1px;border-right-width:1px}.border-y{border-top-width:1px}.border-b,.border-y{border-bottom-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-t{border-top-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-none{border-style:none}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity:1;border-color:rgb(156 163 175/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-gray-400{--tw-bg-opacity:1;background-color:rgb(156 163 175/var(--tw-bg-opacity))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}.bg-indigo-50{--tw-bg-opacity:1;background-color:rgb(238 242 255/var(--tw-bg-opacity))}.bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.bg-opacity-90{--tw-bg-opacity:0.9}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.stroke-\[3\]{stroke-width:3}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1\.5{padding:.375rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.\!py-2{padding-bottom:.5rem!important;padding-top:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.px-8{padding-left:2rem;padding-right:2rem}.pr-0{padding-right:0}.pt-2{padding-top:.5rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pb-6{padding-bottom:1.5rem}.pb-\[10px\]{padding-bottom:10px}.pt-0{padding-top:0}.pr-4{padding-right:1rem}.pl-1{padding-left:.25rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-5{padding-left:1.25rem}.pr-\[1\.7rem\]{padding-right:1.7rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-top{vertical-align:top}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.uppercase{text-transform:uppercase}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.leading-5{line-height:1.25rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-50{opacity:.5}.opacity-90{opacity:.9}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.outline-none{outline:2px solid transparent;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-0,.ring-1{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-0{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.\[stroke-dasharray\:6\2c 4\]{stroke-dasharray:6,4}.\[stroke-linecap\:round\]{stroke-linecap:round}.\[stroke-linejoin\:round\]{stroke-linejoin:round}.hover\:whitespace-normal:hover{white-space:normal}.hover\:bg-indigo-600:hover{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.hover\:bg-indigo-200:hover{--tw-bg-opacity:1;background-color:rgb(199 210 254/var(--tw-bg-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-1:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-0:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus-visible\:ring-white\/75:focus-visible{--tw-ring-color:hsla(0,0%,100%,.75)}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border{border-width:1px}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-600{--tw-bg-opacity:1;background-color:rgb(75 85 99/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-opacity-80{--tw-bg-opacity:0.8}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-200{--tw-text-opacity:1;color:rgb(229 231 235/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-indigo-500{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}[class~=theme-dark] .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}[class~=theme-dark] .dark\:hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity))}[class~=theme-dark] .hover\:dark\:text-gray-100:hover{--tw-text-opacity:1;color:rgb(243 244 246/var(--tw-text-opacity))}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:768px){.md\:block{display:block}.md\:flex-row{flex-direction:row}.md\:items-end{align-items:flex-end}.md\:justify-end{justify-content:flex-end}}@media (min-width:1024px){.lg\:flex{display:flex}}
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.421",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@headlessui/react": "^1.7.19",
|
|
7
7
|
"@iconify/react": "^4.0.0",
|
|
8
8
|
"@parca/client": "0.16.123",
|
|
9
|
-
"@parca/components": "0.16.
|
|
9
|
+
"@parca/components": "0.16.298",
|
|
10
10
|
"@parca/dynamicsize": "0.16.65",
|
|
11
11
|
"@parca/hooks": "0.0.69",
|
|
12
12
|
"@parca/icons": "0.16.70",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"access": "public",
|
|
74
74
|
"registry": "https://registry.npmjs.org/"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "607879f8a01750627b626e64b39347bfb7ee1d66"
|
|
77
77
|
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import React, {useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
|
+
import {Switch} from '@headlessui/react';
|
|
16
17
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
18
|
import Select, {type SelectInstance} from 'react-select';
|
|
18
19
|
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
IconButton,
|
|
26
27
|
useGrpcMetadata,
|
|
27
28
|
useParcaContext,
|
|
29
|
+
useURLState,
|
|
28
30
|
} from '@parca/components';
|
|
29
31
|
import {CloseIcon} from '@parca/icons';
|
|
30
32
|
import {Query} from '@parca/parser';
|
|
@@ -35,6 +37,7 @@ import MatchersInput, {useLabelNames} from '../MatchersInput/index';
|
|
|
35
37
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
36
38
|
import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
|
|
37
39
|
import ProfileTypeSelector from '../ProfileTypeSelector/index';
|
|
40
|
+
import SimpleMatchers from '../SimpleMatchers';
|
|
38
41
|
import {useDefaultSumBy, useSumBySelection} from '../useSumBy';
|
|
39
42
|
import {useAutoQuerySelector} from './useAutoQuerySelector';
|
|
40
43
|
|
|
@@ -106,6 +109,7 @@ const ProfileSelector = ({
|
|
|
106
109
|
const {heightStyle} = useMetricsGraphDimensions(comparing);
|
|
107
110
|
const {viewComponent} = useParcaContext();
|
|
108
111
|
const sumByRef = useRef(null);
|
|
112
|
+
const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
|
|
109
113
|
|
|
110
114
|
const [timeRangeSelection, setTimeRangeSelection] = useState(
|
|
111
115
|
DateTimeRange.fromRangeKey(querySelection.timeSelection, querySelection.from, querySelection.to)
|
|
@@ -113,6 +117,10 @@ const ProfileSelector = ({
|
|
|
113
117
|
|
|
114
118
|
const [queryExpressionString, setQueryExpressionString] = useState(querySelection.expression);
|
|
115
119
|
|
|
120
|
+
const [advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser] = useState(
|
|
121
|
+
queryBrowserMode === 'advanced'
|
|
122
|
+
);
|
|
123
|
+
|
|
116
124
|
const profileType = useMemo(() => {
|
|
117
125
|
return Query.parse(queryExpressionString).profileType();
|
|
118
126
|
}, [queryExpressionString]);
|
|
@@ -268,6 +276,8 @@ const ProfileSelector = ({
|
|
|
268
276
|
queryExpressionString === '' ||
|
|
269
277
|
queryExpressionString === '{}';
|
|
270
278
|
|
|
279
|
+
const queryBrowserRef = useRef<HTMLDivElement>(null);
|
|
280
|
+
|
|
271
281
|
return (
|
|
272
282
|
<>
|
|
273
283
|
<div className="mb-2 flex gap-2">
|
|
@@ -283,19 +293,51 @@ const ProfileSelector = ({
|
|
|
283
293
|
disabled={viewComponent?.disableProfileTypesDropdown}
|
|
284
294
|
/>
|
|
285
295
|
</div>
|
|
286
|
-
<div className="w-full flex-1 pb-6">
|
|
287
|
-
<div className="
|
|
288
|
-
<
|
|
296
|
+
<div className="w-full flex-1 flex flex-col pb-6 gap-1" ref={queryBrowserRef}>
|
|
297
|
+
<div className="flex items-center justify-between">
|
|
298
|
+
<div className="flex items-center gap-3">
|
|
299
|
+
<label className="text-xs">Query</label>
|
|
300
|
+
<Switch
|
|
301
|
+
checked={advancedModeForQueryBrowser}
|
|
302
|
+
onChange={() => {
|
|
303
|
+
setAdvancedModeForQueryBrowser(!advancedModeForQueryBrowser);
|
|
304
|
+
setQueryBrowserMode(advancedModeForQueryBrowser ? 'simple' : 'advanced');
|
|
305
|
+
}}
|
|
306
|
+
className={`${
|
|
307
|
+
advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-900'
|
|
308
|
+
}
|
|
309
|
+
relative inline-flex h-[20px] w-[44px] shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus-visible:ring-2 focus-visible:ring-white/75`}
|
|
310
|
+
>
|
|
311
|
+
<span className="sr-only">Use setting</span>
|
|
312
|
+
<span
|
|
313
|
+
aria-hidden="true"
|
|
314
|
+
className={`${advancedModeForQueryBrowser ? 'translate-x-6' : 'translate-x-0'}
|
|
315
|
+
pointer-events-none inline-block h-[16px] w-[16px] transform rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out`}
|
|
316
|
+
/>
|
|
317
|
+
</Switch>
|
|
318
|
+
<label className="text-xs">Advanced Mode</label>
|
|
319
|
+
</div>
|
|
289
320
|
{(query.matchers.length > 0 || query.inputMatcherString.length > 0) &&
|
|
290
321
|
viewComponent !== undefined && <div>{viewComponent?.createViewComponent}</div>}
|
|
291
322
|
</div>
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
323
|
+
{advancedModeForQueryBrowser ? (
|
|
324
|
+
<MatchersInput
|
|
325
|
+
queryClient={queryClient}
|
|
326
|
+
setMatchersString={setMatchersString}
|
|
327
|
+
runQuery={setQueryExpression}
|
|
328
|
+
currentQuery={query}
|
|
329
|
+
profileType={selectedProfileName}
|
|
330
|
+
/>
|
|
331
|
+
) : (
|
|
332
|
+
<SimpleMatchers
|
|
333
|
+
queryClient={queryClient}
|
|
334
|
+
setMatchersString={setMatchersString}
|
|
335
|
+
runQuery={setQueryExpression}
|
|
336
|
+
currentQuery={query}
|
|
337
|
+
profileType={selectedProfileName}
|
|
338
|
+
queryBrowserRef={queryBrowserRef}
|
|
339
|
+
/>
|
|
340
|
+
)}
|
|
299
341
|
</div>
|
|
300
342
|
<div className="pb-6">
|
|
301
343
|
<div className="mb-0.5 mt-1.5 flex items-center justify-between">
|
|
@@ -306,7 +348,7 @@ const ProfileSelector = ({
|
|
|
306
348
|
isMulti
|
|
307
349
|
name="colors"
|
|
308
350
|
options={labels.map(label => ({label, value: label}))}
|
|
309
|
-
className="parca-select-container text-sm w-80"
|
|
351
|
+
className="parca-select-container text-sm w-full max-w-80"
|
|
310
352
|
classNamePrefix="parca-select"
|
|
311
353
|
value={(sumBySelection ?? []).map(sumBy => ({label: sumBy, value: sumBy}))}
|
|
312
354
|
onChange={selectedOptions => {
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import React, {useEffect, useRef, useState} from 'react';
|
|
15
|
+
|
|
16
|
+
import {Icon} from '@iconify/react';
|
|
17
|
+
import cx from 'classnames';
|
|
18
|
+
|
|
19
|
+
import {useParcaContext} from '@parca/components';
|
|
20
|
+
|
|
21
|
+
export interface SelectElement {
|
|
22
|
+
active: JSX.Element;
|
|
23
|
+
expanded: JSX.Element;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SelectItem {
|
|
27
|
+
key: string;
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
element: SelectElement;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface CustomSelectProps {
|
|
33
|
+
items: SelectItem[];
|
|
34
|
+
selectedKey: string | undefined;
|
|
35
|
+
onSelection: (value: string) => void;
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
width?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
loading?: boolean;
|
|
40
|
+
primary?: boolean;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
icon?: JSX.Element;
|
|
43
|
+
id?: string;
|
|
44
|
+
optionsClassname?: string;
|
|
45
|
+
searchable?: boolean;
|
|
46
|
+
onButtonClick?: () => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const CustomSelect: React.FC<CustomSelectProps> = ({
|
|
50
|
+
items,
|
|
51
|
+
selectedKey,
|
|
52
|
+
onSelection,
|
|
53
|
+
placeholder,
|
|
54
|
+
width,
|
|
55
|
+
className = '',
|
|
56
|
+
loading,
|
|
57
|
+
primary = false,
|
|
58
|
+
disabled = false,
|
|
59
|
+
icon,
|
|
60
|
+
id,
|
|
61
|
+
optionsClassname = '',
|
|
62
|
+
searchable = false,
|
|
63
|
+
onButtonClick,
|
|
64
|
+
}) => {
|
|
65
|
+
const {loader} = useParcaContext();
|
|
66
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
67
|
+
const [focusedIndex, setFocusedIndex] = useState(-1);
|
|
68
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
69
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
70
|
+
const optionsRef = useRef<HTMLDivElement>(null);
|
|
71
|
+
const searchInputRef = useRef<HTMLInputElement>(null);
|
|
72
|
+
const optionRefs = useRef<Array<HTMLElement | null>>([]);
|
|
73
|
+
|
|
74
|
+
const filteredItems = searchable
|
|
75
|
+
? items.filter(item =>
|
|
76
|
+
item.element.active.props.children
|
|
77
|
+
.toString()
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.includes(searchTerm.toLowerCase())
|
|
80
|
+
)
|
|
81
|
+
: items;
|
|
82
|
+
|
|
83
|
+
const selection = items.find(v => v.key === selectedKey);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
const handleClickOutside = (event: MouseEvent): void => {
|
|
87
|
+
if (containerRef.current !== null && !containerRef.current.contains(event.target as Node)) {
|
|
88
|
+
setIsOpen(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
93
|
+
return () => {
|
|
94
|
+
document.removeEventListener('mousedown', handleClickOutside);
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (isOpen && searchable) {
|
|
100
|
+
searchInputRef.current?.focus();
|
|
101
|
+
}
|
|
102
|
+
}, [isOpen, searchable]);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
if (
|
|
106
|
+
focusedIndex !== -1 &&
|
|
107
|
+
optionsRef.current !== null &&
|
|
108
|
+
optionRefs.current[focusedIndex] !== null
|
|
109
|
+
) {
|
|
110
|
+
const optionElement = optionRefs.current[focusedIndex];
|
|
111
|
+
const optionsContainer = optionsRef.current;
|
|
112
|
+
|
|
113
|
+
if (optionElement !== null && optionsContainer !== null) {
|
|
114
|
+
const optionRect = optionElement.getBoundingClientRect();
|
|
115
|
+
const containerRect = optionsContainer.getBoundingClientRect();
|
|
116
|
+
|
|
117
|
+
if (optionRect.bottom > containerRect.bottom) {
|
|
118
|
+
optionsContainer.scrollTop += optionRect.bottom - containerRect.bottom;
|
|
119
|
+
} else if (optionRect.top < containerRect.top) {
|
|
120
|
+
optionsContainer.scrollTop -= containerRect.top - optionRect.top;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}, [focusedIndex]);
|
|
125
|
+
|
|
126
|
+
const handleKeyDown = (e: React.KeyboardEvent): void => {
|
|
127
|
+
if (e.key === 'Enter') {
|
|
128
|
+
if (!isOpen) {
|
|
129
|
+
setIsOpen(true);
|
|
130
|
+
} else if (focusedIndex !== -1) {
|
|
131
|
+
onSelection(filteredItems[focusedIndex].key);
|
|
132
|
+
setIsOpen(false);
|
|
133
|
+
}
|
|
134
|
+
} else if (e.key === 'Escape') {
|
|
135
|
+
setIsOpen(false);
|
|
136
|
+
} else if (e.key === 'Tab') {
|
|
137
|
+
if (isOpen) {
|
|
138
|
+
e.preventDefault();
|
|
139
|
+
if (e.shiftKey) {
|
|
140
|
+
// Shift+Tab: Move focus to the previous item
|
|
141
|
+
setFocusedIndex(prevIndex => (prevIndex <= 0 ? filteredItems.length - 1 : prevIndex - 1));
|
|
142
|
+
} else {
|
|
143
|
+
// Tab: Move focus to the next item
|
|
144
|
+
setFocusedIndex(prevIndex => (prevIndex + 1) % filteredItems.length);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} else if (e.key === 'ArrowDown') {
|
|
148
|
+
e.preventDefault();
|
|
149
|
+
setFocusedIndex(prevIndex => (prevIndex + 1) % filteredItems.length);
|
|
150
|
+
} else if (e.key === 'ArrowUp') {
|
|
151
|
+
e.preventDefault();
|
|
152
|
+
setFocusedIndex(prevIndex => (prevIndex - 1 + filteredItems.length) % filteredItems.length);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const styles =
|
|
157
|
+
'relative border rounded-md shadow-sm px-4 py-2 text-left cursor-default focus:outline-none focus:ring-1 items-center focus:ring-indigo-500 focus:border-indigo-500 text-sm flex gap-2 flex items-center justify-between';
|
|
158
|
+
const defaultStyles = 'bg-white dark:bg-gray-900 dark:border-gray-600';
|
|
159
|
+
const primaryStyles =
|
|
160
|
+
'text-gray-100 dark:gray-900 bg-indigo-600 border-indigo-500 font-medium py-2 px-4';
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div ref={containerRef} className="relative" onKeyDown={handleKeyDown} onClick={onButtonClick}>
|
|
164
|
+
<div
|
|
165
|
+
id={id}
|
|
166
|
+
onClick={() => !disabled && setIsOpen(!isOpen)}
|
|
167
|
+
className={cx(
|
|
168
|
+
styles,
|
|
169
|
+
width !== undefined ? `w-${width}` : 'w-full',
|
|
170
|
+
disabled ? 'cursor-not-allowed opacity-50 pointer-events-none' : '',
|
|
171
|
+
primary ? primaryStyles : defaultStyles,
|
|
172
|
+
{[className]: className.length > 0}
|
|
173
|
+
)}
|
|
174
|
+
tabIndex={0}
|
|
175
|
+
role="button"
|
|
176
|
+
aria-haspopup="listbox"
|
|
177
|
+
aria-expanded={isOpen}
|
|
178
|
+
>
|
|
179
|
+
<div
|
|
180
|
+
className={cx(
|
|
181
|
+
icon != null ? '' : 'block overflow-x-hidden text-ellipsis whitespace-nowrap'
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
{selection?.element.active ?? placeholder}
|
|
185
|
+
</div>
|
|
186
|
+
<div className={cx(icon != null ? '' : 'pointer-events-none text-gray-400')}>
|
|
187
|
+
{icon ?? <Icon icon="heroicons:chevron-up-down-20-solid" aria-hidden="true" />}
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{isOpen && (
|
|
192
|
+
<div
|
|
193
|
+
ref={optionsRef}
|
|
194
|
+
className={cx(
|
|
195
|
+
'absolute z-50 mt-1 pt-0 max-h-[50vh] w-max overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm',
|
|
196
|
+
{[optionsClassname]: optionsClassname.length > 0}
|
|
197
|
+
)}
|
|
198
|
+
role="listbox"
|
|
199
|
+
>
|
|
200
|
+
{searchable && (
|
|
201
|
+
<div className="sticky h-[45px] z-10 top-[-5px] border-b border-gray-200">
|
|
202
|
+
<input
|
|
203
|
+
ref={searchInputRef}
|
|
204
|
+
type="text"
|
|
205
|
+
className="w-full px-6 h-full text-sm border-none rounded-none ring-0 outline-none bg-gray-50 dark:bg-gray-800 dark:text-white"
|
|
206
|
+
placeholder="Search..."
|
|
207
|
+
value={searchTerm}
|
|
208
|
+
onChange={e => setSearchTerm(e.target.value)}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
{loading === true ? (
|
|
213
|
+
<div className="w-[270px]">{loader}</div>
|
|
214
|
+
) : (
|
|
215
|
+
filteredItems.map((item, index) => (
|
|
216
|
+
<div
|
|
217
|
+
key={item.key}
|
|
218
|
+
ref={el => (optionRefs.current[index] = el)}
|
|
219
|
+
className={cx(
|
|
220
|
+
'relative cursor-default select-none py-2 pl-3 pr-9',
|
|
221
|
+
index === focusedIndex && 'bg-indigo-600 text-white',
|
|
222
|
+
item.key === selectedKey && 'bg-indigo-100 dark:bg-indigo-700',
|
|
223
|
+
item.disabled !== null &&
|
|
224
|
+
item.disabled === true &&
|
|
225
|
+
'opacity-50 cursor-not-allowed',
|
|
226
|
+
'focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 hover:bg-indigo-600 hover:text-white'
|
|
227
|
+
)}
|
|
228
|
+
role="option"
|
|
229
|
+
aria-selected={item.key === selectedKey}
|
|
230
|
+
tabIndex={-1}
|
|
231
|
+
onClick={() => {
|
|
232
|
+
if (!(item.disabled ?? false)) {
|
|
233
|
+
onSelection(item.key);
|
|
234
|
+
setIsOpen(false);
|
|
235
|
+
}
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{item.element.expanded}
|
|
239
|
+
{item.key === selectedKey && (
|
|
240
|
+
<span className="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600">
|
|
241
|
+
<Icon icon="heroicons:check-20-solid" aria-hidden="true" />
|
|
242
|
+
</span>
|
|
243
|
+
)}
|
|
244
|
+
</div>
|
|
245
|
+
))
|
|
246
|
+
)}
|
|
247
|
+
</div>
|
|
248
|
+
)}
|
|
249
|
+
</div>
|
|
250
|
+
);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export default CustomSelect;
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useCallback, useEffect, useMemo, useState} from 'react';
|
|
15
|
+
|
|
16
|
+
import {Icon} from '@iconify/react';
|
|
17
|
+
import cx from 'classnames';
|
|
18
|
+
|
|
19
|
+
import {QueryServiceClient} from '@parca/client';
|
|
20
|
+
import {useGrpcMetadata} from '@parca/components';
|
|
21
|
+
import {Query} from '@parca/parser';
|
|
22
|
+
import {sanitizeLabelValue} from '@parca/utilities';
|
|
23
|
+
|
|
24
|
+
import {useLabelNames} from '../MatchersInput';
|
|
25
|
+
import Select, {type SelectItem} from './Select';
|
|
26
|
+
|
|
27
|
+
interface Props {
|
|
28
|
+
queryClient: QueryServiceClient;
|
|
29
|
+
setMatchersString: (arg: string) => void;
|
|
30
|
+
runQuery: () => void;
|
|
31
|
+
currentQuery: Query;
|
|
32
|
+
profileType: string;
|
|
33
|
+
queryBrowserRef: React.RefObject<HTMLDivElement>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface QueryRow {
|
|
37
|
+
labelName: string;
|
|
38
|
+
operator: string;
|
|
39
|
+
labelValue: string;
|
|
40
|
+
labelValues: string[];
|
|
41
|
+
isLoading: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const transformLabelsForSelect = (labelNames: string[]): SelectItem[] => {
|
|
45
|
+
return labelNames.map(labelName => ({
|
|
46
|
+
key: labelName,
|
|
47
|
+
element: {active: <>{labelName}</>, expanded: <>{labelName}</>},
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const operatorOptions = [
|
|
52
|
+
{
|
|
53
|
+
key: '=',
|
|
54
|
+
element: {
|
|
55
|
+
active: <>=</>,
|
|
56
|
+
expanded: (
|
|
57
|
+
<>
|
|
58
|
+
<span>=</span>
|
|
59
|
+
</>
|
|
60
|
+
),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: '!=',
|
|
65
|
+
element: {
|
|
66
|
+
active: <>{'!='}</>,
|
|
67
|
+
expanded: (
|
|
68
|
+
<>
|
|
69
|
+
<span>{'!='}</span>
|
|
70
|
+
</>
|
|
71
|
+
),
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
// TODO: Implement these operators to work properly.
|
|
75
|
+
// {
|
|
76
|
+
// key: '=~',
|
|
77
|
+
// element: {
|
|
78
|
+
// active: <>{'=~'}</>,
|
|
79
|
+
// expanded: (
|
|
80
|
+
// <>
|
|
81
|
+
// <span>{'=~'}</span>
|
|
82
|
+
// </>
|
|
83
|
+
// ),
|
|
84
|
+
// },
|
|
85
|
+
// },
|
|
86
|
+
// {
|
|
87
|
+
// key: '!~',
|
|
88
|
+
// element: {
|
|
89
|
+
// active: <>{'!~'}</>,
|
|
90
|
+
// expanded: (
|
|
91
|
+
// <>
|
|
92
|
+
// <span>{'!~'}</span>
|
|
93
|
+
// </>
|
|
94
|
+
// ),
|
|
95
|
+
// },
|
|
96
|
+
// },
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const SimpleMatchers = ({
|
|
100
|
+
queryClient,
|
|
101
|
+
setMatchersString,
|
|
102
|
+
// runQuery,
|
|
103
|
+
currentQuery,
|
|
104
|
+
profileType,
|
|
105
|
+
queryBrowserRef,
|
|
106
|
+
}: Props): JSX.Element => {
|
|
107
|
+
const [queryRows, setQueryRows] = useState<QueryRow[]>([
|
|
108
|
+
{labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
|
|
109
|
+
]);
|
|
110
|
+
const metadata = useGrpcMetadata();
|
|
111
|
+
|
|
112
|
+
const {loading: labelNamesLoading, result} = useLabelNames(queryClient, profileType);
|
|
113
|
+
const {response: labelNamesResponse, error: labelNamesError} = result;
|
|
114
|
+
const [showAll, setShowAll] = useState(false);
|
|
115
|
+
|
|
116
|
+
const visibleRows = showAll ? queryRows : queryRows.slice(0, 3);
|
|
117
|
+
const hiddenRowsCount = queryRows.length - 3;
|
|
118
|
+
|
|
119
|
+
const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString() as string}px]`;
|
|
120
|
+
|
|
121
|
+
const currentMatchers = currentQuery.matchersString();
|
|
122
|
+
|
|
123
|
+
const fetchLabelValues = useCallback(
|
|
124
|
+
async (labelName: string): Promise<string[]> => {
|
|
125
|
+
try {
|
|
126
|
+
const response = await queryClient.values(
|
|
127
|
+
{labelName, match: [], profileType},
|
|
128
|
+
{meta: metadata}
|
|
129
|
+
).response;
|
|
130
|
+
const sanitizedValues = sanitizeLabelValue(response.labelValues);
|
|
131
|
+
return sanitizedValues;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.error('Error fetching label values:', error);
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
[queryClient, metadata, profileType]
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const updateMatchersString = useCallback(
|
|
141
|
+
(rows: QueryRow[]) => {
|
|
142
|
+
const matcherString = rows
|
|
143
|
+
.filter(row => row.labelName.length > 0 && row.labelValue)
|
|
144
|
+
.map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
|
|
145
|
+
.join(',');
|
|
146
|
+
setMatchersString(matcherString);
|
|
147
|
+
},
|
|
148
|
+
[setMatchersString]
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
if (currentMatchers === '') {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const fetchAndSetQueryRows = async (): Promise<void> => {
|
|
157
|
+
const newRows = await Promise.all(
|
|
158
|
+
currentMatchers.split(',').map(async matcher => {
|
|
159
|
+
let [labelName, operator, labelValue] = matcher.split(/(=|!=|=~|!~)/);
|
|
160
|
+
labelName = labelName.trim();
|
|
161
|
+
if (labelName === '') return null;
|
|
162
|
+
|
|
163
|
+
const labelValues = await fetchLabelValues(labelName);
|
|
164
|
+
const sanitizedLabelValue =
|
|
165
|
+
labelValue.startsWith('"') && labelValue.endsWith('"')
|
|
166
|
+
? labelValue.slice(1, -1)
|
|
167
|
+
: labelValue;
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
labelName,
|
|
171
|
+
operator,
|
|
172
|
+
labelValue: sanitizedLabelValue,
|
|
173
|
+
labelValues,
|
|
174
|
+
};
|
|
175
|
+
})
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
const filteredRows = newRows.filter((row): row is QueryRow => row !== null);
|
|
179
|
+
setQueryRows(filteredRows);
|
|
180
|
+
updateMatchersString(filteredRows);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
void fetchAndSetQueryRows();
|
|
184
|
+
}, [currentMatchers, fetchLabelValues, updateMatchersString]);
|
|
185
|
+
|
|
186
|
+
const labelNames = useMemo(() => {
|
|
187
|
+
return (labelNamesError === undefined || labelNamesError == null) &&
|
|
188
|
+
labelNamesResponse !== undefined &&
|
|
189
|
+
labelNamesResponse != null
|
|
190
|
+
? labelNamesResponse.labelNames.filter(e => e !== '__name__')
|
|
191
|
+
: [];
|
|
192
|
+
}, [labelNamesError, labelNamesResponse]);
|
|
193
|
+
|
|
194
|
+
const labelNameOptions = useMemo(() => {
|
|
195
|
+
return transformLabelsForSelect(labelNames);
|
|
196
|
+
}, [labelNames]);
|
|
197
|
+
|
|
198
|
+
const updateRow = useCallback(
|
|
199
|
+
async (index: number, field: keyof QueryRow, value: string): Promise<void> => {
|
|
200
|
+
const updatedRows = [...queryRows];
|
|
201
|
+
const prevLabelName = updatedRows[index].labelName;
|
|
202
|
+
updatedRows[index] = {...updatedRows[index], [field]: value};
|
|
203
|
+
|
|
204
|
+
if (field === 'labelName' && value !== prevLabelName) {
|
|
205
|
+
updatedRows[index].labelValues = [];
|
|
206
|
+
updatedRows[index].labelValue = '';
|
|
207
|
+
updatedRows[index].isLoading = true;
|
|
208
|
+
setQueryRows([...updatedRows]);
|
|
209
|
+
|
|
210
|
+
const labelValues = await fetchLabelValues(value);
|
|
211
|
+
updatedRows[index].labelValues = labelValues;
|
|
212
|
+
updatedRows[index].isLoading = false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setQueryRows([...updatedRows]);
|
|
216
|
+
updateMatchersString(updatedRows);
|
|
217
|
+
},
|
|
218
|
+
[queryRows, fetchLabelValues, updateMatchersString]
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const handleUpdateRow = useCallback(
|
|
222
|
+
(index: number, field: keyof QueryRow, value: string) => {
|
|
223
|
+
void updateRow(index, field, value);
|
|
224
|
+
},
|
|
225
|
+
[updateRow]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const addNewRow = useCallback((): void => {
|
|
229
|
+
const newRows = [
|
|
230
|
+
...queryRows,
|
|
231
|
+
{labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
|
|
232
|
+
];
|
|
233
|
+
setQueryRows(newRows);
|
|
234
|
+
updateMatchersString(newRows);
|
|
235
|
+
}, [queryRows, updateMatchersString]);
|
|
236
|
+
|
|
237
|
+
const removeRow = useCallback(
|
|
238
|
+
(index: number): void => {
|
|
239
|
+
if (queryRows.length === 1) {
|
|
240
|
+
// Reset the single row instead of removing it
|
|
241
|
+
const resetRow = {
|
|
242
|
+
labelName: '',
|
|
243
|
+
operator: '=',
|
|
244
|
+
labelValue: '',
|
|
245
|
+
labelValues: [],
|
|
246
|
+
isLoading: false,
|
|
247
|
+
};
|
|
248
|
+
setQueryRows([resetRow]);
|
|
249
|
+
updateMatchersString([resetRow]);
|
|
250
|
+
} else {
|
|
251
|
+
const updatedRows = queryRows.filter((_, i) => i !== index);
|
|
252
|
+
setQueryRows(updatedRows);
|
|
253
|
+
updateMatchersString(updatedRows);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
[queryRows, updateMatchersString]
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const handleLabelValueClick = useCallback(
|
|
260
|
+
(index: number) => {
|
|
261
|
+
return async () => {
|
|
262
|
+
const updatedRows = [...queryRows];
|
|
263
|
+
if (updatedRows[index].labelValues.length === 0 && updatedRows[index].labelName !== '') {
|
|
264
|
+
updatedRows[index].isLoading = true;
|
|
265
|
+
setQueryRows([...updatedRows]);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const labelValues = await fetchLabelValues(updatedRows[index].labelName);
|
|
269
|
+
updatedRows[index].labelValues = labelValues;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('Error fetching label values:', error);
|
|
272
|
+
} finally {
|
|
273
|
+
updatedRows[index].isLoading = false;
|
|
274
|
+
setQueryRows([...updatedRows]);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`Label values already present or empty label name`);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
},
|
|
281
|
+
[queryRows, fetchLabelValues]
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<div className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}>
|
|
286
|
+
{visibleRows.map((row, index) => (
|
|
287
|
+
<div key={index} className="flex items-center">
|
|
288
|
+
<Select
|
|
289
|
+
items={labelNameOptions}
|
|
290
|
+
onSelection={value => handleUpdateRow(index, 'labelName', value)}
|
|
291
|
+
placeholder="Select label name"
|
|
292
|
+
selectedKey={row.labelName}
|
|
293
|
+
className="rounded-tr-none rounded-br-none ring-0 focus:ring-0 outline-none"
|
|
294
|
+
loading={labelNamesLoading}
|
|
295
|
+
searchable={true}
|
|
296
|
+
/>
|
|
297
|
+
<Select
|
|
298
|
+
items={operatorOptions}
|
|
299
|
+
onSelection={value => handleUpdateRow(index, 'operator', value)}
|
|
300
|
+
selectedKey={row.operator}
|
|
301
|
+
className="rounded-none ring-0 focus:ring-0 outline-none"
|
|
302
|
+
/>
|
|
303
|
+
<Select
|
|
304
|
+
items={transformLabelsForSelect(row.labelValues)}
|
|
305
|
+
onSelection={value => handleUpdateRow(index, 'labelValue', value)}
|
|
306
|
+
placeholder="Select label value"
|
|
307
|
+
selectedKey={row.labelValue}
|
|
308
|
+
className="rounded-none ring-0 focus:ring-0 outline-none max-w-48"
|
|
309
|
+
optionsClassname="max-w-[300px]"
|
|
310
|
+
searchable={true}
|
|
311
|
+
disabled={row.labelName === ''}
|
|
312
|
+
loading={row.isLoading}
|
|
313
|
+
onButtonClick={() => handleLabelValueClick(index)}
|
|
314
|
+
/>
|
|
315
|
+
<button
|
|
316
|
+
onClick={() => removeRow(index)}
|
|
317
|
+
className={cx(
|
|
318
|
+
'p-2 border-gray-200 border rounded rounded-tl-none rounded-bl-none focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900'
|
|
319
|
+
)}
|
|
320
|
+
>
|
|
321
|
+
<Icon icon="carbon:close" className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
))}
|
|
325
|
+
|
|
326
|
+
{queryRows.length > 3 && (
|
|
327
|
+
<button
|
|
328
|
+
onClick={() => setShowAll(!showAll)}
|
|
329
|
+
className="mr-2 px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900"
|
|
330
|
+
>
|
|
331
|
+
{showAll ? 'Show less' : `Show ${hiddenRowsCount} more`}
|
|
332
|
+
</button>
|
|
333
|
+
)}
|
|
334
|
+
|
|
335
|
+
<button
|
|
336
|
+
onClick={addNewRow}
|
|
337
|
+
className="p-2 border-gray-200 dark:bg-gray-900 dark:border-gray-600 border rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
|
|
338
|
+
>
|
|
339
|
+
<Icon icon="material-symbols:add" className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
340
|
+
</button>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export default SimpleMatchers;
|