@perses-dev/dashboards 0.50.1 → 0.50.3

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.
@@ -21,53 +21,159 @@ Object.defineProperty(exports, "DownloadButton", {
21
21
  }
22
22
  });
23
23
  const _jsxruntime = require("react/jsx-runtime");
24
- const _react = require("react");
25
- const _DownloadOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/DownloadOutline"));
24
+ const _material = require("@mui/material");
26
25
  const _components = require("@perses-dev/components");
27
- const _constants = require("../../constants");
26
+ const _DownloadOutline = /*#__PURE__*/ _interop_require_default(require("mdi-material-ui/DownloadOutline"));
27
+ const _react = /*#__PURE__*/ _interop_require_wildcard(require("react"));
28
+ const _yaml = require("yaml");
28
29
  const _context = require("../../context");
29
30
  function _interop_require_default(obj) {
30
31
  return obj && obj.__esModule ? obj : {
31
32
  default: obj
32
33
  };
33
34
  }
34
- function DownloadButton({ heightPx }) {
35
+ function _getRequireWildcardCache(nodeInterop) {
36
+ if (typeof WeakMap !== "function") return null;
37
+ var cacheBabelInterop = new WeakMap();
38
+ var cacheNodeInterop = new WeakMap();
39
+ return (_getRequireWildcardCache = function(nodeInterop) {
40
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
41
+ })(nodeInterop);
42
+ }
43
+ function _interop_require_wildcard(obj, nodeInterop) {
44
+ if (!nodeInterop && obj && obj.__esModule) {
45
+ return obj;
46
+ }
47
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
48
+ return {
49
+ default: obj
50
+ };
51
+ }
52
+ var cache = _getRequireWildcardCache(nodeInterop);
53
+ if (cache && cache.has(obj)) {
54
+ return cache.get(obj);
55
+ }
56
+ var newObj = {
57
+ __proto__: null
58
+ };
59
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
60
+ for(var key in obj){
61
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
62
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
63
+ if (desc && (desc.get || desc.set)) {
64
+ Object.defineProperty(newObj, key, desc);
65
+ } else {
66
+ newObj[key] = obj[key];
67
+ }
68
+ }
69
+ }
70
+ newObj.default = obj;
71
+ if (cache) {
72
+ cache.set(obj, newObj);
73
+ }
74
+ return newObj;
75
+ }
76
+ function DownloadButton() {
35
77
  const { dashboard } = (0, _context.useDashboard)();
36
78
  const hiddenLinkRef = (0, _react.useRef)(null);
37
- const height = heightPx === undefined ? undefined : `${heightPx}px`;
38
- const onDownloadButtonClick = ()=>{
39
- if (!hiddenLinkRef || !hiddenLinkRef.current) return;
40
- // Create blob URL
41
- const hiddenLinkUrl = URL.createObjectURL(new Blob([
42
- JSON.stringify(dashboard)
43
- ], {
44
- type: 'application/json'
45
- }));
46
- // Simulate click
47
- hiddenLinkRef.current.href = hiddenLinkUrl;
48
- hiddenLinkRef.current.click();
49
- // Remove blob URL (for memory management)
50
- URL.revokeObjectURL(hiddenLinkUrl);
79
+ const [anchorEl, setAnchorEl] = _react.default.useState(null);
80
+ const open = Boolean(anchorEl);
81
+ const handleClick = (event)=>{
82
+ setAnchorEl(event.currentTarget);
51
83
  };
84
+ const handleItemClick = (format, shape)=>()=>{
85
+ setAnchorEl(null);
86
+ let type, content = '';
87
+ switch(format){
88
+ case 'json':
89
+ type = 'application/json';
90
+ content = JSON.stringify(dashboard, null, 2);
91
+ break;
92
+ case 'yaml':
93
+ {
94
+ type = 'application/yaml';
95
+ if (shape === 'cr') {
96
+ const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
97
+ content = (0, _yaml.stringify)({
98
+ apiVersion: 'perses.dev/v1alpha1',
99
+ kind: 'PersesDashboard',
100
+ metadata: {
101
+ labels: {
102
+ 'app.kubernetes.io/name': 'perses-dashboard',
103
+ 'app.kubernetes.io/instance': name,
104
+ 'app.kubernetes.io/part-of': 'perses-operator'
105
+ },
106
+ name
107
+ },
108
+ namespace: dashboard.metadata.project,
109
+ spec: dashboard.spec
110
+ });
111
+ } else {
112
+ content = (0, _yaml.stringify)(dashboard);
113
+ }
114
+ }
115
+ break;
116
+ }
117
+ if (!hiddenLinkRef || !hiddenLinkRef.current) return;
118
+ // Create blob URL
119
+ const hiddenLinkUrl = URL.createObjectURL(new Blob([
120
+ content
121
+ ], {
122
+ type
123
+ }));
124
+ // Simulate click
125
+ hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;
126
+ hiddenLinkRef.current.href = hiddenLinkUrl;
127
+ hiddenLinkRef.current.click();
128
+ // Remove blob URL (for memory management)
129
+ URL.revokeObjectURL(hiddenLinkUrl);
130
+ };
52
131
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_jsxruntime.Fragment, {
53
132
  children: [
54
- /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.InfoTooltip, {
55
- description: _constants.TOOLTIP_TEXT.downloadDashboard,
56
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ToolbarIconButton, {
57
- "aria-label": _constants.TOOLTIP_TEXT.downloadDashboard,
58
- onClick: onDownloadButtonClick,
59
- sx: {
60
- height
61
- },
62
- children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_DownloadOutline.default, {})
133
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_components.ToolbarIconButton, {
134
+ id: "download-dashboard-button",
135
+ "aria-controls": open ? 'basic-menu' : undefined,
136
+ "aria-haspopup": "true",
137
+ "aria-expanded": open ? 'true' : undefined,
138
+ onClick: handleClick,
139
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_DownloadOutline.default, {})
140
+ }),
141
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.Menu, {
142
+ id: "download-dashboard-formats",
143
+ anchorEl: anchorEl,
144
+ open: open,
145
+ hideBackdrop: true,
146
+ onClose: ()=>setAnchorEl(null),
147
+ MenuListProps: {
148
+ 'aria-labelledby': 'download-dashboard-button'
149
+ },
150
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)("div", {
151
+ children: /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.ClickAwayListener, {
152
+ onClickAway: ()=>setAnchorEl(null),
153
+ children: /*#__PURE__*/ (0, _jsxruntime.jsxs)(_material.MenuList, {
154
+ children: [
155
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.MenuItem, {
156
+ onClick: handleItemClick('json'),
157
+ children: "JSON"
158
+ }),
159
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.MenuItem, {
160
+ onClick: handleItemClick('yaml'),
161
+ children: "YAML"
162
+ }),
163
+ /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.MenuItem, {
164
+ onClick: handleItemClick('yaml', 'cr'),
165
+ children: "YAML (CR)"
166
+ })
167
+ ]
168
+ })
169
+ })
63
170
  })
64
171
  }),
65
172
  /*#__PURE__*/ (0, _jsxruntime.jsx)("a", {
66
173
  ref: hiddenLinkRef,
67
174
  style: {
68
175
  display: 'none'
69
- },
70
- download: `${dashboard.metadata.name}.json`
176
+ }
71
177
  })
72
178
  ]
73
179
  });
@@ -44,6 +44,12 @@ function PanelEditorForm(props) {
44
44
  const { panelDefinition, setName, setDescription, setLinks, setQueries, setPlugin, setPanelDefinition } = (0, _usePanelEditor.usePanelEditor)(initialValues.panelDefinition);
45
45
  const { plugin } = panelDefinition.spec;
46
46
  const [isDiscardDialogOpened, setDiscardDialogOpened] = (0, _react.useState)(false);
47
+ const { panelEditorSchema } = (0, _pluginsystem.useValidationSchemas)();
48
+ const form = (0, _reacthookform.useForm)({
49
+ resolver: (0, _zod.zodResolver)(panelEditorSchema),
50
+ mode: 'onBlur',
51
+ defaultValues: initialValues
52
+ });
47
53
  // Use common plugin editor logic even though we've split the inputs up in this form
48
54
  const pluginEditor = (0, _pluginsystem.usePluginEditor)({
49
55
  pluginTypes: [
@@ -72,12 +78,6 @@ function PanelEditorForm(props) {
72
78
  });
73
79
  const titleAction = (0, _pluginsystem.getTitleAction)(initialAction, true);
74
80
  const submitText = (0, _pluginsystem.getSubmitText)(initialAction, true);
75
- const { panelEditorSchema } = (0, _pluginsystem.useValidationSchemas)();
76
- const form = (0, _reacthookform.useForm)({
77
- resolver: (0, _zod.zodResolver)(panelEditorSchema),
78
- mode: 'onBlur',
79
- defaultValues: initialValues
80
- });
81
81
  const links = (0, _reacthookform.useWatch)({
82
82
  control: form.control,
83
83
  name: 'panelDefinition.spec.links'
@@ -111,6 +111,18 @@ function PanelEditorForm(props) {
111
111
  }
112
112
  setPanelDefinition(nextPanelDef);
113
113
  };
114
+ const watchedName = (0, _reacthookform.useWatch)({
115
+ control: form.control,
116
+ name: 'panelDefinition.spec.display.name'
117
+ });
118
+ const watchedDescription = (0, _reacthookform.useWatch)({
119
+ control: form.control,
120
+ name: 'panelDefinition.spec.display.description'
121
+ });
122
+ const watchedPluginKind = (0, _reacthookform.useWatch)({
123
+ control: form.control,
124
+ name: 'panelDefinition.spec.plugin.kind'
125
+ });
114
126
  return /*#__PURE__*/ (0, _jsxruntime.jsxs)(_reacthookform.FormProvider, {
115
127
  ...form,
116
128
  children: [
@@ -169,7 +181,6 @@ function PanelEditorForm(props) {
169
181
  name: "panelDefinition.spec.display.name",
170
182
  render: ({ field, fieldState })=>{
171
183
  var _fieldState_error;
172
- var _field_value;
173
184
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
174
185
  ...field,
175
186
  required: true,
@@ -177,7 +188,7 @@ function PanelEditorForm(props) {
177
188
  label: "Name",
178
189
  error: !!fieldState.error,
179
190
  helperText: (_fieldState_error = fieldState.error) === null || _fieldState_error === void 0 ? void 0 : _fieldState_error.message,
180
- value: (_field_value = field.value) !== null && _field_value !== void 0 ? _field_value : '',
191
+ value: watchedName !== null && watchedName !== void 0 ? watchedName : '',
181
192
  onChange: (event)=>{
182
193
  field.onChange(event);
183
194
  setName(event.target.value);
@@ -224,14 +235,13 @@ function PanelEditorForm(props) {
224
235
  name: "panelDefinition.spec.display.description",
225
236
  render: ({ field, fieldState })=>{
226
237
  var _fieldState_error;
227
- var _field_value;
228
238
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
229
239
  ...field,
230
240
  fullWidth: true,
231
241
  label: "Description",
232
242
  error: !!fieldState.error,
233
243
  helperText: (_fieldState_error = fieldState.error) === null || _fieldState_error === void 0 ? void 0 : _fieldState_error.message,
234
- value: (_field_value = field.value) !== null && _field_value !== void 0 ? _field_value : '',
244
+ value: watchedDescription !== null && watchedDescription !== void 0 ? watchedDescription : '',
235
245
  onChange: (event)=>{
236
246
  field.onChange(event);
237
247
  setDescription(event.target.value);
@@ -262,7 +272,7 @@ function PanelEditorForm(props) {
262
272
  helperText: (_pluginEditor_error_message = (_pluginEditor_error = pluginEditor.error) === null || _pluginEditor_error === void 0 ? void 0 : _pluginEditor_error.message) !== null && _pluginEditor_error_message !== void 0 ? _pluginEditor_error_message : (_fieldState_error = fieldState.error) === null || _fieldState_error === void 0 ? void 0 : _fieldState_error.message,
263
273
  value: {
264
274
  type: 'Panel',
265
- kind: field.value
275
+ kind: watchedPluginKind
266
276
  },
267
277
  onChange: (event)=>{
268
278
  field.onChange(event.kind);
@@ -219,6 +219,8 @@ function ListVariable({ name, source }) {
219
219
  const { setVariableValue, setVariableLoading, setVariableOptions } = (0, _context.useVariableDefinitionActions)();
220
220
  const { selectedOptions, value, loading, options, viewOptions } = useListVariableState(definition === null || definition === void 0 ? void 0 : definition.spec, ctx.state, variablesOptionsQuery);
221
221
  const [inputWidth, setInputWidth] = (0, _react.useState)(_constants.MIN_VARIABLE_WIDTH);
222
+ // Used for multiple value variables, it will not clear variable input when selecting an option
223
+ const [inputValue, setInputValue] = (0, _react.useState)('');
222
224
  var _definition_spec_display_name;
223
225
  const title = (_definition_spec_display_name = definition === null || definition === void 0 ? void 0 : (_definition_spec_display = definition.spec.display) === null || _definition_spec_display === void 0 ? void 0 : _definition_spec_display.name) !== null && _definition_spec_display_name !== void 0 ? _definition_spec_display_name : name;
224
226
  const allowMultiple = (definition === null || definition === void 0 ? void 0 : definition.spec.allowMultiple) === true;
@@ -270,7 +272,8 @@ function ListVariable({ name, source }) {
270
272
  renderInput: (params)=>{
271
273
  return allowMultiple ? /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
272
274
  ...params,
273
- label: title
275
+ label: title,
276
+ onChange: (e)=>setInputValue(e.target.value)
274
277
  }) : /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.TextField, {
275
278
  ...params,
276
279
  label: title,
@@ -295,11 +298,17 @@ function ListVariable({ name, source }) {
295
298
  setVariableValue(name, variableOptionToVariableValue(value), source);
296
299
  }
297
300
  },
301
+ inputValue: allowMultiple ? inputValue : undefined,
298
302
  onInputChange: (_, newInputValue)=>{
299
303
  if (!allowMultiple) {
300
304
  setInputWidth(getWidthPx(newInputValue, 'list'));
301
305
  }
302
306
  },
307
+ onBlur: ()=>{
308
+ if (allowMultiple) {
309
+ setInputValue('');
310
+ }
311
+ },
303
312
  options: viewOptions
304
313
  }),
305
314
  loading && /*#__PURE__*/ (0, _jsxruntime.jsx)(_material.LinearProgress, {})
@@ -32,7 +32,6 @@ const TOOLTIP_TEXT = {
32
32
  // Toolbar buttons
33
33
  addPanel: 'Add panel',
34
34
  addGroup: 'Add panel group',
35
- downloadDashboard: 'Download JSON',
36
35
  editDatasources: 'Edit datasources',
37
36
  editJson: 'Edit JSON',
38
37
  editVariables: 'Edit variables',
@@ -52,6 +52,7 @@ const _zustand = require("zustand");
52
52
  const _traditional = require("zustand/traditional");
53
53
  const _immer = require("zustand/middleware/immer");
54
54
  const _middleware = require("zustand/middleware");
55
+ const _shallow = require("zustand/shallow");
55
56
  const _immer1 = /*#__PURE__*/ _interop_require_default(require("immer"));
56
57
  const _pluginsystem = require("@perses-dev/plugin-system");
57
58
  const _core = require("@perses-dev/core");
@@ -126,7 +127,7 @@ function useVariableDefinitionAndState(name, source) {
126
127
  }
127
128
  function useVariableDefinitionActions() {
128
129
  const store = useVariableDefinitionStoreCtx();
129
- return (0, _zustand.useStore)(store, (s)=>{
130
+ return (0, _traditional.useStoreWithEqualityFn)(store, (s)=>{
130
131
  return {
131
132
  setVariableValue: s.setVariableValue,
132
133
  setVariableLoading: s.setVariableLoading,
@@ -135,7 +136,7 @@ function useVariableDefinitionActions() {
135
136
  setVariableDefaultValues: s.setVariableDefaultValues,
136
137
  getSavedVariablesStatus: s.getSavedVariablesStatus
137
138
  };
138
- });
139
+ }, _shallow.shallow);
139
140
  }
140
141
  function useVariableDefinitions() {
141
142
  const store = useVariableDefinitionStoreCtx();
@@ -372,10 +373,10 @@ function createVariableDefinitionStore({ initialVariableDefinitions = [], extern
372
373
  return store; // TODO: @Gladorme check if we can avoid this cast
373
374
  }
374
375
  function VariableProvider({ children, initialVariableDefinitions = [], externalVariableDefinitions = [], builtinVariableDefinitions = [] }) {
375
- const [store] = (0, _react.useState)(createVariableDefinitionStore({
376
- initialVariableDefinitions,
377
- externalVariableDefinitions
378
- }));
376
+ const [store] = (0, _react.useState)(()=>createVariableDefinitionStore({
377
+ initialVariableDefinitions,
378
+ externalVariableDefinitions
379
+ }));
379
380
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(VariableDefinitionStoreContext.Provider, {
380
381
  value: store,
381
382
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(PluginProvider, {
@@ -387,11 +388,11 @@ function VariableProvider({ children, initialVariableDefinitions = [], externalV
387
388
  function VariableProviderWithQueryParams({ children, initialVariableDefinitions = [], externalVariableDefinitions = [], builtinVariableDefinitions: builtinVariables = [] }) {
388
389
  const allVariableDefs = (0, _utils.mergeVariableDefinitions)(initialVariableDefinitions, externalVariableDefinitions);
389
390
  const queryParams = (0, _queryparams.useVariableQueryParams)(allVariableDefs);
390
- const [store] = (0, _react.useState)(createVariableDefinitionStore({
391
- initialVariableDefinitions,
392
- externalVariableDefinitions,
393
- queryParams
394
- }));
391
+ const [store] = (0, _react.useState)(()=>createVariableDefinitionStore({
392
+ initialVariableDefinitions,
393
+ externalVariableDefinitions,
394
+ queryParams
395
+ }));
395
396
  return /*#__PURE__*/ (0, _jsxruntime.jsx)(VariableDefinitionStoreContext.Provider, {
396
397
  value: store,
397
398
  children: /*#__PURE__*/ (0, _jsxruntime.jsx)(PluginProvider, {
@@ -1,7 +1,3 @@
1
1
  import { ReactElement } from 'react';
2
- interface DownloadButtonProps {
3
- heightPx?: number;
4
- }
5
- export declare function DownloadButton({ heightPx }: DownloadButtonProps): ReactElement;
6
- export {};
2
+ export declare function DownloadButton(): ReactElement;
7
3
  //# sourceMappingURL=DownloadButton.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAU,MAAM,OAAO,CAAC;AAM7C,UAAU,mBAAmB;IAE3B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,wBAAgB,cAAc,CAAC,EAAE,QAAQ,EAAE,EAAE,mBAAmB,GAAG,YAAY,CAiC9E"}
1
+ {"version":3,"file":"DownloadButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"names":[],"mappings":"AAgBA,OAAc,EAAE,YAAY,EAAU,MAAM,OAAO,CAAC;AAKpD,wBAAgB,cAAc,IAAI,YAAY,CAgG7C"}
@@ -11,49 +11,114 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
14
- import { useRef } from 'react';
14
+ import { ClickAwayListener, Menu, MenuItem, MenuList } from '@mui/material';
15
+ import { ToolbarIconButton } from '@perses-dev/components';
15
16
  import DownloadIcon from 'mdi-material-ui/DownloadOutline';
16
- import { InfoTooltip, ToolbarIconButton } from '@perses-dev/components';
17
- import { TOOLTIP_TEXT } from '../../constants';
17
+ import React, { useRef } from 'react';
18
+ import { stringify } from 'yaml';
18
19
  import { useDashboard } from '../../context';
19
20
  // Button that enables downloading the dashboard as a JSON file
20
- export function DownloadButton({ heightPx }) {
21
+ export function DownloadButton() {
21
22
  const { dashboard } = useDashboard();
22
23
  const hiddenLinkRef = useRef(null);
23
- const height = heightPx === undefined ? undefined : `${heightPx}px`;
24
- const onDownloadButtonClick = ()=>{
25
- if (!hiddenLinkRef || !hiddenLinkRef.current) return;
26
- // Create blob URL
27
- const hiddenLinkUrl = URL.createObjectURL(new Blob([
28
- JSON.stringify(dashboard)
29
- ], {
30
- type: 'application/json'
31
- }));
32
- // Simulate click
33
- hiddenLinkRef.current.href = hiddenLinkUrl;
34
- hiddenLinkRef.current.click();
35
- // Remove blob URL (for memory management)
36
- URL.revokeObjectURL(hiddenLinkUrl);
24
+ const [anchorEl, setAnchorEl] = React.useState(null);
25
+ const open = Boolean(anchorEl);
26
+ const handleClick = (event)=>{
27
+ setAnchorEl(event.currentTarget);
37
28
  };
29
+ const handleItemClick = (format, shape)=>()=>{
30
+ setAnchorEl(null);
31
+ let type, content = '';
32
+ switch(format){
33
+ case 'json':
34
+ type = 'application/json';
35
+ content = JSON.stringify(dashboard, null, 2);
36
+ break;
37
+ case 'yaml':
38
+ {
39
+ type = 'application/yaml';
40
+ if (shape === 'cr') {
41
+ const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
42
+ content = stringify({
43
+ apiVersion: 'perses.dev/v1alpha1',
44
+ kind: 'PersesDashboard',
45
+ metadata: {
46
+ labels: {
47
+ 'app.kubernetes.io/name': 'perses-dashboard',
48
+ 'app.kubernetes.io/instance': name,
49
+ 'app.kubernetes.io/part-of': 'perses-operator'
50
+ },
51
+ name
52
+ },
53
+ namespace: dashboard.metadata.project,
54
+ spec: dashboard.spec
55
+ });
56
+ } else {
57
+ content = stringify(dashboard);
58
+ }
59
+ }
60
+ break;
61
+ }
62
+ if (!hiddenLinkRef || !hiddenLinkRef.current) return;
63
+ // Create blob URL
64
+ const hiddenLinkUrl = URL.createObjectURL(new Blob([
65
+ content
66
+ ], {
67
+ type
68
+ }));
69
+ // Simulate click
70
+ hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;
71
+ hiddenLinkRef.current.href = hiddenLinkUrl;
72
+ hiddenLinkRef.current.click();
73
+ // Remove blob URL (for memory management)
74
+ URL.revokeObjectURL(hiddenLinkUrl);
75
+ };
38
76
  return /*#__PURE__*/ _jsxs(_Fragment, {
39
77
  children: [
40
- /*#__PURE__*/ _jsx(InfoTooltip, {
41
- description: TOOLTIP_TEXT.downloadDashboard,
42
- children: /*#__PURE__*/ _jsx(ToolbarIconButton, {
43
- "aria-label": TOOLTIP_TEXT.downloadDashboard,
44
- onClick: onDownloadButtonClick,
45
- sx: {
46
- height
47
- },
48
- children: /*#__PURE__*/ _jsx(DownloadIcon, {})
78
+ /*#__PURE__*/ _jsx(ToolbarIconButton, {
79
+ id: "download-dashboard-button",
80
+ "aria-controls": open ? 'basic-menu' : undefined,
81
+ "aria-haspopup": "true",
82
+ "aria-expanded": open ? 'true' : undefined,
83
+ onClick: handleClick,
84
+ children: /*#__PURE__*/ _jsx(DownloadIcon, {})
85
+ }),
86
+ /*#__PURE__*/ _jsx(Menu, {
87
+ id: "download-dashboard-formats",
88
+ anchorEl: anchorEl,
89
+ open: open,
90
+ hideBackdrop: true,
91
+ onClose: ()=>setAnchorEl(null),
92
+ MenuListProps: {
93
+ 'aria-labelledby': 'download-dashboard-button'
94
+ },
95
+ children: /*#__PURE__*/ _jsx("div", {
96
+ children: /*#__PURE__*/ _jsx(ClickAwayListener, {
97
+ onClickAway: ()=>setAnchorEl(null),
98
+ children: /*#__PURE__*/ _jsxs(MenuList, {
99
+ children: [
100
+ /*#__PURE__*/ _jsx(MenuItem, {
101
+ onClick: handleItemClick('json'),
102
+ children: "JSON"
103
+ }),
104
+ /*#__PURE__*/ _jsx(MenuItem, {
105
+ onClick: handleItemClick('yaml'),
106
+ children: "YAML"
107
+ }),
108
+ /*#__PURE__*/ _jsx(MenuItem, {
109
+ onClick: handleItemClick('yaml', 'cr'),
110
+ children: "YAML (CR)"
111
+ })
112
+ ]
113
+ })
114
+ })
49
115
  })
50
116
  }),
51
117
  /*#__PURE__*/ _jsx("a", {
52
118
  ref: hiddenLinkRef,
53
119
  style: {
54
120
  display: 'none'
55
- },
56
- download: `${dashboard.metadata.name}.json`
121
+ }
57
122
  })
58
123
  ]
59
124
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ReactElement, useRef } from 'react';\nimport DownloadIcon from 'mdi-material-ui/DownloadOutline';\nimport { InfoTooltip, ToolbarIconButton } from '@perses-dev/components';\nimport { TOOLTIP_TEXT } from '../../constants';\nimport { useDashboard } from '../../context';\n\ninterface DownloadButtonProps {\n // The button look best at heights >= 28 pixels\n heightPx?: number;\n}\n\n// Button that enables downloading the dashboard as a JSON file\nexport function DownloadButton({ heightPx }: DownloadButtonProps): ReactElement {\n const { dashboard } = useDashboard();\n const hiddenLinkRef = useRef<HTMLAnchorElement>(null);\n const height = heightPx === undefined ? undefined : `${heightPx}px`;\n\n const onDownloadButtonClick = (): void => {\n if (!hiddenLinkRef || !hiddenLinkRef.current) return;\n // Create blob URL\n const hiddenLinkUrl = URL.createObjectURL(\n new Blob([JSON.stringify(dashboard)], {\n type: 'application/json',\n })\n );\n // Simulate click\n hiddenLinkRef.current.href = hiddenLinkUrl;\n hiddenLinkRef.current.click();\n // Remove blob URL (for memory management)\n URL.revokeObjectURL(hiddenLinkUrl);\n };\n\n return (\n <>\n <InfoTooltip description={TOOLTIP_TEXT.downloadDashboard}>\n <ToolbarIconButton aria-label={TOOLTIP_TEXT.downloadDashboard} onClick={onDownloadButtonClick} sx={{ height }}>\n <DownloadIcon />\n </ToolbarIconButton>\n </InfoTooltip>\n {/* Hidden link to download the dashboard as a JSON file */}\n {/* eslint-disable jsx-a11y/anchor-has-content */}\n {/* eslint-disable jsx-a11y/anchor-is-valid */}\n <a ref={hiddenLinkRef} style={{ display: 'none' }} download={`${dashboard.metadata.name}.json`} />\n </>\n );\n}\n"],"names":["useRef","DownloadIcon","InfoTooltip","ToolbarIconButton","TOOLTIP_TEXT","useDashboard","DownloadButton","heightPx","dashboard","hiddenLinkRef","height","undefined","onDownloadButtonClick","current","hiddenLinkUrl","URL","createObjectURL","Blob","JSON","stringify","type","href","click","revokeObjectURL","description","downloadDashboard","aria-label","onClick","sx","a","ref","style","display","download","metadata","name"],"mappings":"AAAA,oCAAoC;AACpC,kEAAkE;AAClE,mEAAmE;AACnE,0CAA0C;AAC1C,EAAE;AACF,6CAA6C;AAC7C,EAAE;AACF,sEAAsE;AACtE,oEAAoE;AACpE,2EAA2E;AAC3E,sEAAsE;AACtE,iCAAiC;;AAEjC,SAAuBA,MAAM,QAAQ,QAAQ;AAC7C,OAAOC,kBAAkB,kCAAkC;AAC3D,SAASC,WAAW,EAAEC,iBAAiB,QAAQ,yBAAyB;AACxE,SAASC,YAAY,QAAQ,kBAAkB;AAC/C,SAASC,YAAY,QAAQ,gBAAgB;AAO7C,+DAA+D;AAC/D,OAAO,SAASC,eAAe,EAAEC,QAAQ,EAAuB;IAC9D,MAAM,EAAEC,SAAS,EAAE,GAAGH;IACtB,MAAMI,gBAAgBT,OAA0B;IAChD,MAAMU,SAASH,aAAaI,YAAYA,YAAY,CAAC,EAAEJ,SAAS,EAAE,CAAC;IAEnE,MAAMK,wBAAwB;QAC5B,IAAI,CAACH,iBAAiB,CAACA,cAAcI,OAAO,EAAE;QAC9C,kBAAkB;QAClB,MAAMC,gBAAgBC,IAAIC,eAAe,CACvC,IAAIC,KAAK;YAACC,KAAKC,SAAS,CAACX;SAAW,EAAE;YACpCY,MAAM;QACR;QAEF,iBAAiB;QACjBX,cAAcI,OAAO,CAACQ,IAAI,GAAGP;QAC7BL,cAAcI,OAAO,CAACS,KAAK;QAC3B,0CAA0C;QAC1CP,IAAIQ,eAAe,CAACT;IACtB;IAEA,qBACE;;0BACE,KAACZ;gBAAYsB,aAAapB,aAAaqB,iBAAiB;0BACtD,cAAA,KAACtB;oBAAkBuB,cAAYtB,aAAaqB,iBAAiB;oBAAEE,SAASf;oBAAuBgB,IAAI;wBAAElB;oBAAO;8BAC1G,cAAA,KAACT;;;0BAML,KAAC4B;gBAAEC,KAAKrB;gBAAesB,OAAO;oBAAEC,SAAS;gBAAO;gBAAGC,UAAU,CAAC,EAAEzB,UAAU0B,QAAQ,CAACC,IAAI,CAAC,KAAK,CAAC;;;;AAGpG"}
1
+ {"version":3,"sources":["../../../src/components/DownloadButton/DownloadButton.tsx"],"sourcesContent":["// Copyright 2023 The Perses Authors\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nimport { ClickAwayListener, Menu, MenuItem, MenuList } from '@mui/material';\nimport { ToolbarIconButton } from '@perses-dev/components';\nimport DownloadIcon from 'mdi-material-ui/DownloadOutline';\nimport React, { ReactElement, useRef } from 'react';\nimport { stringify } from 'yaml';\nimport { useDashboard } from '../../context';\n\n// Button that enables downloading the dashboard as a JSON file\nexport function DownloadButton(): ReactElement {\n const { dashboard } = useDashboard();\n const hiddenLinkRef = useRef<HTMLAnchorElement>(null);\n const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);\n const open = Boolean(anchorEl);\n const handleClick = (event: React.MouseEvent<HTMLButtonElement>): void => {\n setAnchorEl(event.currentTarget);\n };\n const handleItemClick = (format: 'json' | 'yaml', shape?: 'cr') => (): void => {\n setAnchorEl(null);\n\n let type,\n content = '';\n\n switch (format) {\n case 'json':\n type = 'application/json';\n content = JSON.stringify(dashboard, null, 2);\n break;\n case 'yaml':\n {\n type = 'application/yaml';\n\n if (shape === 'cr') {\n const name = dashboard.metadata.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');\n content = stringify({\n apiVersion: 'perses.dev/v1alpha1',\n kind: 'PersesDashboard',\n metadata: {\n labels: {\n 'app.kubernetes.io/name': 'perses-dashboard',\n 'app.kubernetes.io/instance': name,\n 'app.kubernetes.io/part-of': 'perses-operator',\n },\n name,\n },\n namespace: dashboard.metadata.project,\n spec: dashboard.spec,\n });\n } else {\n content = stringify(dashboard);\n }\n }\n break;\n }\n\n if (!hiddenLinkRef || !hiddenLinkRef.current) return;\n // Create blob URL\n const hiddenLinkUrl = URL.createObjectURL(new Blob([content], { type }));\n // Simulate click\n hiddenLinkRef.current.download = `${dashboard.metadata.name}${shape === 'cr' ? '-cr' : ''}.${format}`;\n hiddenLinkRef.current.href = hiddenLinkUrl;\n hiddenLinkRef.current.click();\n // Remove blob URL (for memory management)\n URL.revokeObjectURL(hiddenLinkUrl);\n };\n\n return (\n <>\n <ToolbarIconButton\n id=\"download-dashboard-button\"\n aria-controls={open ? 'basic-menu' : undefined}\n aria-haspopup=\"true\"\n aria-expanded={open ? 'true' : undefined}\n onClick={handleClick}\n >\n <DownloadIcon />\n </ToolbarIconButton>\n\n <Menu\n id=\"download-dashboard-formats\"\n anchorEl={anchorEl}\n open={open}\n hideBackdrop={true}\n onClose={() => setAnchorEl(null)}\n MenuListProps={{\n 'aria-labelledby': 'download-dashboard-button',\n }}\n >\n <div>\n <ClickAwayListener onClickAway={() => setAnchorEl(null)}>\n <MenuList>\n <MenuItem onClick={handleItemClick('json')}>JSON</MenuItem>\n <MenuItem onClick={handleItemClick('yaml')}>YAML</MenuItem>\n <MenuItem onClick={handleItemClick('yaml', 'cr')}>YAML (CR)</MenuItem>\n </MenuList>\n </ClickAwayListener>\n </div>\n </Menu>\n\n {/* Hidden link to download the dashboard as a JSON or YAML file */}\n {/* eslint-disable jsx-a11y/anchor-has-content */}\n {/* eslint-disable jsx-a11y/anchor-is-valid */}\n <a ref={hiddenLinkRef} style={{ display: 'none' }} />\n </>\n );\n}\n"],"names":["ClickAwayListener","Menu","MenuItem","MenuList","ToolbarIconButton","DownloadIcon","React","useRef","stringify","useDashboard","DownloadButton","dashboard","hiddenLinkRef","anchorEl","setAnchorEl","useState","open","Boolean","handleClick","event","currentTarget","handleItemClick","format","shape","type","content","JSON","name","metadata","toLowerCase","replace","apiVersion","kind","labels","namespace","project","spec","current","hiddenLinkUrl","URL","createObjectURL","Blob","download","href","click","revokeObjectURL","id","aria-controls","undefined","aria-haspopup","aria-expanded","onClick","hideBackdrop","onClose","MenuListProps","div","onClickAway","a","ref","style","display"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"PanelEditorForm.d.ts","sourceRoot":"","sources":["../../../src/components/PanelDrawer/PanelEditorForm.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAuB,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAmB,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgB9E,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,iBAAiB,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CA0NzE;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC"}
1
+ {"version":3,"file":"PanelEditorForm.d.ts","sourceRoot":"","sources":["../../../src/components/PanelDrawer/PanelEditorForm.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAE,YAAY,EAAuB,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,MAAM,EAAmB,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAgB9E,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,iBAAiB,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,YAAY,CA8NzE;AAED;;GAEG;AACH,eAAO,MAAM,iBAAiB,sBAAsB,CAAC"}