@parca/profile 0.16.50 → 0.16.52

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 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.52](https://github.com/parca-dev/parca/compare/ui-v0.16.51...ui-v0.16.52) (2022-10-19)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.51](https://github.com/parca-dev/parca/compare/ui-v0.16.50...ui-v0.16.51) (2022-10-19)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.50](https://github.com/parca-dev/parca/compare/ui-v0.16.49...ui-v0.16.50) (2022-10-18)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import { ProfileTypesResponse } from '@parca/client';
2
+ import { ProfileType, ProfileTypesResponse } from '@parca/client';
3
3
  import { RpcError } from '@protobuf-ts/runtime-rpc';
4
4
  interface WellKnownProfile {
5
5
  name: string;
@@ -9,6 +9,7 @@ interface WellKnownProfiles {
9
9
  [key: string]: WellKnownProfile;
10
10
  }
11
11
  export declare const wellKnownProfiles: WellKnownProfiles;
12
+ export declare const normalizeProfileTypesData: (types: ProfileType[]) => string[];
12
13
  interface Props {
13
14
  profileTypesData?: ProfileTypesResponse;
14
15
  loading?: boolean;
@@ -94,8 +94,9 @@ export var wellKnownProfiles = {
94
94
  };
95
95
  function flexibleWellKnownProfileMatching(name) {
96
96
  var prefixExcludedName = name.split(':').slice(1).join(':');
97
+ var deltaExcludedName = prefixExcludedName.replace(/:delta$/, '');
97
98
  var requiredKey = Object.keys(wellKnownProfiles).find(function (key) {
98
- if (key.endsWith(prefixExcludedName)) {
99
+ if (key.includes(deltaExcludedName)) {
99
100
  return true;
100
101
  }
101
102
  return false;
@@ -114,19 +115,22 @@ function profileSelectElement(name, flexibleKnownProfilesDetection) {
114
115
  expanded: (_jsxs(_Fragment, { children: [_jsx("span", { children: title }), _jsx("br", {}), _jsx("span", __assign({ className: "text-xs" }, { children: wellKnown.help }))] })),
115
116
  };
116
117
  }
118
+ export var normalizeProfileTypesData = function (types) {
119
+ return types
120
+ .map(function (type) {
121
+ return "".concat(type.name, ":").concat(type.sampleType, ":").concat(type.sampleUnit, ":").concat(type.periodType, ":").concat(type.periodUnit).concat(type.delta ? ':delta' : '');
122
+ })
123
+ .sort(function (a, b) {
124
+ return a.localeCompare(b);
125
+ });
126
+ };
117
127
  var ProfileTypeSelector = function (_a) {
118
128
  var profileTypesData = _a.profileTypesData, _b = _a.loading, loading = _b === void 0 ? false : _b, error = _a.error, selectedKey = _a.selectedKey, onSelection = _a.onSelection, _c = _a.flexibleKnownProfilesDetection, flexibleKnownProfilesDetection = _c === void 0 ? false : _c;
119
129
  var profileNames = useMemo(function () {
120
130
  return (error === undefined || error == null) &&
121
131
  profileTypesData !== undefined &&
122
132
  profileTypesData != null
123
- ? profileTypesData.types
124
- .map(function (type) {
125
- return "".concat(type.name, ":").concat(type.sampleType, ":").concat(type.sampleUnit, ":").concat(type.periodType, ":").concat(type.periodUnit).concat(type.delta ? ':delta' : '');
126
- })
127
- .sort(function (a, b) {
128
- return a.localeCompare(b);
129
- })
133
+ ? normalizeProfileTypesData(profileTypesData.types)
130
134
  : [];
131
135
  }, [profileTypesData, error]);
132
136
  var profileLabels = profileNames.map(function (name) { return ({
@@ -3,12 +3,12 @@ import { QueryServiceClient, Flamegraph, Top, Callgraph } from '@parca/client';
3
3
  import { ProfileSource } from './ProfileSource';
4
4
  import './ProfileView.styles.css';
5
5
  declare type NavigateFunction = (path: string, queryParams: any) => void;
6
- interface FlamegraphData {
6
+ export interface FlamegraphData {
7
7
  loading: boolean;
8
8
  data?: Flamegraph;
9
9
  error?: any;
10
10
  }
11
- interface TopTableData {
11
+ export interface TopTableData {
12
12
  loading: boolean;
13
13
  data?: Top;
14
14
  error?: any;
@@ -19,11 +19,11 @@ interface CallgraphData {
19
19
  error?: any;
20
20
  }
21
21
  export declare type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
22
- interface ProfileVisState {
22
+ export interface ProfileVisState {
23
23
  currentView: VisualizationType;
24
24
  setCurrentView: (view: VisualizationType) => void;
25
25
  }
26
- interface ProfileViewProps {
26
+ export interface ProfileViewProps {
27
27
  flamegraphData?: FlamegraphData;
28
28
  topTableData?: TopTableData;
29
29
  callgraphData?: CallgraphData;
@@ -104,8 +104,8 @@ export var ProfileView = function (_a) {
104
104
  var router = parseParams(window.location.search);
105
105
  navigateTo('/', __assign(__assign({}, router), { currentProfileView: view }));
106
106
  };
107
- return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
108
- e.preventDefault();
109
- onDownloadPProf();
110
- } }, { children: "Download pprof" }))] })), _jsx(SearchNodes, {})] })), _jsxs("div", __assign({ className: "flex ml-auto" }, { children: [_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })) })), callgraphEnabled ? (_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Call Graph" })) }))) : null, _jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] })), _jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(CallgraphComponent, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] }))] }) }) })) }));
107
+ return (_jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
108
+ e.preventDefault();
109
+ onDownloadPProf();
110
+ } }, { children: "Download pprof" }))] })), _jsx(SearchNodes, {})] })), _jsxs("div", __assign({ className: "flex ml-auto" }, { children: [_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })) })), callgraphEnabled ? (_jsx("div", __assign({ className: "mr-3" }, { children: _jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Call Graph" })) }))) : null, _jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] })), _jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(CallgraphComponent, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] }))] }) }) })));
111
111
  };
@@ -52,6 +52,7 @@ import { useQuery } from './useQuery';
52
52
  import { ProfileView, useProfileVisState } from './ProfileView';
53
53
  import { downloadPprof } from './utils';
54
54
  import { useGrpcMetadata } from '@parca/components';
55
+ import { saveAsBlob } from '@parca/functions';
55
56
  export var ProfileViewWithData = function (_a) {
56
57
  var _b, _c;
57
58
  var queryClient = _a.queryClient, profileSource = _a.profileSource, navigateTo = _a.navigateTo;
@@ -69,7 +70,7 @@ export var ProfileViewWithData = function (_a) {
69
70
  }), callgraphLoading = _f.isLoading, callgraphResponse = _f.response, callgraphError = _f.error;
70
71
  var sampleUnit = profileSource.ProfileType().sampleUnit;
71
72
  var downloadPProfClick = function () { return __awaiter(void 0, void 0, void 0, function () {
72
- var blob, link, error_1;
73
+ var blob, error_1;
73
74
  return __generator(this, function (_a) {
74
75
  switch (_a.label) {
75
76
  case 0:
@@ -82,10 +83,7 @@ export var ProfileViewWithData = function (_a) {
82
83
  return [4 /*yield*/, downloadPprof(profileSource.QueryRequest(), queryClient, metadata)];
83
84
  case 2:
84
85
  blob = _a.sent();
85
- link = document.createElement('a');
86
- link.href = window.URL.createObjectURL(blob);
87
- link.download = 'profile.pb.gz';
88
- link.click();
86
+ saveAsBlob(blob, "profile.pb.gz");
89
87
  return [3 /*break*/, 4];
90
88
  case 3:
91
89
  error_1 = _a.sent();
package/dist/index.d.ts CHANGED
@@ -2,12 +2,28 @@ import React from 'react';
2
2
  import type { Props as CallgraphProps } from '@parca/profile/src/Callgraph';
3
3
  import ProfileExplorer from './ProfileExplorer';
4
4
  import ProfileTypeSelector from './ProfileTypeSelector';
5
+ import type { FlamegraphData, TopTableData } from './ProfileView';
6
+ import { QueryServiceClient } from '@parca/client';
5
7
  export * from './IcicleGraph';
6
8
  export * from './ProfileIcicleGraph';
7
9
  export * from './ProfileSource';
8
10
  export * from './ProfileView';
9
11
  export * from './ProfileViewWithData';
10
12
  export * from './utils';
13
+ export * from './ProfileTypeSelector';
11
14
  export type { CallgraphProps };
12
15
  declare const Callgraph: React.LazyExoticComponent<({ graph, sampleUnit, width }: CallgraphProps) => JSX.Element>;
13
16
  export { Callgraph, ProfileExplorer, ProfileTypeSelector };
17
+ interface GrafanaParcaDataPayload {
18
+ flamegraphData: FlamegraphData;
19
+ topTableData: TopTableData;
20
+ actions: {
21
+ downloadPprof: () => void;
22
+ getQueryClient: () => QueryServiceClient;
23
+ };
24
+ error?: undefined;
25
+ }
26
+ interface GrafanaParcaErrorPayload {
27
+ error: Error;
28
+ }
29
+ export declare type GrafanaParcaData = GrafanaParcaErrorPayload | GrafanaParcaDataPayload;
package/dist/index.js CHANGED
@@ -55,6 +55,7 @@ export * from './ProfileSource';
55
55
  export * from './ProfileView';
56
56
  export * from './ProfileViewWithData';
57
57
  export * from './utils';
58
+ export * from './ProfileTypeSelector';
58
59
  var Callgraph = React.lazy(function () { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
59
60
  switch (_a.label) {
60
61
  case 0: return [4 /*yield*/, import('@parca/profile/src/Callgraph')];
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.1.8 | 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-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%}*,: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}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.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}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.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}.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-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.rounded-none{border-radius:0}.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-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.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))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.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-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.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-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/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-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.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);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);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)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}.dark .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}.dark .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.dark .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}.dark .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.dark .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.dark .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.dark .dark\:fill-\[\#ffffff\]{fill:#fff}.dark .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.dark .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.dark .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}.dark .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}.dark .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ /*! tailwindcss v3.1.8 | 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-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%}*,: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}}.absolute{position:absolute}.relative{position:relative}.left-0{left:0}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mr-3{margin-right:.75rem}.ml-auto{margin-left:auto}.ml-2{margin-left:.5rem}.mr-6{margin-right:1.5rem}.mt-2{margin-top:.5rem}.mt-1{margin-top:.25rem}.mb-2{margin-bottom:.5rem}.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}.h-36{height:9rem}.h-1{height:.25rem}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.w-full{width:100%}.w-2\/5{width:40%}.w-auto{width:auto}.w-1\/2{width:50%}.w-\[150px\]{width:150px}.w-1\/5{width:20%}.w-4\/5{width:80%}.w-fit{width:-moz-fit-content;width:fit-content}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-40{width:10rem}.w-8{width:2rem}.w-16{width:4rem}.w-\[420px\]{width:420px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.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}.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-not-allowed{cursor:not-allowed}.cursor-default{cursor:default}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-scroll{overflow-x:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded{border-radius:.25rem}.rounded-none{border-radius:0}.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-l-0{border-left-width:0}.border-r-0{border-right-width:0}.border-b{border-bottom-width:1px}.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))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.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-inherit{background-color:inherit}.fill-\[\#161616\]{fill:#161616}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.p-10{padding:2.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-20{padding-bottom:5rem;padding-top:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pt-2{padding-top:.5rem}.pb-2{padding-bottom:.5rem}.pl-2{padding-left:.5rem}.pr-2{padding-right:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-xs{font-size:.75rem;line-height:1rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-base{font-size:1rem;line-height:1.5rem}.font-bold{font-weight:700}.font-semibold{font-weight:600}.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-gray-100{--tw-text-opacity:1;color:rgb(243 244 246/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-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-90{opacity:.9}.opacity-100{opacity:1}.opacity-0{opacity:0}.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);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);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)}.hover\:bg-\[\#62626212\]:hover{background-color:#62626212}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}[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\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/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-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:fill-\[\#ffffff\]{fill:#fff}[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-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/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-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:hover\:bg-\[\#ffffff12\]:hover{background-color:#ffffff12}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
package/package.json CHANGED
@@ -1,21 +1,29 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.50",
3
+ "version": "0.16.52",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
- "@parca/client": "^0.16.49",
8
- "@parca/components": "^0.16.49",
9
- "@parca/dynamicsize": "^0.16.49",
10
- "@parca/functions": "^0.16.49",
7
+ "@parca/client": "^0.16.51",
8
+ "@parca/components": "^0.16.51",
9
+ "@parca/dynamicsize": "^0.16.51",
10
+ "@parca/functions": "^0.16.51",
11
11
  "@parca/parser": "^0.16.49",
12
12
  "@parca/store": "^0.16.49",
13
+ "d3": "7.6.1",
13
14
  "d3-scale": "^4.0.2",
14
15
  "d3-selection": "3.0.0",
15
- "react-copy-to-clipboard": "^5.1.0"
16
+ "graphviz-wasm": "3.0.0",
17
+ "konva": "8.3.12",
18
+ "react-copy-to-clipboard": "^5.1.0",
19
+ "react-cytoscapejs": "1.2.1",
20
+ "react-konva": "18.2.1"
16
21
  },
17
22
  "devDependencies": {
18
- "@types/react-copy-to-clipboard": "5.0.4"
23
+ "@types/cytoscape": "3.19.9",
24
+ "@types/d3": "7.4.0",
25
+ "@types/react-copy-to-clipboard": "5.0.4",
26
+ "@types/react-cytoscapejs": "1.2.2"
19
27
  },
20
28
  "main": "dist/index.js",
21
29
  "scripts": {
@@ -31,5 +39,5 @@
31
39
  "access": "public",
32
40
  "registry": "https://registry.npmjs.org/"
33
41
  },
34
- "gitHead": "5d06484489b4317175c47ac3a6070d40e0ebae76"
42
+ "gitHead": "5641bcb39df874b8235c2d0447d22c40bc3fa5dd"
35
43
  }
@@ -12,7 +12,7 @@
12
12
  // limitations under the License.
13
13
 
14
14
  import React, {useMemo} from 'react';
15
- import {ProfileTypesResponse} from '@parca/client';
15
+ import {ProfileType, ProfileTypesResponse} from '@parca/client';
16
16
  import {RpcError} from '@protobuf-ts/runtime-rpc';
17
17
  import {SelectElement, Select} from '@parca/components';
18
18
 
@@ -96,8 +96,9 @@ export const wellKnownProfiles: WellKnownProfiles = {
96
96
 
97
97
  function flexibleWellKnownProfileMatching(name: string): WellKnownProfile | undefined {
98
98
  const prefixExcludedName = name.split(':').slice(1).join(':');
99
+ const deltaExcludedName = prefixExcludedName.replace(/:delta$/, '');
99
100
  const requiredKey = Object.keys(wellKnownProfiles).find(key => {
100
- if (key.endsWith(prefixExcludedName)) {
101
+ if (key.includes(deltaExcludedName)) {
101
102
  return true;
102
103
  }
103
104
  return false;
@@ -127,6 +128,19 @@ function profileSelectElement(
127
128
  };
128
129
  }
129
130
 
131
+ export const normalizeProfileTypesData = (types: ProfileType[]): string[] => {
132
+ return types
133
+ .map(
134
+ type =>
135
+ `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
136
+ type.delta ? ':delta' : ''
137
+ }`
138
+ )
139
+ .sort((a: string, b: string): number => {
140
+ return a.localeCompare(b);
141
+ });
142
+ };
143
+
130
144
  interface Props {
131
145
  profileTypesData?: ProfileTypesResponse;
132
146
  loading?: boolean;
@@ -148,16 +162,7 @@ const ProfileTypeSelector = ({
148
162
  return (error === undefined || error == null) &&
149
163
  profileTypesData !== undefined &&
150
164
  profileTypesData != null
151
- ? profileTypesData.types
152
- .map(
153
- type =>
154
- `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${
155
- type.periodUnit
156
- }${type.delta ? ':delta' : ''}`
157
- )
158
- .sort((a: string, b: string): number => {
159
- return a.localeCompare(b);
160
- })
165
+ ? normalizeProfileTypesData(profileTypesData.types)
161
166
  : [];
162
167
  }, [profileTypesData, error]);
163
168
 
@@ -30,13 +30,13 @@ import './ProfileView.styles.css';
30
30
 
31
31
  type NavigateFunction = (path: string, queryParams: any) => void;
32
32
 
33
- interface FlamegraphData {
33
+ export interface FlamegraphData {
34
34
  loading: boolean;
35
35
  data?: Flamegraph;
36
36
  error?: any;
37
37
  }
38
38
 
39
- interface TopTableData {
39
+ export interface TopTableData {
40
40
  loading: boolean;
41
41
  data?: Top;
42
42
  error?: any;
@@ -50,12 +50,12 @@ interface CallgraphData {
50
50
 
51
51
  export type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
52
52
 
53
- interface ProfileVisState {
53
+ export interface ProfileVisState {
54
54
  currentView: VisualizationType;
55
55
  setCurrentView: (view: VisualizationType) => void;
56
56
  }
57
57
 
58
- interface ProfileViewProps {
58
+ export interface ProfileViewProps {
59
59
  flamegraphData?: FlamegraphData;
60
60
  topTableData?: TopTableData;
61
61
  callgraphData?: CallgraphData;
@@ -170,136 +170,134 @@ export const ProfileView = ({
170
170
  };
171
171
 
172
172
  return (
173
- <>
174
- <div className="py-3">
175
- <Card>
176
- <Card.Body>
177
- <div className="flex py-3 w-full">
178
- <div className="w-2/5 flex space-x-4">
179
- <div className="flex space-x-1">
180
- {profileSource != null && queryClient != null ? (
181
- <ProfileShareButton
182
- queryRequest={profileSource.QueryRequest()}
183
- queryClient={queryClient}
184
- />
185
- ) : null}
186
-
187
- <Button
188
- color="neutral"
189
- onClick={e => {
190
- e.preventDefault();
191
- onDownloadPProf();
192
- }}
193
- >
194
- Download pprof
195
- </Button>
196
- </div>
197
-
198
- <SearchNodes />
199
- </div>
200
-
201
- <div className="flex ml-auto">
202
- <div className="mr-3">
203
- <Button
204
- color="neutral"
205
- onClick={resetIcicleGraph}
206
- disabled={curPath.length === 0}
207
- className="whitespace-nowrap text-ellipsis"
208
- >
209
- Reset View
210
- </Button>
211
- </div>
212
-
213
- {callgraphEnabled ? (
214
- <div className="mr-3">
215
- <Button
216
- variant={`${currentView === 'callgraph' ? 'primary' : 'neutral'}`}
217
- onClick={() => switchProfileView('callgraph')}
218
- className="whitespace-nowrap text-ellipsis"
219
- >
220
- Call Graph
221
- </Button>
222
- </div>
173
+ <div className="py-3">
174
+ <Card>
175
+ <Card.Body>
176
+ <div className="flex py-3 w-full">
177
+ <div className="w-2/5 flex space-x-4">
178
+ <div className="flex space-x-1">
179
+ {profileSource != null && queryClient != null ? (
180
+ <ProfileShareButton
181
+ queryRequest={profileSource.QueryRequest()}
182
+ queryClient={queryClient}
183
+ />
223
184
  ) : null}
224
185
 
225
186
  <Button
226
- variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
227
- className="items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
228
- onClick={() => switchProfileView('table')}
187
+ color="neutral"
188
+ onClick={e => {
189
+ e.preventDefault();
190
+ onDownloadPProf();
191
+ }}
229
192
  >
230
- Table
193
+ Download pprof
231
194
  </Button>
195
+ </div>
232
196
 
233
- <Button
234
- variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
235
- className="items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis"
236
- onClick={() => switchProfileView('both')}
237
- >
238
- Both
239
- </Button>
197
+ <SearchNodes />
198
+ </div>
240
199
 
200
+ <div className="flex ml-auto">
201
+ <div className="mr-3">
241
202
  <Button
242
- variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
243
- className="items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
244
- onClick={() => switchProfileView('icicle')}
203
+ color="neutral"
204
+ onClick={resetIcicleGraph}
205
+ disabled={curPath.length === 0}
206
+ className="whitespace-nowrap text-ellipsis"
245
207
  >
246
- Icicle Graph
208
+ Reset View
247
209
  </Button>
248
210
  </div>
211
+
212
+ {callgraphEnabled ? (
213
+ <div className="mr-3">
214
+ <Button
215
+ variant={`${currentView === 'callgraph' ? 'primary' : 'neutral'}`}
216
+ onClick={() => switchProfileView('callgraph')}
217
+ className="whitespace-nowrap text-ellipsis"
218
+ >
219
+ Call Graph
220
+ </Button>
221
+ </div>
222
+ ) : null}
223
+
224
+ <Button
225
+ variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
226
+ className="items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
227
+ onClick={() => switchProfileView('table')}
228
+ >
229
+ Table
230
+ </Button>
231
+
232
+ <Button
233
+ variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
234
+ className="items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis"
235
+ onClick={() => switchProfileView('both')}
236
+ >
237
+ Both
238
+ </Button>
239
+
240
+ <Button
241
+ variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
242
+ className="items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
243
+ onClick={() => switchProfileView('icicle')}
244
+ >
245
+ Icicle Graph
246
+ </Button>
249
247
  </div>
248
+ </div>
249
+
250
+ <div ref={ref} className="flex space-x-4 justify-between w-full">
251
+ {currentView === 'icicle' && flamegraphData?.data != null && (
252
+ <div className="w-full">
253
+ <ProfileIcicleGraph
254
+ curPath={curPath}
255
+ setNewCurPath={setNewCurPath}
256
+ graph={flamegraphData.data}
257
+ sampleUnit={sampleUnit}
258
+ />
259
+ </div>
260
+ )}
250
261
 
251
- <div ref={ref} className="flex space-x-4 justify-between w-full">
252
- {currentView === 'icicle' && flamegraphData?.data != null && (
253
- <div className="w-full">
254
- <ProfileIcicleGraph
255
- curPath={curPath}
256
- setNewCurPath={setNewCurPath}
257
- graph={flamegraphData.data}
262
+ {currentView === 'callgraph' && callgraphData?.data != null && (
263
+ <div className="w-full">
264
+ {dimensions?.width !== undefined && (
265
+ <CallgraphComponent
266
+ graph={callgraphData.data}
258
267
  sampleUnit={sampleUnit}
268
+ width={dimensions?.width}
259
269
  />
270
+ )}
271
+ </div>
272
+ )}
273
+
274
+ {currentView === 'table' && topTableData != null && (
275
+ <div className="w-full">
276
+ <TopTable data={topTableData.data} sampleUnit={sampleUnit} />
277
+ </div>
278
+ )}
279
+
280
+ {currentView === 'both' && (
281
+ <>
282
+ <div className="w-1/2">
283
+ <TopTable data={topTableData?.data} sampleUnit={sampleUnit} />
260
284
  </div>
261
- )}
262
285
 
263
- {currentView === 'callgraph' && callgraphData?.data != null && (
264
- <div className="w-full">
265
- {dimensions?.width !== undefined && (
266
- <CallgraphComponent
267
- graph={callgraphData.data}
286
+ <div className="w-1/2">
287
+ {flamegraphData != null && (
288
+ <ProfileIcicleGraph
289
+ curPath={curPath}
290
+ setNewCurPath={setNewCurPath}
291
+ graph={flamegraphData.data}
268
292
  sampleUnit={sampleUnit}
269
- width={dimensions?.width}
270
293
  />
271
294
  )}
272
295
  </div>
273
- )}
274
-
275
- {currentView === 'table' && topTableData != null && (
276
- <div className="w-full">
277
- <TopTable data={topTableData.data} sampleUnit={sampleUnit} />
278
- </div>
279
- )}
280
-
281
- {currentView === 'both' && (
282
- <>
283
- <div className="w-1/2">
284
- <TopTable data={topTableData?.data} sampleUnit={sampleUnit} />
285
- </div>
286
-
287
- <div className="w-1/2">
288
- {flamegraphData != null && (
289
- <ProfileIcicleGraph
290
- curPath={curPath}
291
- setNewCurPath={setNewCurPath}
292
- graph={flamegraphData.data}
293
- sampleUnit={sampleUnit}
294
- />
295
- )}
296
- </div>
297
- </>
298
- )}
299
- </div>
300
- </Card.Body>
301
- </Card>
302
- </div>
303
- </>
296
+ </>
297
+ )}
298
+ </div>
299
+ </Card.Body>
300
+ </Card>
301
+ </div>
304
302
  );
305
303
  };
@@ -18,6 +18,7 @@ import {ProfileView, useProfileVisState} from './ProfileView';
18
18
  import {ProfileSource} from './ProfileSource';
19
19
  import {downloadPprof} from './utils';
20
20
  import {useGrpcMetadata} from '@parca/components';
21
+ import {saveAsBlob} from '@parca/functions';
21
22
 
22
23
  type NavigateFunction = (path: string, queryParams: any) => void;
23
24
 
@@ -69,10 +70,7 @@ export const ProfileViewWithData = ({
69
70
 
70
71
  try {
71
72
  const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
72
- const link = document.createElement('a');
73
- link.href = window.URL.createObjectURL(blob);
74
- link.download = 'profile.pb.gz';
75
- link.click();
73
+ saveAsBlob(blob, `profile.pb.gz`);
76
74
  } catch (error) {
77
75
  console.error('Error while querying', error);
78
76
  }
package/src/index.tsx CHANGED
@@ -15,6 +15,8 @@ import React from 'react';
15
15
  import type {Props as CallgraphProps} from '@parca/profile/src/Callgraph';
16
16
  import ProfileExplorer from './ProfileExplorer';
17
17
  import ProfileTypeSelector from './ProfileTypeSelector';
18
+ import type {FlamegraphData, TopTableData} from './ProfileView';
19
+ import {QueryServiceClient} from '@parca/client';
18
20
 
19
21
  export * from './IcicleGraph';
20
22
  export * from './ProfileIcicleGraph';
@@ -22,9 +24,27 @@ export * from './ProfileSource';
22
24
  export * from './ProfileView';
23
25
  export * from './ProfileViewWithData';
24
26
  export * from './utils';
27
+ export * from './ProfileTypeSelector';
25
28
 
26
29
  export type {CallgraphProps};
27
30
 
28
31
  const Callgraph = React.lazy(async () => await import('@parca/profile/src/Callgraph'));
29
32
 
30
33
  export {Callgraph, ProfileExplorer, ProfileTypeSelector};
34
+
35
+ // Leaving this in here due to lack of a better place to put it.
36
+ interface GrafanaParcaDataPayload {
37
+ flamegraphData: FlamegraphData;
38
+ topTableData: TopTableData;
39
+ actions: {
40
+ downloadPprof: () => void;
41
+ getQueryClient: () => QueryServiceClient;
42
+ };
43
+ error?: undefined;
44
+ }
45
+
46
+ interface GrafanaParcaErrorPayload {
47
+ error: Error;
48
+ }
49
+
50
+ export type GrafanaParcaData = GrafanaParcaErrorPayload | GrafanaParcaDataPayload;
@@ -1,6 +1,6 @@
1
1
  module.exports = {
2
2
  content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
3
- darkMode: 'class',
3
+ darkMode: ['class', '[class~="theme-dark"]'],
4
4
  theme: {
5
5
  extend: {},
6
6
  },