mirror of
https://github.com/SigNoz/signoz.git
synced 2025-12-17 15:36:48 +00:00
266 lines
7.2 KiB
TypeScript
266 lines
7.2 KiB
TypeScript
import './importJSON.styles.scss';
|
|
|
|
import { red } from '@ant-design/colors';
|
|
import { ExclamationCircleTwoTone } from '@ant-design/icons';
|
|
import MEditor, { Monaco } from '@monaco-editor/react';
|
|
import { Color } from '@signozhq/design-tokens';
|
|
import {
|
|
Button,
|
|
Flex,
|
|
Modal,
|
|
Space,
|
|
Typography,
|
|
Upload,
|
|
UploadProps,
|
|
} from 'antd';
|
|
import logEvent from 'api/common/logEvent';
|
|
import createDashboard from 'api/v1/dashboards/create';
|
|
import ROUTES from 'constants/routes';
|
|
import { useIsDarkMode } from 'hooks/useDarkMode';
|
|
import { useNotifications } from 'hooks/useNotifications';
|
|
import { useSafeNavigate } from 'hooks/useSafeNavigate';
|
|
import { getUpdatedLayout } from 'lib/dashboard/getUpdatedLayout';
|
|
import { ExternalLink, Github, MonitorDot, MoveRight, X } from 'lucide-react';
|
|
import { useErrorModal } from 'providers/ErrorModalProvider';
|
|
// #TODO: Lucide will be removing brand icons like GitHub in the future. In that case, we can use Simple Icons. https://simpleicons.org/
|
|
// See more: https://github.com/lucide-icons/lucide/issues/94
|
|
import { useState } from 'react';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { generatePath } from 'react-router-dom';
|
|
import { DashboardData } from 'types/api/dashboard/getAll';
|
|
import APIError from 'types/api/error';
|
|
|
|
function ImportJSON({
|
|
isImportJSONModalVisible,
|
|
uploadedGrafana,
|
|
onModalHandler,
|
|
}: ImportJSONProps): JSX.Element {
|
|
const { safeNavigate } = useSafeNavigate();
|
|
const [jsonData, setJsonData] = useState<Record<string, unknown>>();
|
|
const { t } = useTranslation(['dashboard', 'common']);
|
|
const [isUploadJSONError, setIsUploadJSONError] = useState<boolean>(false);
|
|
const [isCreateDashboardError, setIsCreateDashboardError] = useState<boolean>(
|
|
false,
|
|
);
|
|
|
|
const [dashboardCreating, setDashboardCreating] = useState<boolean>(false);
|
|
|
|
const [editorValue, setEditorValue] = useState<string>('');
|
|
|
|
const { notifications } = useNotifications();
|
|
|
|
const onChangeHandler: UploadProps['onChange'] = (info) => {
|
|
const { fileList } = info;
|
|
const reader = new FileReader();
|
|
|
|
const lastFile = fileList[fileList.length - 1];
|
|
|
|
if (lastFile.originFileObj) {
|
|
reader.onload = async (event): Promise<void> => {
|
|
if (event.target) {
|
|
const target = event.target.result;
|
|
try {
|
|
if (target) {
|
|
const targetFile = target.toString();
|
|
const parsedValue = JSON.parse(targetFile);
|
|
setJsonData(parsedValue);
|
|
setEditorValue(JSON.stringify(parsedValue, null, 2));
|
|
setIsUploadJSONError(false);
|
|
}
|
|
} catch (error) {
|
|
setIsUploadJSONError(true);
|
|
}
|
|
}
|
|
};
|
|
reader.readAsText(lastFile.originFileObj);
|
|
}
|
|
};
|
|
|
|
const { showErrorModal } = useErrorModal();
|
|
|
|
const onClickLoadJsonHandler = async (): Promise<void> => {
|
|
try {
|
|
setDashboardCreating(true);
|
|
logEvent('Dashboard List: Import and next clicked', {});
|
|
|
|
const dashboardData = JSON.parse(editorValue) as DashboardData;
|
|
|
|
if (dashboardData?.layout) {
|
|
dashboardData.layout = getUpdatedLayout(dashboardData.layout);
|
|
} else {
|
|
dashboardData.layout = [];
|
|
}
|
|
|
|
const response = await createDashboard({
|
|
...dashboardData,
|
|
uploadedGrafana,
|
|
});
|
|
|
|
safeNavigate(
|
|
generatePath(ROUTES.DASHBOARD, {
|
|
dashboardId: response.data.id,
|
|
}),
|
|
);
|
|
logEvent('Dashboard List: New dashboard imported successfully', {
|
|
dashboardId: response.data?.id,
|
|
dashboardName: response.data?.data?.title,
|
|
});
|
|
|
|
setDashboardCreating(false);
|
|
} catch (error) {
|
|
showErrorModal(error as APIError);
|
|
setDashboardCreating(false);
|
|
setIsCreateDashboardError(true);
|
|
notifications.error({
|
|
message: error instanceof Error ? error.message : t('error_loading_json'),
|
|
});
|
|
}
|
|
};
|
|
|
|
const getErrorNode = (error: string): JSX.Element => (
|
|
<Space>
|
|
<ExclamationCircleTwoTone twoToneColor={[red[7], '#1f1f1f']} />
|
|
<Typography style={{ color: '#D89614' }}>{error}</Typography>
|
|
</Space>
|
|
);
|
|
|
|
const onCancelHandler = (): void => {
|
|
setIsUploadJSONError(false);
|
|
setIsCreateDashboardError(false);
|
|
onModalHandler();
|
|
};
|
|
|
|
const isDarkMode = useIsDarkMode();
|
|
|
|
function setEditorTheme(monaco: Monaco): void {
|
|
monaco.editor.defineTheme('my-theme', {
|
|
base: 'vs-dark',
|
|
inherit: true,
|
|
rules: [
|
|
{ token: 'string.key.json', foreground: Color.BG_VANILLA_400 },
|
|
{ token: 'string.value.json', foreground: Color.BG_ROBIN_400 },
|
|
],
|
|
colors: {
|
|
'editor.background': Color.BG_INK_300,
|
|
},
|
|
});
|
|
}
|
|
|
|
return (
|
|
<Modal
|
|
wrapClassName="import-json-modal"
|
|
open={isImportJSONModalVisible}
|
|
centered
|
|
closable={false}
|
|
destroyOnClose
|
|
width="60vw"
|
|
footer={
|
|
<div className="import-json-modal-footer">
|
|
{isCreateDashboardError && (
|
|
<div className="create-dashboard-json-error">
|
|
{getErrorNode(t('error_loading_json'))}
|
|
</div>
|
|
)}
|
|
|
|
{isUploadJSONError && (
|
|
<div className="create-dashboard-json-error">
|
|
{getErrorNode(t('error_upload_json'))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="action-btns-container">
|
|
<Flex gap="small">
|
|
<Upload
|
|
accept=".json"
|
|
showUploadList={false}
|
|
multiple={false}
|
|
onChange={onChangeHandler}
|
|
beforeUpload={(): boolean => false}
|
|
action="none"
|
|
data={jsonData}
|
|
>
|
|
<Button
|
|
type="default"
|
|
className="periscope-btn"
|
|
icon={<MonitorDot size={14} />}
|
|
onClick={(): void => {
|
|
logEvent('Dashboard List: Upload JSON file clicked', {});
|
|
}}
|
|
>
|
|
{' '}
|
|
{t('upload_json_file')}
|
|
</Button>
|
|
</Upload>
|
|
<a
|
|
href="https://signoz.io/docs/dashboards/dashboard-templates/overview/"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
>
|
|
<Button
|
|
type="default"
|
|
className="periscope-btn"
|
|
icon={<Github size={14} />}
|
|
>
|
|
{t('view_template')}
|
|
<ExternalLink size={14} />
|
|
</Button>
|
|
</a>
|
|
</Flex>
|
|
|
|
<Button
|
|
// disabled={editorValue.length === 0}
|
|
onClick={onClickLoadJsonHandler}
|
|
loading={dashboardCreating}
|
|
className="periscope-btn primary"
|
|
type="primary"
|
|
>
|
|
{t('import_and_next')} <MoveRight size={14} />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
}
|
|
>
|
|
<div className="import-json-content-container">
|
|
<div className="import-json-content-header">
|
|
<Typography.Text>{t('import_json')}</Typography.Text>
|
|
|
|
<X size={14} className="periscope-btn ghost" onClick={onCancelHandler} />
|
|
</div>
|
|
|
|
<MEditor
|
|
language="json"
|
|
height="40vh"
|
|
onChange={(newValue): void => setEditorValue(newValue || '')}
|
|
value={editorValue}
|
|
options={{
|
|
scrollbar: {
|
|
alwaysConsumeMouseWheel: false,
|
|
},
|
|
minimap: {
|
|
enabled: false,
|
|
},
|
|
fontSize: 14,
|
|
fontFamily: 'Space Mono',
|
|
}}
|
|
theme={isDarkMode ? 'my-theme' : 'light'}
|
|
onMount={(_, monaco): void => {
|
|
document.fonts.ready.then(() => {
|
|
monaco.editor.remeasureFonts();
|
|
});
|
|
}}
|
|
// eslint-disable-next-line react/jsx-no-bind
|
|
beforeMount={setEditorTheme}
|
|
/>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
interface ImportJSONProps {
|
|
isImportJSONModalVisible: boolean;
|
|
onModalHandler: VoidFunction;
|
|
uploadedGrafana: boolean;
|
|
}
|
|
|
|
export default ImportJSON;
|