Chore: Define components outside of the scene class (#110180)
define components outside of the scene class
This commit is contained in:
+2
-17
@@ -1734,8 +1734,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
|
||||
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"]
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/pages/DashboardScenePage.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
@@ -1752,9 +1751,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/panel-edit/PanelOptionsPane.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
|
||||
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"],
|
||||
[0, 0, 0, "React Hook \\"useToggle\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"]
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/panel-edit/PanelVizTypePicker.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
@@ -1765,9 +1762,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"],
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "3"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx:5381": [
|
||||
[0, 0, 0, "React Hook \\"useIsProvisionedNG\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/saving/SaveDashboardForm.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"]
|
||||
],
|
||||
@@ -1866,15 +1860,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/dashboard-scene/serialization/transformToV1TypesUtils.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx:5381": [
|
||||
[0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/JsonModelEditView.tsx:5381": [
|
||||
[0, 0, 0, "React Hook \\"useDashboardEditPageNav\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "0"],
|
||||
[0, 0, 0, "React Hook \\"useSaveDashboard\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "1"],
|
||||
[0, 0, 0, "React Hook \\"useState\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "2"],
|
||||
[0, 0, 0, "React Hook \\"useStyles2\\" cannot be called in a class component. React Hooks must be called in a React function component or a custom React Hook function.", "3"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/settings/annotations/AnnotationSettingsEdit.tsx:5381": [
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"],
|
||||
[0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"],
|
||||
|
||||
@@ -166,48 +166,50 @@ export class InspectJsonTab extends SceneObjectBase<InspectJsonTabState> {
|
||||
return dashboard.state.meta.canEdit;
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<InspectJsonTab>) => {
|
||||
const { source: show, jsonText } = model.useState();
|
||||
const styles = useStyles2(getPanelInspectorStyles2);
|
||||
const options = model.getOptions();
|
||||
static Component = InspectJsonTabComponent;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.toolbar} data-testid={selectors.components.PanelInspector.Json.content}>
|
||||
<Field label={t('dashboard.inspect-json.select-source', 'Select source')} className="flex-grow-1">
|
||||
<Select
|
||||
inputId="select-source-dropdown"
|
||||
options={options}
|
||||
value={options.find((v) => v.value === show) ?? options[0].value}
|
||||
onChange={model.onChangeSource}
|
||||
/>
|
||||
</Field>
|
||||
{model.isEditable() && (
|
||||
<Button className={styles.toolbarItem} onClick={model.onApplyChange}>
|
||||
<Trans i18nKey="dashboard-scene.inspect-json-tab.apply">Apply</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
function InspectJsonTabComponent({ model }: SceneComponentProps<InspectJsonTab>) {
|
||||
const { source: show, jsonText } = model.useState();
|
||||
const styles = useStyles2(getPanelInspectorStyles2);
|
||||
const options = model.getOptions();
|
||||
|
||||
<div className={styles.content}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
height={height}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={jsonText.length > 100}
|
||||
value={jsonText}
|
||||
readOnly={!model.isEditable()}
|
||||
onBlur={model.onCodeEditorBlur}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.toolbar} data-testid={selectors.components.PanelInspector.Json.content}>
|
||||
<Field label={t('dashboard.inspect-json.select-source', 'Select source')} className="flex-grow-1">
|
||||
<Select
|
||||
inputId="select-source-dropdown"
|
||||
options={options}
|
||||
value={options.find((v) => v.value === show) ?? options[0].value}
|
||||
onChange={model.onChangeSource}
|
||||
/>
|
||||
</Field>
|
||||
{model.isEditable() && (
|
||||
<Button className={styles.toolbarItem} onClick={model.onApplyChange}>
|
||||
<Trans i18nKey="dashboard-scene.inspect-json-tab.apply">Apply</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
<div className={styles.content}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
height={height}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={jsonText.length > 100}
|
||||
value={jsonText}
|
||||
readOnly={!model.isEditable()}
|
||||
onBlur={model.onCodeEditorBlur}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function getJsonText(show: ShowContent, panel: VizPanel): string {
|
||||
|
||||
@@ -119,78 +119,80 @@ export class PanelOptionsPane extends SceneObjectBase<PanelOptionsPaneState> {
|
||||
];
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<PanelOptionsPane>) => {
|
||||
const { isVizPickerOpen, searchQuery, listMode, panelRef } = model.useState();
|
||||
const panel = panelRef.resolve();
|
||||
const { pluginId } = panel.useState();
|
||||
const { data } = sceneGraph.getData(panel).useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const isSearching = searchQuery.length > 0;
|
||||
const hasFieldConfig = !isSearching && !panel.getPlugin()?.fieldConfigRegistry.isEmpty();
|
||||
const [isSearchingOptions, setIsSearchingOptions] = useToggle(false);
|
||||
const onlyOverrides = listMode === OptionFilter.Overrides;
|
||||
static Component = PanelOptionsPaneComponent;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{!isVizPickerOpen && (
|
||||
<>
|
||||
<div className={styles.top}>
|
||||
<Field
|
||||
label={t('dashboard.panel-edit.visualization-button-label', 'Visualization')}
|
||||
className={styles.vizField}
|
||||
>
|
||||
<Stack gap={1}>
|
||||
<VisualizationButton pluginId={pluginId} onOpen={model.onToggleVizPicker} />
|
||||
<Button
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
onClick={setIsSearchingOptions}
|
||||
tooltip={t('dashboard.panel-edit.visualization-button-tooltip', 'Search options')}
|
||||
/>
|
||||
{hasFieldConfig && (
|
||||
<ToolbarButton
|
||||
icon="filter"
|
||||
tooltip={t('dashboard.panel-edit.only-overrides-button-tooltip', 'Show only overrides')}
|
||||
variant={onlyOverrides ? 'active' : 'canvas'}
|
||||
onClick={() => {
|
||||
model.onSetListMode(onlyOverrides ? OptionFilter.All : OptionFilter.Overrides);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Field>
|
||||
function PanelOptionsPaneComponent({ model }: SceneComponentProps<PanelOptionsPane>) {
|
||||
const { isVizPickerOpen, searchQuery, listMode, panelRef } = model.useState();
|
||||
const panel = panelRef.resolve();
|
||||
const { pluginId } = panel.useState();
|
||||
const { data } = sceneGraph.getData(panel).useState();
|
||||
const styles = useStyles2(getStyles);
|
||||
const isSearching = searchQuery.length > 0;
|
||||
const hasFieldConfig = !isSearching && !panel.getPlugin()?.fieldConfigRegistry.isEmpty();
|
||||
const [isSearchingOptions, setIsSearchingOptions] = useToggle(false);
|
||||
const onlyOverrides = listMode === OptionFilter.Overrides;
|
||||
|
||||
{isSearchingOptions && (
|
||||
<FilterInput
|
||||
className={styles.searchOptions}
|
||||
value={searchQuery}
|
||||
placeholder={t('dashboard.panel-edit.placeholder-search-options', 'Search options')}
|
||||
onChange={model.onSetSearchQuery}
|
||||
autoFocus={true}
|
||||
onBlur={() => {
|
||||
if (searchQuery.length === 0) {
|
||||
setIsSearchingOptions(false);
|
||||
}
|
||||
}}
|
||||
return (
|
||||
<>
|
||||
{!isVizPickerOpen && (
|
||||
<>
|
||||
<div className={styles.top}>
|
||||
<Field
|
||||
label={t('dashboard.panel-edit.visualization-button-label', 'Visualization')}
|
||||
className={styles.vizField}
|
||||
>
|
||||
<Stack gap={1}>
|
||||
<VisualizationButton pluginId={pluginId} onOpen={model.onToggleVizPicker} />
|
||||
<Button
|
||||
icon="search"
|
||||
variant="secondary"
|
||||
onClick={setIsSearchingOptions}
|
||||
tooltip={t('dashboard.panel-edit.visualization-button-tooltip', 'Search options')}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ScrollContainer>
|
||||
<PanelOptions panel={panel} searchQuery={searchQuery} listMode={listMode} data={data} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
)}
|
||||
{isVizPickerOpen && (
|
||||
<PanelVizTypePicker
|
||||
panel={panel}
|
||||
onChange={model.onChangePanelPlugin}
|
||||
onClose={model.onToggleVizPicker}
|
||||
data={data}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
{hasFieldConfig && (
|
||||
<ToolbarButton
|
||||
icon="filter"
|
||||
tooltip={t('dashboard.panel-edit.only-overrides-button-tooltip', 'Show only overrides')}
|
||||
variant={onlyOverrides ? 'active' : 'canvas'}
|
||||
onClick={() => {
|
||||
model.onSetListMode(onlyOverrides ? OptionFilter.All : OptionFilter.Overrides);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Field>
|
||||
|
||||
{isSearchingOptions && (
|
||||
<FilterInput
|
||||
className={styles.searchOptions}
|
||||
value={searchQuery}
|
||||
placeholder={t('dashboard.panel-edit.placeholder-search-options', 'Search options')}
|
||||
onChange={model.onSetSearchQuery}
|
||||
autoFocus={true}
|
||||
onBlur={() => {
|
||||
if (searchQuery.length === 0) {
|
||||
setIsSearchingOptions(false);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ScrollContainer>
|
||||
<PanelOptions panel={panel} searchQuery={searchQuery} listMode={listMode} data={data} />
|
||||
</ScrollContainer>
|
||||
</>
|
||||
)}
|
||||
{isVizPickerOpen && (
|
||||
<PanelVizTypePicker
|
||||
panel={panel}
|
||||
onChange={model.onChangePanelPlugin}
|
||||
onClose={model.onToggleVizPicker}
|
||||
data={data}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
|
||||
@@ -45,80 +45,80 @@ export class SaveDashboardDrawer extends SceneObjectBase<SaveDashboardDrawerStat
|
||||
this.setState({ saveRefresh: !this.state.saveRefresh });
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<SaveDashboardDrawer>) => {
|
||||
const { showDiff, saveAsCopy, saveTimeRange, saveVariables, saveRefresh } = model.useState();
|
||||
static Component = SaveDashboardDrawerComponent;
|
||||
}
|
||||
|
||||
const changeInfo = model.state.dashboardRef
|
||||
.resolve()
|
||||
.getDashboardChanges(saveTimeRange, saveVariables, saveRefresh);
|
||||
function SaveDashboardDrawerComponent({ model }: SceneComponentProps<SaveDashboardDrawer>) {
|
||||
const { showDiff, saveAsCopy, saveTimeRange, saveVariables, saveRefresh } = model.useState();
|
||||
|
||||
const { changedSaveModel, initialSaveModel, diffs, diffCount, hasFolderChanges, hasMigratedToV2 } = changeInfo;
|
||||
const changesCount = diffCount + (hasFolderChanges ? 1 : 0);
|
||||
const dashboard = model.state.dashboardRef.resolve();
|
||||
const { meta } = dashboard.useState();
|
||||
const { provisioned: isProvisioned, folderTitle } = meta;
|
||||
const managedResourceCannotBeEdited = dashboard.managedResourceCannotBeEdited();
|
||||
const isProvisionedNG = useIsProvisionedNG(dashboard);
|
||||
const changeInfo = model.state.dashboardRef.resolve().getDashboardChanges(saveTimeRange, saveVariables, saveRefresh);
|
||||
|
||||
const tabs = (
|
||||
<TabsBar>
|
||||
const { changedSaveModel, initialSaveModel, diffs, diffCount, hasFolderChanges, hasMigratedToV2 } = changeInfo;
|
||||
const changesCount = diffCount + (hasFolderChanges ? 1 : 0);
|
||||
const dashboard = model.state.dashboardRef.resolve();
|
||||
const { meta } = dashboard.useState();
|
||||
const { provisioned: isProvisioned, folderTitle } = meta;
|
||||
const managedResourceCannotBeEdited = dashboard.managedResourceCannotBeEdited();
|
||||
const isProvisionedNG = useIsProvisionedNG(dashboard);
|
||||
|
||||
const tabs = (
|
||||
<TabsBar>
|
||||
<Tab
|
||||
label={t('dashboard-scene.save-dashboard-drawer.tabs.label-details', 'Details')}
|
||||
active={!showDiff}
|
||||
onChangeTab={() => model.setState({ showDiff: false })}
|
||||
/>
|
||||
{changesCount > 0 && !managedResourceCannotBeEdited && (
|
||||
<Tab
|
||||
label={t('dashboard-scene.save-dashboard-drawer.tabs.label-details', 'Details')}
|
||||
active={!showDiff}
|
||||
onChangeTab={() => model.setState({ showDiff: false })}
|
||||
label={t('dashboard-scene.save-dashboard-drawer.tabs.label-changes', 'Changes')}
|
||||
active={showDiff}
|
||||
onChangeTab={() => model.setState({ showDiff: true })}
|
||||
counter={changesCount}
|
||||
/>
|
||||
{changesCount > 0 && !managedResourceCannotBeEdited && (
|
||||
<Tab
|
||||
label={t('dashboard-scene.save-dashboard-drawer.tabs.label-changes', 'Changes')}
|
||||
active={showDiff}
|
||||
onChangeTab={() => model.setState({ showDiff: true })}
|
||||
counter={changesCount}
|
||||
/>
|
||||
)}
|
||||
</TabsBar>
|
||||
);
|
||||
)}
|
||||
</TabsBar>
|
||||
);
|
||||
|
||||
let title = t('dashboard-scene.save-dashboard-drawer.tabs.title', 'Save dashboard');
|
||||
if (saveAsCopy) {
|
||||
title = t('dashboard-scene.save-dashboard-drawer.tabs.title-copy', 'Save dashboard copy');
|
||||
} else if (isProvisioned || isProvisionedNG) {
|
||||
title = t('dashboard-scene.save-dashboard-drawer.tabs.title-provisioned', 'Provisioned dashboard');
|
||||
let title = t('dashboard-scene.save-dashboard-drawer.tabs.title', 'Save dashboard');
|
||||
if (saveAsCopy) {
|
||||
title = t('dashboard-scene.save-dashboard-drawer.tabs.title-copy', 'Save dashboard copy');
|
||||
} else if (isProvisioned || isProvisionedNG) {
|
||||
title = t('dashboard-scene.save-dashboard-drawer.tabs.title-provisioned', 'Provisioned dashboard');
|
||||
}
|
||||
|
||||
const renderBody = () => {
|
||||
if (showDiff) {
|
||||
return (
|
||||
<SaveDashboardDiff
|
||||
diff={diffs}
|
||||
oldValue={initialSaveModel}
|
||||
newValue={changedSaveModel}
|
||||
hasFolderChanges={hasFolderChanges}
|
||||
hasMigratedToV2={hasMigratedToV2}
|
||||
oldFolder={dashboard.getInitialState()?.meta.folderTitle}
|
||||
newFolder={folderTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const renderBody = () => {
|
||||
if (showDiff) {
|
||||
return (
|
||||
<SaveDashboardDiff
|
||||
diff={diffs}
|
||||
oldValue={initialSaveModel}
|
||||
newValue={changedSaveModel}
|
||||
hasFolderChanges={hasFolderChanges}
|
||||
hasMigratedToV2={hasMigratedToV2}
|
||||
oldFolder={dashboard.getInitialState()?.meta.folderTitle}
|
||||
newFolder={folderTitle}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isProvisionedNG) {
|
||||
return <SaveProvisionedDashboard dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
}
|
||||
|
||||
if (isProvisionedNG) {
|
||||
return <SaveProvisionedDashboard dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
}
|
||||
if (saveAsCopy || changeInfo.isNew) {
|
||||
return <SaveDashboardAsForm dashboard={dashboard} changeInfo={changeInfo} />;
|
||||
}
|
||||
|
||||
if (saveAsCopy || changeInfo.isNew) {
|
||||
return <SaveDashboardAsForm dashboard={dashboard} changeInfo={changeInfo} />;
|
||||
}
|
||||
if (isProvisioned || managedResourceCannotBeEdited) {
|
||||
return <SaveProvisionedDashboardForm dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
}
|
||||
|
||||
if (isProvisioned || managedResourceCannotBeEdited) {
|
||||
return <SaveProvisionedDashboardForm dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
}
|
||||
|
||||
return <SaveDashboardForm dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer title={title} subtitle={dashboard.state.title} onClose={model.onClose} tabs={tabs}>
|
||||
{renderBody()}
|
||||
</Drawer>
|
||||
);
|
||||
return <SaveDashboardForm dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
|
||||
};
|
||||
|
||||
return (
|
||||
<Drawer title={title} subtitle={dashboard.state.title} onClose={model.onClose} tabs={tabs}>
|
||||
{renderBody()}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ export class GeneralSettingsEditView
|
||||
});
|
||||
};
|
||||
|
||||
private onMoveSuccess = (folderUID: string, folderTitle: string) => {
|
||||
public onMoveSuccess = (folderUID: string, folderTitle: string) => {
|
||||
const newMeta = {
|
||||
...this._dashboard.state.meta,
|
||||
folderUid: folderUID,
|
||||
@@ -192,181 +192,176 @@ export class GeneralSettingsEditView
|
||||
this.onMoveModalDismiss();
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<GeneralSettingsEditView>) => {
|
||||
const dashboard = model.getDashboard();
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const { title, description, tags, meta, editable } = dashboard.useState();
|
||||
const { showMoveModal, moveModalProps } = model.useState();
|
||||
const { sync: graphTooltip } = model.getCursorSync()?.useState() || {};
|
||||
const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState();
|
||||
const { intervals } = model.getRefreshPicker().useState();
|
||||
const { hideTimeControls } = model.getDashboardControls().useState();
|
||||
const { enabled: liveNow } = model.getLiveNowTimer().useState();
|
||||
const EDITABLE_OPTIONS = [
|
||||
{
|
||||
label: t('dashboard-scene.general-settings-edit-view.editable_options.label.editable', 'Editable'),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.general-settings-edit-view.editable_options.label.readonly', 'Read-only'),
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
static Component = GeneralSettingsEditViewComponent;
|
||||
}
|
||||
|
||||
const GRAPH_TOOLTIP_OPTIONS = [
|
||||
{
|
||||
value: 0,
|
||||
label: t('dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.default', 'Default'),
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: t(
|
||||
'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-crosshair',
|
||||
'Shared crosshair'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: t(
|
||||
'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-tooltip',
|
||||
'Shared tooltip'
|
||||
),
|
||||
},
|
||||
];
|
||||
function GeneralSettingsEditViewComponent({ model }: SceneComponentProps<GeneralSettingsEditView>) {
|
||||
const dashboard = model.getDashboard();
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const { title, description, tags, meta, editable } = dashboard.useState();
|
||||
const { showMoveModal, moveModalProps } = model.useState();
|
||||
const { sync: graphTooltip } = model.getCursorSync()?.useState() || {};
|
||||
const { timeZone, weekStart, UNSAFE_nowDelay: nowDelay } = model.getTimeRange().useState();
|
||||
const { intervals } = model.getRefreshPicker().useState();
|
||||
const { hideTimeControls } = model.getDashboardControls().useState();
|
||||
const { enabled: liveNow } = model.getLiveNowTimer().useState();
|
||||
const EDITABLE_OPTIONS = [
|
||||
{
|
||||
label: t('dashboard-scene.general-settings-edit-view.editable_options.label.editable', 'Editable'),
|
||||
value: true,
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.general-settings-edit-view.editable_options.label.readonly', 'Read-only'),
|
||||
value: false,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
<Box display="flex" direction="column" gap={2} marginBottom={5}>
|
||||
const GRAPH_TOOLTIP_OPTIONS = [
|
||||
{
|
||||
value: 0,
|
||||
label: t('dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.default', 'Default'),
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: t(
|
||||
'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-crosshair',
|
||||
'Shared crosshair'
|
||||
),
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: t(
|
||||
'dashboard-scene.general-settings-edit-view.graph_tooltip_options.label.shared-tooltip',
|
||||
'Shared tooltip'
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
<Box display="flex" direction="column" gap={2} marginBottom={5}>
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Stack justifyContent="space-between">
|
||||
<Label htmlFor="title-input">
|
||||
<Trans i18nKey="dashboard-settings.general.title-label">Title</Trans>
|
||||
</Label>
|
||||
{config.featureToggles.dashgpt && (
|
||||
<GenAIDashTitleButton onGenerate={(title) => model.onTitleChange(title)} />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="title-input"
|
||||
name="title"
|
||||
value={title}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => model.onTitleChange(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Stack justifyContent="space-between">
|
||||
<Label htmlFor="description-input">
|
||||
{t('dashboard-settings.general.description-label', 'Description')}
|
||||
</Label>
|
||||
{config.featureToggles.dashgpt && (
|
||||
<GenAIDashDescriptionButton onGenerate={(description) => model.onDescriptionChange(description)} />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<TextArea
|
||||
id="description-input"
|
||||
name="description"
|
||||
value={description}
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => model.onDescriptionChange(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field noMargin label={t('dashboard-settings.general.tags-label', 'Tags')}>
|
||||
<TagsInput id="tags-input" tags={tags} onChange={model.onTagsChange} width={40} />
|
||||
</Field>
|
||||
<Field noMargin label={t('dashboard-settings.general.folder-label', 'Folder')}>
|
||||
<FolderPicker
|
||||
value={meta.folderUid}
|
||||
onChange={dashboard.isManagedRepository() ? model.onProvisionedFolderChange : model.onFolderChange}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-settings.general.editable-label', 'Editable')}
|
||||
description={t(
|
||||
'dashboard-settings.general.editable-description',
|
||||
'Set to read-only to disable all editing. Reload the dashboard for changes to take effect'
|
||||
)}
|
||||
>
|
||||
<RadioButtonGroup value={editable} options={EDITABLE_OPTIONS} onChange={model.onEditableChange} />
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<TimePickerSettings
|
||||
onTimeZoneChange={model.onTimeZoneChange}
|
||||
onWeekStartChange={model.onWeekStartChange}
|
||||
onRefreshIntervalChange={model.onRefreshIntervalChange}
|
||||
onNowDelayChange={model.onNowDelayChange}
|
||||
onHideTimePickerChange={model.onHideTimePickerChange}
|
||||
onLiveNowChange={model.onLiveNowChange}
|
||||
refreshIntervals={intervals}
|
||||
timePickerHidden={hideTimeControls}
|
||||
nowDelay={nowDelay || ''}
|
||||
liveNow={liveNow}
|
||||
timezone={timeZone || ''}
|
||||
weekStart={weekStart}
|
||||
/>
|
||||
|
||||
{/* @todo: Update "Graph tooltip" description to remove prompt about reloading when resolving #46581 */}
|
||||
<CollapsableSection label={t('dashboard-settings.general.panel-options-label', 'Panel options')} isOpen={true}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Stack justifyContent="space-between">
|
||||
<Label htmlFor="title-input">
|
||||
<Trans i18nKey="dashboard-settings.general.title-label">Title</Trans>
|
||||
</Label>
|
||||
{config.featureToggles.dashgpt && (
|
||||
<GenAIDashTitleButton onGenerate={(title) => model.onTitleChange(title)} />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
id="title-input"
|
||||
name="title"
|
||||
value={title}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) => model.onTitleChange(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field
|
||||
noMargin
|
||||
label={
|
||||
<Stack justifyContent="space-between">
|
||||
<Label htmlFor="description-input">
|
||||
{t('dashboard-settings.general.description-label', 'Description')}
|
||||
</Label>
|
||||
{config.featureToggles.dashgpt && (
|
||||
<GenAIDashDescriptionButton onGenerate={(description) => model.onDescriptionChange(description)} />
|
||||
)}
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<TextArea
|
||||
id="description-input"
|
||||
name="description"
|
||||
value={description}
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => model.onDescriptionChange(e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Field noMargin label={t('dashboard-settings.general.tags-label', 'Tags')}>
|
||||
<TagsInput id="tags-input" tags={tags} onChange={model.onTagsChange} width={40} />
|
||||
</Field>
|
||||
<Field noMargin label={t('dashboard-settings.general.folder-label', 'Folder')}>
|
||||
<FolderPicker
|
||||
value={meta.folderUid}
|
||||
onChange={dashboard.isManagedRepository() ? model.onProvisionedFolderChange : model.onFolderChange}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-settings.general.editable-label', 'Editable')}
|
||||
label={t('dashboard-settings.general.panel-options-graph-tooltip-label', 'Graph tooltip')}
|
||||
description={t(
|
||||
'dashboard-settings.general.editable-description',
|
||||
'Set to read-only to disable all editing. Reload the dashboard for changes to take effect'
|
||||
'dashboard-settings.general.panel-options-graph-tooltip-description',
|
||||
'Controls tooltip and hover highlight behavior across different panels. Reload the dashboard for changes to take effect'
|
||||
)}
|
||||
>
|
||||
<RadioButtonGroup value={editable} options={EDITABLE_OPTIONS} onChange={model.onEditableChange} />
|
||||
<RadioButtonGroup onChange={model.onTooltipChange} options={GRAPH_TOOLTIP_OPTIONS} value={graphTooltip} />
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
<TimePickerSettings
|
||||
onTimeZoneChange={model.onTimeZoneChange}
|
||||
onWeekStartChange={model.onWeekStartChange}
|
||||
onRefreshIntervalChange={model.onRefreshIntervalChange}
|
||||
onNowDelayChange={model.onNowDelayChange}
|
||||
onHideTimePickerChange={model.onHideTimePickerChange}
|
||||
onLiveNowChange={model.onLiveNowChange}
|
||||
refreshIntervals={intervals}
|
||||
timePickerHidden={hideTimeControls}
|
||||
nowDelay={nowDelay || ''}
|
||||
liveNow={liveNow}
|
||||
timezone={timeZone || ''}
|
||||
weekStart={weekStart}
|
||||
/>
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-settings.general.panels-preload-label', 'Preload panels')}
|
||||
description={t(
|
||||
'dashboard-settings.general.panels-preload-description',
|
||||
'When enabled all panels will start loading as soon as the dashboard has been loaded.'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
id="preload-panels-dashboards-toggle"
|
||||
value={dashboard.state.preload}
|
||||
onChange={(e) => model.onPreloadChange(e.currentTarget.checked)}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
</CollapsableSection>
|
||||
|
||||
{/* @todo: Update "Graph tooltip" description to remove prompt about reloading when resolving #46581 */}
|
||||
<CollapsableSection
|
||||
label={t('dashboard-settings.general.panel-options-label', 'Panel options')}
|
||||
isOpen={true}
|
||||
>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-settings.general.panel-options-graph-tooltip-label', 'Graph tooltip')}
|
||||
description={t(
|
||||
'dashboard-settings.general.panel-options-graph-tooltip-description',
|
||||
'Controls tooltip and hover highlight behavior across different panels. Reload the dashboard for changes to take effect'
|
||||
)}
|
||||
>
|
||||
<RadioButtonGroup
|
||||
onChange={model.onTooltipChange}
|
||||
options={GRAPH_TOOLTIP_OPTIONS}
|
||||
value={graphTooltip}
|
||||
/>
|
||||
</Field>
|
||||
<Box marginTop={3}>{meta.canDelete && <DeleteDashboardButton dashboard={dashboard} />}</Box>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-settings.general.panels-preload-label', 'Preload panels')}
|
||||
description={t(
|
||||
'dashboard-settings.general.panels-preload-description',
|
||||
'When enabled all panels will start loading as soon as the dashboard has been loaded.'
|
||||
)}
|
||||
>
|
||||
<Switch
|
||||
id="preload-panels-dashboards-toggle"
|
||||
value={dashboard.state.preload}
|
||||
onChange={(e) => model.onPreloadChange(e.currentTarget.checked)}
|
||||
/>
|
||||
</Field>
|
||||
</Stack>
|
||||
</CollapsableSection>
|
||||
|
||||
<Box marginTop={3}>{meta.canDelete && <DeleteDashboardButton dashboard={dashboard} />}</Box>
|
||||
</div>
|
||||
|
||||
{showMoveModal && moveModalProps && (
|
||||
<MoveProvisionedDashboardDrawer
|
||||
dashboard={dashboard}
|
||||
targetFolderUID={moveModalProps.targetFolderUID}
|
||||
targetFolderTitle={moveModalProps.targetFolderTitle}
|
||||
onDismiss={model.onMoveModalDismiss}
|
||||
onSuccess={model.onMoveSuccess}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
{showMoveModal && moveModalProps && (
|
||||
<MoveProvisionedDashboardDrawer
|
||||
dashboard={dashboard}
|
||||
targetFolderUID={moveModalProps.targetFolderUID}
|
||||
targetFolderTitle={moveModalProps.targetFolderTitle}
|
||||
onDismiss={model.onMoveModalDismiss}
|
||||
onSuccess={model.onMoveSuccess}
|
||||
/>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,148 +97,150 @@ export class JsonModelEditView extends SceneObjectBase<JsonModelEditViewState> i
|
||||
dashboard.resumeTrackingChanges();
|
||||
};
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<JsonModelEditView>) => {
|
||||
const { state, onSaveDashboard } = useSaveDashboard(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
static Component = JsonModelEditViewComponent;
|
||||
}
|
||||
|
||||
const dashboard = model.getDashboard();
|
||||
function JsonModelEditViewComponent({ model }: SceneComponentProps<JsonModelEditView>) {
|
||||
const { state, onSaveDashboard } = useSaveDashboard(false);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const canSave = dashboard.useState().meta.canSave;
|
||||
const { jsonText } = model.useState();
|
||||
const dashboard = model.getDashboard();
|
||||
|
||||
const onSave = async (overwrite: boolean) => {
|
||||
const result = await onSaveDashboard(dashboard, {
|
||||
folderUid: dashboard.state.meta.folderUid,
|
||||
overwrite,
|
||||
rawDashboardJSON: JSON.parse(model.state.jsonText),
|
||||
k8s: dashboard.state.meta.k8s,
|
||||
});
|
||||
const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey());
|
||||
const canSave = dashboard.useState().meta.canSave;
|
||||
const { jsonText } = model.useState();
|
||||
|
||||
const onSave = async (overwrite: boolean) => {
|
||||
const result = await onSaveDashboard(dashboard, {
|
||||
folderUid: dashboard.state.meta.folderUid,
|
||||
overwrite,
|
||||
rawDashboardJSON: JSON.parse(model.state.jsonText),
|
||||
k8s: dashboard.state.meta.k8s,
|
||||
});
|
||||
|
||||
setIsSaving(true);
|
||||
if (result.status === 'success') {
|
||||
await model.onSaveSuccess(result);
|
||||
setIsSaving(false);
|
||||
} else {
|
||||
setIsSaving(true);
|
||||
if (result.status === 'success') {
|
||||
await model.onSaveSuccess(result);
|
||||
setIsSaving(false);
|
||||
} else {
|
||||
setIsSaving(true);
|
||||
}
|
||||
};
|
||||
|
||||
const saveButton = (overwrite: boolean) => (
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSave(overwrite);
|
||||
}}
|
||||
variant={overwrite ? 'destructive' : 'primary'}
|
||||
>
|
||||
{overwrite ? (
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.save-and-overwrite">'Save and overwrite'</Trans>
|
||||
) : (
|
||||
<Trans i18nKey="dashboard-settings.json-editor.save-button">Save changes</Trans>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const cancelButton = (
|
||||
<Button variant="secondary" onClick={() => setIsSaving(false)} fill="outline">
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.cancel-button.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
function renderSaveButtonAndError(error?: Error) {
|
||||
if (error && isSaving) {
|
||||
if (isVersionMismatchError(error)) {
|
||||
return (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-someone-else-has-updated-this-dashboard',
|
||||
'Someone else has updated this dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.render-save-button-and-error.would-still-dashboard">
|
||||
Would you still like to save this dashboard?
|
||||
</Trans>
|
||||
</p>
|
||||
<Box paddingTop={2}>
|
||||
<Stack alignItems="center">
|
||||
{cancelButton}
|
||||
{saveButton(true)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (isNameExistsError(error)) {
|
||||
return <NameAlreadyExistsError saveButton={saveButton} cancelButton={cancelButton} />;
|
||||
}
|
||||
|
||||
if (isPluginDashboardError(error)) {
|
||||
return (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-plugin-dashboard',
|
||||
'Plugin dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.render-save-button-and-error.body-plugin-dashboard">
|
||||
Your changes will be lost when you update the plugin. Use <strong>Save as</strong> to create custom
|
||||
version.
|
||||
</Trans>
|
||||
</p>
|
||||
<Box paddingTop={2}>
|
||||
<Stack alignItems="center">{saveButton(true)}</Stack>
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && isSaving && (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-failed-to-save-dashboard',
|
||||
'Failed to save dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>{error.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
<Stack alignItems="center">{saveButton(false)}</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div className={styles.wrapper}>
|
||||
<Trans i18nKey="dashboard-settings.json-editor.subtitle">
|
||||
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings,
|
||||
panel settings, layout, queries, and so on.
|
||||
</Trans>
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
value={jsonText}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={true}
|
||||
containerStyles={styles.codeEditor}
|
||||
onBlur={model.onCodeEditorBlur}
|
||||
/>
|
||||
{canSave && <Box paddingTop={2}>{renderSaveButtonAndError(state.error)}</Box>}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
const saveButton = (overwrite: boolean) => (
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={() => {
|
||||
onSave(overwrite);
|
||||
}}
|
||||
variant={overwrite ? 'destructive' : 'primary'}
|
||||
>
|
||||
{overwrite ? (
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.save-and-overwrite">'Save and overwrite'</Trans>
|
||||
) : (
|
||||
<Trans i18nKey="dashboard-settings.json-editor.save-button">Save changes</Trans>
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
|
||||
const cancelButton = (
|
||||
<Button variant="secondary" onClick={() => setIsSaving(false)} fill="outline">
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.cancel-button.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
function renderSaveButtonAndError(error?: Error) {
|
||||
if (error && isSaving) {
|
||||
if (isVersionMismatchError(error)) {
|
||||
return (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-someone-else-has-updated-this-dashboard',
|
||||
'Someone else has updated this dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.render-save-button-and-error.would-still-dashboard">
|
||||
Would you still like to save this dashboard?
|
||||
</Trans>
|
||||
</p>
|
||||
<Box paddingTop={2}>
|
||||
<Stack alignItems="center">
|
||||
{cancelButton}
|
||||
{saveButton(true)}
|
||||
</Stack>
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
if (isNameExistsError(error)) {
|
||||
return <NameAlreadyExistsError saveButton={saveButton} cancelButton={cancelButton} />;
|
||||
}
|
||||
|
||||
if (isPluginDashboardError(error)) {
|
||||
return (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-plugin-dashboard',
|
||||
'Plugin dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="dashboard-scene.json-model-edit-view.render-save-button-and-error.body-plugin-dashboard">
|
||||
Your changes will be lost when you update the plugin. Use <strong>Save as</strong> to create custom
|
||||
version.
|
||||
</Trans>
|
||||
</p>
|
||||
<Box paddingTop={2}>
|
||||
<Stack alignItems="center">{saveButton(true)}</Stack>
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{error && isSaving && (
|
||||
<Alert
|
||||
title={t(
|
||||
'dashboard-scene.json-model-edit-view.render-save-button-and-error.title-failed-to-save-dashboard',
|
||||
'Failed to save dashboard'
|
||||
)}
|
||||
severity="error"
|
||||
>
|
||||
<p>{error.message}</p>
|
||||
</Alert>
|
||||
)}
|
||||
<Stack alignItems="center">{saveButton(false)}</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Page navModel={navModel} pageNav={pageNav} layout={PageLayoutType.Standard}>
|
||||
<NavToolbarActions dashboard={dashboard} />
|
||||
<div className={styles.wrapper}>
|
||||
<Trans i18nKey="dashboard-settings.json-editor.subtitle">
|
||||
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, panel
|
||||
settings, layout, queries, and so on.
|
||||
</Trans>
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
value={jsonText}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={true}
|
||||
containerStyles={styles.codeEditor}
|
||||
onBlur={model.onCodeEditorBlur}
|
||||
/>
|
||||
{canSave && <Box paddingTop={2}>{renderSaveButtonAndError(state.error)}</Box>}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
|
||||
Reference in New Issue
Block a user