mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboards: Add dashboard embed route (#69596)
* Dashboard embed: Set up route * Dashboard embed: Cleanup * Dashboard embed: Separate routes * Dashboard embed: Render dashboard page * Dashboard embed: Add toolbar * Dashboard embed: Send JSON on save * Dashboard embed: Add JSON param * Dashboard embed: Make the dashboard editable * Fix sending dashboard to remote server * Add notifications * Add "dashboardEmbed" feature toggle * Use the toggle * Update toggles * Add toggle on backend * Add get JSON endpoint * Add drawer * Close drawer on success * Update toggles * Cleanup * Update toggle * Allow embedding for the d-embed url * Allow embedding via custom X-Allow-Embedding header * Use callbackUrl * Cleanup * Update public/app/features/dashboard/containers/EmbeddedDashboardPage.tsx Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> * Use theme for spacing * Update toggles * Update public/app/features/dashboard/components/EmbeddedDashboard/SaveDashboardForm.tsx Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com> * Add select data source modal --------- Co-authored-by: kay delaney <45561153+kaydelaney@users.noreply.github.com> Co-authored-by: Polina Boneva <13227501+polibb@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { config } from '@grafana/runtime';
|
||||
import { Drawer, Tab, TabsBar } from '@grafana/ui';
|
||||
|
||||
import { DashboardModel } from '../../state';
|
||||
import DashboardValidation from '../SaveDashboard/DashboardValidation';
|
||||
import { SaveDashboardDiff } from '../SaveDashboard/SaveDashboardDiff';
|
||||
import { SaveDashboardData } from '../SaveDashboard/types';
|
||||
import { jsonDiff } from '../VersionHistory/utils';
|
||||
|
||||
import { SaveDashboardForm } from './SaveDashboardForm';
|
||||
|
||||
type SaveDashboardDrawerProps = {
|
||||
dashboard: DashboardModel;
|
||||
onDismiss: () => void;
|
||||
dashboardJson: string;
|
||||
onSave: (clone: DashboardModel) => Promise<unknown>;
|
||||
};
|
||||
|
||||
export const SaveDashboardDrawer = ({ dashboard, onDismiss, dashboardJson, onSave }: SaveDashboardDrawerProps) => {
|
||||
const data = useMemo<SaveDashboardData>(() => {
|
||||
const clone = dashboard.getSaveModelClone();
|
||||
const cloneJSON = JSON.stringify(clone, null, 2);
|
||||
const cloneSafe = JSON.parse(cloneJSON); // avoids undefined issues
|
||||
|
||||
const diff = jsonDiff(JSON.parse(JSON.stringify(dashboardJson, null, 2)), cloneSafe);
|
||||
let diffCount = 0;
|
||||
for (const d of Object.values(diff)) {
|
||||
diffCount += d.length;
|
||||
}
|
||||
|
||||
return {
|
||||
clone,
|
||||
diff,
|
||||
diffCount,
|
||||
hasChanges: diffCount > 0,
|
||||
};
|
||||
}, [dashboard, dashboardJson]);
|
||||
|
||||
const [showDiff, setShowDiff] = useState(false);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
title={'Save dashboard'}
|
||||
onClose={onDismiss}
|
||||
subtitle={dashboard.title}
|
||||
tabs={
|
||||
<TabsBar>
|
||||
<Tab label={'Details'} active={!showDiff} onChangeTab={() => setShowDiff(false)} />
|
||||
{data.hasChanges && (
|
||||
<Tab label={'Changes'} active={showDiff} onChangeTab={() => setShowDiff(true)} counter={data.diffCount} />
|
||||
)}
|
||||
</TabsBar>
|
||||
}
|
||||
scrollableContent
|
||||
>
|
||||
{showDiff ? (
|
||||
<SaveDashboardDiff diff={data.diff} oldValue={dashboardJson} newValue={data.clone} />
|
||||
) : (
|
||||
<SaveDashboardForm
|
||||
dashboard={dashboard}
|
||||
saveModel={data}
|
||||
onCancel={onDismiss}
|
||||
onSuccess={onDismiss}
|
||||
onSubmit={onSave}
|
||||
/>
|
||||
)}
|
||||
|
||||
{config.featureToggles.showDashboardValidationWarnings && <DashboardValidation dashboard={dashboard} />}
|
||||
</Drawer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { Button, Form } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
|
||||
import { DashboardModel } from '../../state';
|
||||
import { SaveDashboardData } from '../SaveDashboard/types';
|
||||
|
||||
interface SaveDashboardProps {
|
||||
dashboard: DashboardModel;
|
||||
onCancel: () => void;
|
||||
onSubmit?: (clone: DashboardModel) => Promise<unknown>;
|
||||
onSuccess: () => void;
|
||||
saveModel: SaveDashboardData;
|
||||
}
|
||||
export const SaveDashboardForm = ({ dashboard, onCancel, onSubmit, onSuccess, saveModel }: SaveDashboardProps) => {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const notifyApp = useAppNotification();
|
||||
const hasChanges = useMemo(() => dashboard.hasTimeChanged() || saveModel.hasChanges, [dashboard, saveModel]);
|
||||
|
||||
const onFormSubmit = async () => {
|
||||
if (!onSubmit) {
|
||||
return;
|
||||
}
|
||||
setSaving(true);
|
||||
onSubmit(saveModel.clone)
|
||||
.then(() => {
|
||||
notifyApp.success('Dashboard saved locally');
|
||||
onSuccess();
|
||||
})
|
||||
.catch((error) => {
|
||||
notifyApp.error(error.message || 'Error saving dashboard');
|
||||
})
|
||||
.finally(() => setSaving(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={onFormSubmit}>
|
||||
{() => {
|
||||
return (
|
||||
<Stack gap={2}>
|
||||
<Stack alignItems="center">
|
||||
<Button variant="secondary" onClick={onCancel} fill="outline">
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" disabled={!hasChanges} icon={saving ? 'fa fa-spinner' : undefined}>
|
||||
Save
|
||||
</Button>
|
||||
{!hasChanges && <div>No changes to save</div>}
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
}}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user