+
+
+
+
+
+ setImportSettings(!importSettings)}
+ radius='md'
+ my='sm'
+ >
+
+
+ Import {Object.keys(filteredSettings).length} settings
+
+
+
+ );
+}
diff --git a/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4UserChoose.tsx b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4UserChoose.tsx
new file mode 100644
index 00000000..3f37cd05
--- /dev/null
+++ b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4UserChoose.tsx
@@ -0,0 +1,59 @@
+import { Export4 } from '@/lib/import/version4/validateExport';
+import { Avatar, Box, Group, Radio, Stack, Text } from '@mantine/core';
+
+export default function Export4UserChoose({
+ export4,
+ setImportFrom,
+ importFrom,
+}: {
+ export4: Export4;
+ setImportFrom: (importFrom: string) => void;
+ importFrom: string;
+}) {
+ return (
+
+ Select a user to import data from into the current user.
+
+ This option allows you to import data from a user in your export into the currently logged-in user,
+ even if both have the same username. Normally, the system skips importing users with usernames that
+ already exist in the system.
However, if you've just set up your instance
+ and reused the same username as your old instance, this option enables you to merge data from that
+ user into your logged-in account without needing to delete or replace it.{' '}
+ It is recommended to select a user with super-administrator permissions for this operation.
+
+
+ setImportFrom(value)} name='importFrom'>
+ {export4.data.users.map((user, i) => (
+
+
+
+ {user.avatar && }
+
+
+ {user.username} ({user.id})
+ {' '}
+ {user.role === 'SUPERADMIN' && (
+
+ Super Administrator
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+ Do not merge data{' '}
+
+ Select this option if you do not want to merge data from any user into the current user.
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4WarningSameInstance.tsx b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4WarningSameInstance.tsx
new file mode 100644
index 00000000..b690f540
--- /dev/null
+++ b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/Export4WarningSameInstance.tsx
@@ -0,0 +1,51 @@
+import { Export4 } from '@/lib/import/version4/validateExport';
+import { useUserStore } from '@/lib/store/user';
+import { Box, Checkbox, Group, Text } from '@mantine/core';
+
+export function detectSameInstance(export4?: Export4 | null, currentUserId?: string) {
+ if (!export4) return false;
+ if (!currentUserId) return false;
+
+ const idInExport = export4.data.users.find((user) => user.id === currentUserId);
+ return !!idInExport;
+}
+
+export default function Export4WarningSameInstance({
+ export4,
+ sameInstanceAgree,
+ setSameInstanceAgree,
+}: {
+ export4: Export4;
+ sameInstanceAgree: boolean;
+ setSameInstanceAgree: (sameInstanceAgree: boolean) => void;
+}) {
+ const currentUserId = useUserStore((state) => state.user?.id);
+ const isSameInstance = detectSameInstance(export4, currentUserId);
+
+ if (!isSameInstance) return null;
+
+ return (
+
+
+ Same Instance Detected
+
+
+ Detected that you are importing data from the same instance as the current running one. Proceeding
+ with this import may lead to data conflicts or overwriting existing data. Please ensure that you
+ understand the implications before continuing.
+
+
+ setSameInstanceAgree(!sameInstanceAgree)}
+ radius='md'
+ my='sm'
+ >
+
+
+ I agree, and understand the implications.
+
+
+
+ );
+}
diff --git a/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/index.tsx b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/index.tsx
index bfe0b903..f6f7c758 100644
--- a/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/index.tsx
+++ b/src/components/pages/settings/parts/SettingsServerUtil/ImportExportButton/ImportV4Button/index.tsx
@@ -1,14 +1,27 @@
import { Export4, validateExport } from '@/lib/import/version4/validateExport';
-import { Button, FileButton, Modal, Pill } from '@mantine/core';
-import { showNotification } from '@mantine/notifications';
+import { Button, FileButton, Modal, Pill, Text } from '@mantine/core';
+import { showNotification, updateNotification } from '@mantine/notifications';
import { IconDatabaseImport, IconDatabaseOff, IconUpload, IconX } from '@tabler/icons-react';
import { useEffect, useState } from 'react';
import Export4Details from './Export4Details';
+import Export4ImportSettings from './Export4ImportSettings';
+import Export4WarningSameInstance, { detectSameInstance } from './Export4WarningSameInstance';
+import Export4UserChoose from './Export4UserChoose';
+import { useUserStore } from '@/lib/store/user';
+import { modals } from '@mantine/modals';
+import { fetchApi } from '@/lib/fetchApi';
+import { Response } from '@/lib/api/response';
export default function ImportV4Button() {
const [open, setOpen] = useState(false);
const [file, setFile] = useState(null);
const [export4, setExport4] = useState(null);
+ const [importSettings, setImportSettings] = useState(true);
+ const [sameInstanceAgree, setSameInstanceAgree] = useState(false);
+ const [importFrom, setImportFrom] = useState('');
+
+ const currentUserId = useUserStore((state) => state.user?.id);
+ const isSameInstance = detectSameInstance(export4, currentUserId);
const onContent = (content: string) => {
if (!content) return console.error('no content');
@@ -39,6 +52,118 @@ export default function ImportV4Button() {
setExport4(validated.data);
};
+ const handleImport = async () => {
+ if (!export4) return;
+
+ if (isSameInstance && !sameInstanceAgree) {
+ modals.openContextModal({
+ modal: 'alert',
+ title: 'Same Instance Detected',
+ innerProps: {
+ modalBody:
+ 'Detected that you are importing data from the same instance as the current running one. You must agree to the warning before proceeding with the import.',
+ },
+ });
+ return;
+ }
+
+ modals.openConfirmModal({
+ title: 'Are you sure?',
+ children:
+ 'This process will NOT overwrite existing data but will append to it. In case of conflicts, the imported data will be skipped and logged.',
+ labels: {
+ confirm: 'Yes, import data.',
+ cancel: 'Cancel',
+ },
+ onConfirm: async () => {
+ showNotification({
+ title: 'Importing data...',
+ message:
+ 'The export file will be uploaded. This amy take a few moments. The import is running in the background and is logged, so you can close this browser tab if you want.',
+ color: 'blue',
+ autoClose: 5000,
+ id: 'importing-data',
+ loading: true,
+ });
+
+ setOpen(false);
+
+ const { error, data } = await fetchApi(
+ '/api/server/import/v4',
+ 'POST',
+ {
+ export4,
+ config: {
+ settings: importSettings,
+ mergeCurrentUser: importFrom === '' ? undefined : importFrom,
+ },
+ },
+ );
+
+ if (error) {
+ updateNotification({
+ title: 'Failed to import data...',
+ message:
+ error.error ?? 'An error occurred while importing data. Check the logs for more details.',
+ color: 'red',
+ icon: ,
+ id: 'importing-data',
+ autoClose: 10000,
+ });
+ } else {
+ if (!data) return;
+
+ modals.open({
+ title: 'Import Completed.',
+ children: (
+
+ The import has been completed. To make sure files are properly viewable, make sure that you
+ have configured the datasource correctly to match your previous instance. For example, if you
+ were using local storage before, make sure to set it to the same directory (or same backed up
+ directory) as before. If you are using S3, make sure you are using the same bucket. {' '}
+
+ Additionally, it is recommended to restart Zipline to ensure all settings take full effect.
+