mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
436 lines
13 KiB
TypeScript
436 lines
13 KiB
TypeScript
import { CopyOutlined, DownOutlined, InfoCircleOutlined, UpOutlined } from '@ant-design/icons';
|
|
import { App, Button, Checkbox, Input, InputNumber, Switch, Tooltip } from 'antd';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import { IS_CLOUD } from '../../../../constants';
|
|
import { type Database, databaseApi } from '../../../../entity/databases';
|
|
import { MariadbConnectionStringParser } from '../../../../entity/databases/model/mariadb/MariadbConnectionStringParser';
|
|
import { ClipboardHelper } from '../../../../shared/lib/ClipboardHelper';
|
|
import { ToastHelper } from '../../../../shared/toast';
|
|
import { ClipboardPasteModalComponent } from '../../../../shared/ui';
|
|
|
|
interface Props {
|
|
database: Database;
|
|
|
|
isShowCancelButton?: boolean;
|
|
onCancel: () => void;
|
|
|
|
isShowBackButton: boolean;
|
|
onBack: () => void;
|
|
|
|
saveButtonText?: string;
|
|
isSaveToApi: boolean;
|
|
onSaved: (database: Database) => void;
|
|
|
|
isShowDbName?: boolean;
|
|
}
|
|
|
|
export const EditMariaDbSpecificDataComponent = ({
|
|
database,
|
|
|
|
isShowCancelButton,
|
|
onCancel,
|
|
|
|
isShowBackButton,
|
|
onBack,
|
|
|
|
saveButtonText,
|
|
isSaveToApi,
|
|
onSaved,
|
|
isShowDbName = true,
|
|
}: Props) => {
|
|
const { message } = App.useApp();
|
|
|
|
const [editingDatabase, setEditingDatabase] = useState<Database>();
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
const [isConnectionTested, setIsConnectionTested] = useState(false);
|
|
const [isTestingConnection, setIsTestingConnection] = useState(false);
|
|
const [isConnectionFailed, setIsConnectionFailed] = useState(false);
|
|
|
|
const hasAdvancedValues = !!database.mariadb?.isExcludeEvents;
|
|
const [isShowAdvanced, setShowAdvanced] = useState(hasAdvancedValues);
|
|
|
|
const [isShowPasteModal, setIsShowPasteModal] = useState(false);
|
|
|
|
const applyConnectionString = (text: string) => {
|
|
const trimmedText = text.trim();
|
|
|
|
if (!trimmedText) {
|
|
message.error('Clipboard is empty');
|
|
return;
|
|
}
|
|
|
|
const result = MariadbConnectionStringParser.parse(trimmedText);
|
|
|
|
if ('error' in result) {
|
|
message.error(result.error);
|
|
return;
|
|
}
|
|
|
|
if (!editingDatabase?.mariadb) return;
|
|
|
|
const updatedDatabase: Database = {
|
|
...editingDatabase,
|
|
mariadb: {
|
|
...editingDatabase.mariadb,
|
|
host: result.host,
|
|
port: result.port,
|
|
username: result.username,
|
|
password: result.password,
|
|
database: result.database,
|
|
isHttps: result.isHttps,
|
|
},
|
|
};
|
|
|
|
setEditingDatabase(updatedDatabase);
|
|
setIsConnectionTested(false);
|
|
message.success('Connection string parsed successfully');
|
|
};
|
|
|
|
const parseFromClipboard = async () => {
|
|
if (!ClipboardHelper.isClipboardApiAvailable()) {
|
|
setIsShowPasteModal(true);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const text = await ClipboardHelper.readFromClipboard();
|
|
applyConnectionString(text);
|
|
} catch {
|
|
message.error('Failed to read clipboard. Please check browser permissions.');
|
|
}
|
|
};
|
|
|
|
const testConnection = async () => {
|
|
if (!editingDatabase?.mariadb) return;
|
|
setIsTestingConnection(true);
|
|
setIsConnectionFailed(false);
|
|
|
|
const trimmedDatabase = {
|
|
...editingDatabase,
|
|
mariadb: {
|
|
...editingDatabase.mariadb,
|
|
password: editingDatabase.mariadb.password?.trim(),
|
|
},
|
|
};
|
|
|
|
try {
|
|
await databaseApi.testDatabaseConnectionDirect(trimmedDatabase);
|
|
setIsConnectionTested(true);
|
|
ToastHelper.showToast({
|
|
title: 'Connection test passed',
|
|
description: 'You can continue with the next step',
|
|
});
|
|
} catch (e) {
|
|
setIsConnectionFailed(true);
|
|
alert((e as Error).message);
|
|
}
|
|
|
|
setIsTestingConnection(false);
|
|
};
|
|
|
|
const saveDatabase = async () => {
|
|
if (!editingDatabase?.mariadb) return;
|
|
|
|
const trimmedDatabase = {
|
|
...editingDatabase,
|
|
mariadb: {
|
|
...editingDatabase.mariadb,
|
|
password: editingDatabase.mariadb.password?.trim(),
|
|
},
|
|
};
|
|
|
|
if (isSaveToApi) {
|
|
setIsSaving(true);
|
|
|
|
try {
|
|
await databaseApi.updateDatabase(trimmedDatabase);
|
|
} catch (e) {
|
|
alert((e as Error).message);
|
|
}
|
|
|
|
setIsSaving(false);
|
|
}
|
|
|
|
onSaved(trimmedDatabase);
|
|
};
|
|
|
|
useEffect(() => {
|
|
setIsSaving(false);
|
|
setIsConnectionTested(false);
|
|
setIsTestingConnection(false);
|
|
setIsConnectionFailed(false);
|
|
|
|
setEditingDatabase({ ...database });
|
|
}, [database]);
|
|
|
|
if (!editingDatabase) return null;
|
|
|
|
let isAllFieldsFilled = true;
|
|
if (!editingDatabase.mariadb?.host) isAllFieldsFilled = false;
|
|
if (!editingDatabase.mariadb?.port) isAllFieldsFilled = false;
|
|
if (!editingDatabase.mariadb?.username) isAllFieldsFilled = false;
|
|
if (!editingDatabase.id && !editingDatabase.mariadb?.password) isAllFieldsFilled = false;
|
|
if (!editingDatabase.mariadb?.database) isAllFieldsFilled = false;
|
|
|
|
const isLocalhostDb =
|
|
editingDatabase.mariadb?.host?.includes('localhost') ||
|
|
editingDatabase.mariadb?.host?.includes('127.0.0.1');
|
|
|
|
return (
|
|
<div>
|
|
<div className="mb-3 flex">
|
|
<div className="min-w-[150px]" />
|
|
<div
|
|
className="cursor-pointer text-sm text-gray-600 transition-colors hover:text-gray-900 dark:text-gray-400 dark:hover:text-gray-200"
|
|
onClick={parseFromClipboard}
|
|
>
|
|
<CopyOutlined className="mr-1" />
|
|
Parse from clipboard
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Host</div>
|
|
<Input
|
|
value={editingDatabase.mariadb?.host}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: {
|
|
...editingDatabase.mariadb,
|
|
host: e.target.value.trim().replace('https://', '').replace('http://', ''),
|
|
},
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
className="max-w-[200px] grow"
|
|
placeholder="Enter MariaDB host"
|
|
/>
|
|
</div>
|
|
|
|
{isLocalhostDb && !IS_CLOUD && (
|
|
<div className="mb-1 flex">
|
|
<div className="min-w-[150px]" />
|
|
<div className="max-w-[200px] text-xs text-gray-500 dark:text-gray-400">
|
|
Please{' '}
|
|
<a
|
|
href="https://databasus.com/faq/localhost"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
className="!text-blue-600 dark:!text-blue-400"
|
|
>
|
|
read this document
|
|
</a>{' '}
|
|
to study how to backup local database
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Port</div>
|
|
<InputNumber
|
|
type="number"
|
|
value={editingDatabase.mariadb?.port}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb || e === null) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: { ...editingDatabase.mariadb, port: e },
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
className="max-w-[200px] grow"
|
|
placeholder="Enter MariaDB port"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Username</div>
|
|
<Input
|
|
value={editingDatabase.mariadb?.username}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: { ...editingDatabase.mariadb, username: e.target.value.trim() },
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
className="max-w-[200px] grow"
|
|
placeholder="Enter MariaDB username"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Password</div>
|
|
<Input.Password
|
|
value={editingDatabase.mariadb?.password}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: { ...editingDatabase.mariadb, password: e.target.value },
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
className="max-w-[200px] grow"
|
|
placeholder="Enter MariaDB password"
|
|
autoComplete="off"
|
|
data-1p-ignore
|
|
data-lpignore="true"
|
|
data-form-type="other"
|
|
/>
|
|
</div>
|
|
|
|
{isShowDbName && (
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">DB name</div>
|
|
<Input
|
|
value={editingDatabase.mariadb?.database}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: { ...editingDatabase.mariadb, database: e.target.value.trim() },
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
className="max-w-[200px] grow"
|
|
placeholder="Enter MariaDB database name"
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Use HTTPS</div>
|
|
<Switch
|
|
checked={editingDatabase.mariadb?.isHttps}
|
|
onChange={(checked) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: { ...editingDatabase.mariadb, isHttps: checked },
|
|
});
|
|
setIsConnectionTested(false);
|
|
}}
|
|
size="small"
|
|
/>
|
|
</div>
|
|
|
|
<div className="mt-4 mb-1 flex items-center">
|
|
<div
|
|
className="flex cursor-pointer items-center text-sm text-blue-600 hover:text-blue-800"
|
|
onClick={() => setShowAdvanced(!isShowAdvanced)}
|
|
>
|
|
<span className="mr-2">Advanced settings</span>
|
|
|
|
{isShowAdvanced ? (
|
|
<UpOutlined style={{ fontSize: '12px' }} />
|
|
) : (
|
|
<DownOutlined style={{ fontSize: '12px' }} />
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{isShowAdvanced && (
|
|
<div className="mb-1 flex w-full items-center">
|
|
<div className="min-w-[150px]">Exclude events</div>
|
|
<div className="flex items-center">
|
|
<Checkbox
|
|
checked={editingDatabase.mariadb?.isExcludeEvents || false}
|
|
onChange={(e) => {
|
|
if (!editingDatabase.mariadb) return;
|
|
|
|
setEditingDatabase({
|
|
...editingDatabase,
|
|
mariadb: {
|
|
...editingDatabase.mariadb,
|
|
isExcludeEvents: e.target.checked,
|
|
},
|
|
});
|
|
}}
|
|
>
|
|
Skip events
|
|
</Checkbox>
|
|
|
|
<Tooltip
|
|
className="cursor-pointer"
|
|
title="Skip backing up database events. Enable this if the event scheduler is disabled on your MariaDB server."
|
|
>
|
|
<InfoCircleOutlined className="ml-2" style={{ color: 'gray' }} />
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="mt-5 flex">
|
|
{isShowCancelButton && (
|
|
<Button className="mr-1" danger ghost onClick={() => onCancel()}>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
|
|
{isShowBackButton && (
|
|
<Button className="mr-auto" type="primary" ghost onClick={() => onBack()}>
|
|
Back
|
|
</Button>
|
|
)}
|
|
|
|
{!isConnectionTested && (
|
|
<Button
|
|
type="primary"
|
|
onClick={() => testConnection()}
|
|
loading={isTestingConnection}
|
|
disabled={!isAllFieldsFilled}
|
|
className="mr-5"
|
|
>
|
|
Test connection
|
|
</Button>
|
|
)}
|
|
|
|
{isConnectionTested && (
|
|
<Button
|
|
type="primary"
|
|
onClick={() => saveDatabase()}
|
|
loading={isSaving}
|
|
disabled={!isAllFieldsFilled}
|
|
className="mr-5"
|
|
>
|
|
{saveButtonText || 'Save'}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
{isConnectionFailed && !IS_CLOUD && (
|
|
<div className="mt-3 text-sm text-gray-500 dark:text-gray-400">
|
|
If your database uses IP whitelist, make sure Databasus server IP is added to the allowed
|
|
list.
|
|
</div>
|
|
)}
|
|
|
|
<ClipboardPasteModalComponent
|
|
open={isShowPasteModal}
|
|
onSubmit={(text) => {
|
|
setIsShowPasteModal(false);
|
|
applyConnectionString(text);
|
|
}}
|
|
onCancel={() => setIsShowPasteModal(false)}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|