import { css } from '@emotion/css'; import React, { useState } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; import { SceneComponentProps, SceneObjectBase, sceneUtils } from '@grafana/scenes'; import { Dashboard } from '@grafana/schema'; import { Alert, Box, Button, CodeEditor, Stack, useStyles2 } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import { Trans } from 'app/core/internationalization'; import { getPrettyJSON } from 'app/features/inspector/utils/utils'; import { DashboardDTO, SaveDashboardResponseDTO } from 'app/types'; import { NameAlreadyExistsError, isNameExistsError, isPluginDashboardError, isVersionMismatchError, } from '../saving/shared'; import { useSaveDashboard } from '../saving/useSaveDashboard'; import { DashboardScene } from '../scene/DashboardScene'; import { NavToolbarActions } from '../scene/NavToolbarActions'; import { transformSaveModelToScene } from '../serialization/transformSaveModelToScene'; import { transformSceneToSaveModel } from '../serialization/transformSceneToSaveModel'; import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; export interface JsonModelEditViewState extends DashboardEditViewState { jsonText: string; } export class JsonModelEditView extends SceneObjectBase implements DashboardEditView { constructor(state: Omit) { super({ ...state, jsonText: '', }); this.addActivationHandler(() => this.setState({ jsonText: this.getJsonText() })); } public getUrlKey(): string { return 'json-model'; } public getDashboard(): DashboardScene { return getDashboardSceneFor(this); } public getSaveModel(): Dashboard { const dashboard = this.getDashboard(); return transformSceneToSaveModel(dashboard); } public getJsonText(): string { const jsonData = this.getSaveModel(); return getPrettyJSON(jsonData); } public onCodeEditorBlur = (value: string) => { this.setState({ jsonText: value }); }; public onSaveSuccess = (result: SaveDashboardResponseDTO) => { const jsonModel = JSON.parse(this.state.jsonText); const dashboard = this.getDashboard(); jsonModel.version = result.version; const rsp: DashboardDTO = { dashboard: jsonModel, meta: dashboard.state.meta, }; const newDashboardScene = transformSaveModelToScene(rsp); const newState = sceneUtils.cloneSceneObjectState(newDashboardScene.state); dashboard.pauseTrackingChanges(); dashboard.setInitialSaveModel(rsp.dashboard); dashboard.setState(newState); this.setState({ jsonText: this.getJsonText() }); }; static Component = ({ model }: SceneComponentProps) => { const { state, onSaveDashboard } = useSaveDashboard(false); const [isSaving, setIsSaving] = useState(false); const dashboard = model.getDashboard(); 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, JSON.parse(model.state.jsonText), { folderUid: dashboard.state.meta.folderUid, overwrite, }); setIsSaving(true); if (result.status === 'success') { model.onSaveSuccess(result); setIsSaving(false); } else { setIsSaving(true); } }; const saveButton = (overwrite: boolean) => ( ); const cancelButton = ( ); const styles = useStyles2(getStyles); function renderSaveButtonAndError(error?: Error) { if (error && isSaving) { if (isVersionMismatchError(error)) { return (

Would you still like to save this dashboard?

{cancelButton} {saveButton(true)}
); } if (isNameExistsError(error)) { return ; } if (isPluginDashboardError(error)) { return (

Your changes will be lost when you update the plugin. Use Save As to create custom version.

{saveButton(true)}
); } } return ( <> {error && isSaving && (

{error.message}

)} {saveButton(false)} ); } return (
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, panel settings, layout, queries, and so on. {canSave && {renderSaveButtonAndError(state.error)}}
); }; } const getStyles = (theme: GrafanaTheme2) => ({ wrapper: css({ display: 'flex', height: '100%', flexDirection: 'column', gap: theme.spacing(2), }), codeEditor: css({ flexGrow: 1, }), });