mirror of
https://github.com/csunny/DB-GPT.git
synced 2025-09-09 04:49:26 +00:00
feat(datasource): Database connection Renewal (#2359)
Co-authored-by: aries_ckt <916701291@qq.com> Co-authored-by: Fangyin Cheng <staneyffer@gmail.com> Co-authored-by: ‘yanzhiyonggit config --global user.email git config --global user.name ‘yanzhiyong <zhiyong.yan@clife.cn>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { Checkbox, Form, FormInstance, Input, InputNumber, Select } from 'antd';
|
||||
import { useEffect } from 'react';
|
||||
import NestedFormFields from './nested-form-fields';
|
||||
@@ -7,7 +7,7 @@ interface ParamValues {
|
||||
[key: string]: string | number | boolean | Record<string, any>;
|
||||
}
|
||||
|
||||
function ModelParams({ params, form }: { params: Array<SupportModelParams> | null; form: FormInstance<any> }) {
|
||||
function ConfigurableForm({ params, form }: { params: Array<ConfigurableParams> | null; form: FormInstance<any> }) {
|
||||
useEffect(() => {
|
||||
if (params) {
|
||||
const initialValues: ParamValues = {};
|
||||
@@ -53,6 +53,7 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
});
|
||||
return normalized;
|
||||
};
|
||||
|
||||
// Override the original submit method of the form
|
||||
const originalSubmit = form.submit;
|
||||
form.submit = () => {
|
||||
@@ -62,12 +63,12 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
originalSubmit.call(form);
|
||||
};
|
||||
|
||||
function renderItem(param: SupportModelParams) {
|
||||
function renderItem(param: ConfigurableParams) {
|
||||
if (param.nested_fields) {
|
||||
return (
|
||||
<NestedFormFields
|
||||
parentName={param.param_name}
|
||||
fields={param.nested_fields as Record<string, SupportModelParams[]>}
|
||||
fields={param.nested_fields as Record<string, ConfigurableParams[]>}
|
||||
form={form}
|
||||
/>
|
||||
);
|
||||
@@ -75,8 +76,10 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
|
||||
const type = param.param_type.toLowerCase();
|
||||
const isFixed = param.ext_metadata?.tags?.includes('fixed');
|
||||
const isPrivacy = param.ext_metadata?.tags?.includes('privacy');
|
||||
|
||||
if (type === 'str' || type === 'string') {
|
||||
// Handle dropdown selection box
|
||||
if (param.valid_values) {
|
||||
return (
|
||||
<Select disabled={isFixed}>
|
||||
@@ -88,20 +91,30 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle password input box
|
||||
if (isPrivacy) {
|
||||
return <Input.Password disabled={isFixed} autoComplete='new-password' placeholder='请输入密码' />;
|
||||
}
|
||||
|
||||
// Handle normal text input box
|
||||
return <Input disabled={isFixed} />;
|
||||
}
|
||||
|
||||
if (type === 'int' || type === 'integer' || type === 'number' || type === 'float') {
|
||||
return <InputNumber className='w-full' disabled={isFixed} />;
|
||||
}
|
||||
|
||||
if (type === 'bool' || type === 'boolean') {
|
||||
return <Checkbox disabled={isFixed} />;
|
||||
}
|
||||
|
||||
return <Input disabled={isFixed} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-4'>
|
||||
{params?.map((param: SupportModelParams) => (
|
||||
{params?.map((param: ConfigurableParams) => (
|
||||
<Form.Item
|
||||
key={param.param_name}
|
||||
label={<p className='whitespace-normal overflow-wrap-break-word'>{param.label || param.param_name}</p>}
|
||||
@@ -122,4 +135,4 @@ function ModelParams({ params, form }: { params: Array<SupportModelParams> | nul
|
||||
);
|
||||
}
|
||||
|
||||
export default ModelParams;
|
||||
export default ConfigurableForm;
|
@@ -1,10 +1,10 @@
|
||||
import { SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { Checkbox, Form, FormInstance, Input, InputNumber, Select } from 'antd';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
interface NestedFormFieldsProps {
|
||||
parentName: string;
|
||||
fields: Record<string, SupportModelParams[]>;
|
||||
fields: Record<string, ConfigurableParams[]>;
|
||||
form: FormInstance;
|
||||
}
|
||||
const NestedFormFields: React.FC<NestedFormFieldsProps> = ({ parentName, fields, form }) => {
|
||||
@@ -39,7 +39,7 @@ const NestedFormFields: React.FC<NestedFormFieldsProps> = ({ parentName, fields,
|
||||
});
|
||||
};
|
||||
|
||||
const renderFormItem = (param: SupportModelParams) => {
|
||||
const renderFormItem = (param: ConfigurableParams) => {
|
||||
const type = param.param_type.toLowerCase();
|
||||
// Use the complete field path
|
||||
const fieldPath = [parentName, param.param_name];
|
149
web/components/database/database-form.tsx
Normal file
149
web/components/database/database-form.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
import { apiInterceptors, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { DBOption, DBType } from '@/types/db';
|
||||
import { Button, Form, Input, Select, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ConfigurableForm from '../common/configurable-form';
|
||||
|
||||
const { Option } = Select;
|
||||
const FormItem = Form.Item;
|
||||
|
||||
interface DatabaseFormProps {
|
||||
onCancel: () => void;
|
||||
onSuccess: () => void;
|
||||
dbTypeList: DBOption[];
|
||||
editValue?: string;
|
||||
choiceDBType?: DBType;
|
||||
getFromRenderData?: ConfigurableParams[];
|
||||
dbNames?: string[];
|
||||
description?: string; // Add description prop
|
||||
}
|
||||
|
||||
function DatabaseForm({
|
||||
onCancel,
|
||||
onSuccess,
|
||||
dbTypeList,
|
||||
editValue,
|
||||
choiceDBType,
|
||||
getFromRenderData,
|
||||
dbNames = [],
|
||||
description = '', // Default value for description
|
||||
}: DatabaseFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [selectedType, setSelectedType] = useState<DBType | undefined>(choiceDBType);
|
||||
const [params, setParams] = useState<Array<ConfigurableParams> | null>(getFromRenderData || null);
|
||||
console.log('dbTypeList', dbTypeList);
|
||||
console.log('editValue', editValue);
|
||||
console.log('choiceDBType', choiceDBType);
|
||||
|
||||
useEffect(() => {
|
||||
if (choiceDBType) {
|
||||
setSelectedType(choiceDBType);
|
||||
}
|
||||
}, [choiceDBType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editValue && getFromRenderData) {
|
||||
setParams(getFromRenderData);
|
||||
// set description
|
||||
form.setFieldValue('description', description);
|
||||
}
|
||||
}, [editValue, getFromRenderData, description, form]);
|
||||
|
||||
const handleTypeChange = (value: DBType) => {
|
||||
setSelectedType(value);
|
||||
form.resetFields(['params']);
|
||||
|
||||
const selectedDBType = dbTypeList.find(type => type.value === value);
|
||||
if (selectedDBType?.parameters) {
|
||||
setParams(selectedDBType.parameters);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (formValues: any) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
console.log('dbNames:', dbNames);
|
||||
|
||||
// Check if database name is duplicated
|
||||
// if (!editValue && dbNames.includes(values.database)) {
|
||||
// message.error(t('database_name_exists'));
|
||||
// return;
|
||||
// }
|
||||
|
||||
const { description, type, ...values } = formValues;
|
||||
|
||||
const data = {
|
||||
type: selectedType,
|
||||
params: values,
|
||||
description: description || '',
|
||||
};
|
||||
|
||||
// If in edit mode, add id
|
||||
if (editValue) {
|
||||
data.id = editValue;
|
||||
}
|
||||
|
||||
console.log('Form submitted:', data);
|
||||
|
||||
const [testErr] = await apiInterceptors(postDbTestConnect(data));
|
||||
if (testErr) return;
|
||||
const [err] = await apiInterceptors((editValue ? postDbEdit : postDbAdd)(data));
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success(t(editValue ? 'update_success' : 'create_success'));
|
||||
onSuccess?.();
|
||||
} catch (error) {
|
||||
console.error('Failed to submit form:', error);
|
||||
message.error(t(editValue ? 'update_failed' : 'create_failed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form
|
||||
form={form}
|
||||
layout='vertical'
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
type: selectedType,
|
||||
}}
|
||||
>
|
||||
<FormItem
|
||||
label={t('database_type')}
|
||||
name='type'
|
||||
rules={[{ required: true, message: t('please_select_database_type') }]}
|
||||
>
|
||||
<Select placeholder={t('select_database_type')} onChange={handleTypeChange} disabled={!!editValue}>
|
||||
{dbTypeList.map(type => (
|
||||
<Option key={type.value} value={type.value} disabled={type.disabled}>
|
||||
{type.label}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</FormItem>
|
||||
|
||||
{params && <ConfigurableForm params={params} form={form} />}
|
||||
|
||||
<FormItem label={t('description')} name='description'>
|
||||
<Input.TextArea rows={2} placeholder={t('input_description')} />
|
||||
</FormItem>
|
||||
|
||||
<div className='flex justify-end space-x-4 mt-6'>
|
||||
<Button onClick={onCancel}>{t('cancel')}</Button>
|
||||
<Button type='primary' htmlType='submit' loading={loading}>
|
||||
{t('submit')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export default DatabaseForm;
|
@@ -1,217 +1,54 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { addOmcDB, apiInterceptors, getSupportDBList, postDbAdd, postDbEdit, postDbTestConnect } from '@/client/api';
|
||||
import { isFileDb } from '@/pages/construct/database';
|
||||
import { DBOption, DBType, DbListResponse, PostDbParams } from '@/types/db';
|
||||
import { useDebounceFn } from 'ahooks';
|
||||
import { Button, Form, Input, InputNumber, Modal, Select, Spin, Tooltip, message } from 'antd';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { DBOption, DBType } from '@/types/db';
|
||||
import { Modal } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import DatabaseForm from './database-form';
|
||||
|
||||
type DBItem = DbListResponse[0] & { db_arn?: string };
|
||||
|
||||
interface Props {
|
||||
dbTypeList: DBOption[];
|
||||
interface NewFormDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onSuccess: () => void;
|
||||
dbTypeList: DBOption[];
|
||||
editValue?: string;
|
||||
choiceDBType?: DBType;
|
||||
editValue?: DBItem;
|
||||
dbNames: string[];
|
||||
onSuccess?: () => void;
|
||||
onClose?: () => void;
|
||||
getFromRenderData?: ConfigurableParams[];
|
||||
dbNames?: string[];
|
||||
dbTypeData?: DBOption[];
|
||||
description?: string;
|
||||
}
|
||||
|
||||
function FormDialog({ open, choiceDBType, dbTypeList, editValue, dbNames, onClose, onSuccess }: Props) {
|
||||
const [loading, setLoading] = useState(false);
|
||||
function FormDialog({
|
||||
open,
|
||||
onClose,
|
||||
onSuccess,
|
||||
dbTypeList,
|
||||
editValue,
|
||||
choiceDBType,
|
||||
getFromRenderData,
|
||||
dbNames,
|
||||
description,
|
||||
}: NewFormDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm<DBItem>();
|
||||
const dbType = Form.useWatch('db_type', form);
|
||||
const [omcDBList, setOmcDBList] = useState([]);
|
||||
const [omcListLoading, setOmcListLoading] = useState(false);
|
||||
const fileDb = useMemo(() => isFileDb(dbTypeList, dbType), [dbTypeList, dbType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (choiceDBType) {
|
||||
form.setFieldValue('db_type', choiceDBType);
|
||||
}
|
||||
}, [choiceDBType]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editValue) {
|
||||
form.setFieldsValue({ ...editValue });
|
||||
if (editValue.db_type === 'omc') {
|
||||
form.setFieldValue('db_arn', editValue.db_path);
|
||||
}
|
||||
}
|
||||
}, [editValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const onFinish = async (val: DBItem) => {
|
||||
const { db_host, db_path, db_port, db_type, ...params } = val;
|
||||
setLoading(true);
|
||||
|
||||
if (db_type === 'omc') {
|
||||
const item = omcDBList?.find((item: any) => item.arn === val.db_name) as any;
|
||||
|
||||
try {
|
||||
const [err] = await apiInterceptors(
|
||||
addOmcDB({
|
||||
db_type: 'omc',
|
||||
file_path: val.db_arn || '',
|
||||
comment: val.comment,
|
||||
db_name: item?.dbName || val.db_name,
|
||||
}),
|
||||
);
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
onSuccess?.();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
if (!editValue && dbNames.some(item => item === params.db_name)) {
|
||||
message.error('The database already exists!');
|
||||
return;
|
||||
}
|
||||
const data: PostDbParams = {
|
||||
db_host: fileDb ? undefined : db_host,
|
||||
db_port: fileDb ? undefined : db_port,
|
||||
db_type: db_type,
|
||||
file_path: fileDb ? db_path : undefined,
|
||||
...params,
|
||||
};
|
||||
try {
|
||||
const [testErr] = await apiInterceptors(postDbTestConnect(data));
|
||||
if (testErr) return;
|
||||
const [err] = await apiInterceptors((editValue ? postDbEdit : postDbAdd)(data));
|
||||
if (err) {
|
||||
message.error(err.message);
|
||||
return;
|
||||
}
|
||||
message.success('success');
|
||||
onSuccess?.();
|
||||
} catch (e: any) {
|
||||
message.error(e.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { run: fetchOmcList } = useDebounceFn(
|
||||
async (name: string) => {
|
||||
setOmcListLoading(true);
|
||||
const [_, data = []] = (await apiInterceptors(getSupportDBList(name))) as any;
|
||||
setOmcListLoading(false);
|
||||
|
||||
setOmcDBList(data.map((item: any) => ({ ...item, label: item.dbName, value: item.arn })));
|
||||
},
|
||||
{
|
||||
wait: 500,
|
||||
},
|
||||
);
|
||||
|
||||
const lockDBType = useMemo(() => !!editValue || !!choiceDBType, [editValue, choiceDBType]);
|
||||
return (
|
||||
<Modal
|
||||
title={t(editValue ? 'edit_database' : 'add_database')}
|
||||
open={open}
|
||||
width={800}
|
||||
title={editValue ? t('Edit') : t('create_database')}
|
||||
maskClosable={false}
|
||||
footer={null}
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
width={800}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form form={form} className='pt-2' labelCol={{ span: 6 }} labelAlign='left' onFinish={onFinish}>
|
||||
<Form.Item name='db_type' label='DB Type' className='mb-6' rules={[{ required: true }]}>
|
||||
<Select aria-readonly={lockDBType} disabled={lockDBType} options={dbTypeList} />
|
||||
</Form.Item>
|
||||
{form.getFieldValue('db_type') === 'omc' ? (
|
||||
<Form.Item name='db_name' label='DB Name' className='mb-6' rules={[{ required: true }]}>
|
||||
<Select
|
||||
optionRender={(option, { index }) => {
|
||||
const item = omcDBList[index] as any;
|
||||
return (
|
||||
<div key={option.value} className='flex flex-col'>
|
||||
<span className='text-[18px]'>{item?.dbName}</span>
|
||||
<span>
|
||||
<span>env: </span>
|
||||
<span className='text-gray-500'>{item.env}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>account: </span>
|
||||
<span className='text-gray-500'>{item.account}</span>
|
||||
</span>
|
||||
<span>
|
||||
<span>searchName: </span>
|
||||
<Tooltip title={item.searchName}>
|
||||
<span className='text-gray-500'>{item.searchName}</span>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
notFoundContent={omcListLoading ? <Spin size='small' /> : null}
|
||||
showSearch
|
||||
options={omcDBList}
|
||||
onSearch={fetchOmcList}
|
||||
onSelect={searchName => {
|
||||
const item = omcDBList?.find((item: any) => item.value === searchName) as any;
|
||||
form.setFieldsValue({
|
||||
db_arn: item?.arn,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item name='db_name' label='DB Name' className='mb-3' rules={[{ required: true }]}>
|
||||
<Input readOnly={!!editValue} disabled={!!editValue} />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === true && (
|
||||
<Form.Item name='db_path' label='Path' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
{fileDb === false && form.getFieldValue('db_type') !== 'omc' && (
|
||||
<>
|
||||
<Form.Item name='db_user' label='Username' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_pwd' label='Password' className='mb-6' rules={[{ required: false }]}>
|
||||
<Input type='password' />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_host' label='Host' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name='db_port' label='Port' className='mb-6' rules={[{ required: true }]}>
|
||||
<InputNumber min={1} step={1} max={65535} />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{form.getFieldValue('db_type') === 'omc' && (
|
||||
<Form.Item name='db_arn' label='Arn' className='mb-6' rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item name='comment' label='Remark' className='mb-6'>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item className='flex flex-row-reverse pt-1 mb-0'>
|
||||
<Button htmlType='submit' type='primary' size='middle' className='mr-1' loading={loading}>
|
||||
Save
|
||||
</Button>
|
||||
<Button size='middle' onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<DatabaseForm
|
||||
onCancel={onClose}
|
||||
onSuccess={onSuccess}
|
||||
dbTypeList={dbTypeList}
|
||||
editValue={editValue}
|
||||
choiceDBType={choiceDBType}
|
||||
getFromRenderData={getFromRenderData}
|
||||
dbNames={dbNames}
|
||||
description={description}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
@@ -1,11 +1,12 @@
|
||||
import { apiInterceptors, createModel, getSupportModels } from '@/client/api';
|
||||
import { renderModelIcon } from '@/components/chat/header/model-selector';
|
||||
import { StartModelParams, SupportModel, SupportModelParams } from '@/types/model';
|
||||
import { ConfigurableParams } from '@/types/common';
|
||||
import { StartModelParams, SupportModel } from '@/types/model';
|
||||
import { AutoComplete, Button, Form, Select, Tooltip, message } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import ModelParams from './model-params';
|
||||
import ConfigurableForm from '../common/configurable-form';
|
||||
|
||||
const { Option } = Select;
|
||||
const FormItem = Form.Item;
|
||||
@@ -18,7 +19,7 @@ function ModelForm({ onCancel, onSuccess }: { onCancel: () => void; onSuccess: (
|
||||
const [_, setModels] = useState<Array<SupportModel> | null>([]);
|
||||
const [selectedWorkerType, setSelectedWorkerType] = useState<string>();
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>();
|
||||
const [params, setParams] = useState<Array<SupportModelParams> | null>(null);
|
||||
const [params, setParams] = useState<Array<ConfigurableParams> | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -215,7 +216,7 @@ function ModelForm({ onCancel, onSuccess }: { onCancel: () => void; onSuccess: (
|
||||
/>
|
||||
</FormItem>
|
||||
|
||||
<ModelParams params={params.filter(p => p.param_name !== 'name')} form={form} />
|
||||
<ConfigurableForm params={params.filter(p => p.param_name !== 'name')} form={form} />
|
||||
</>
|
||||
)}
|
||||
|
||||
|
Reference in New Issue
Block a user