mirror of
https://github.com/CodePhiliaX/Chat2DB.git
synced 2025-08-02 05:20:15 +08:00
Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@ -255,7 +255,9 @@ jobs:
|
||||
cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file
|
||||
cp -r chat2db-client/release/*.dmg ./oss_temp_file
|
||||
cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist
|
||||
cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ && zip -r chat2db-server-start.zip ./
|
||||
cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/ && zip -r ${{ steps.chat2db_version.outputs.substring }}.zip ./
|
||||
cp -r ${{ steps.chat2db_version.outputs.substring }}.zip ../../../oss_temp_file
|
||||
cd static/ && zip -r chat2db-server-start.zip ./
|
||||
cp -r chat2db-server-start.zip ../../../../oss_temp_file
|
||||
|
||||
# 准备要需要的数据 MacOS arm64
|
||||
|
25
CHANGELOG.md
25
CHANGELOG.md
@ -1,3 +1,28 @@
|
||||
# 3.0.5
|
||||
`2023-10-23`
|
||||
**Changelog**
|
||||
- ⭐【New Features】Supports visual database creation
|
||||
- ⭐【New Features】Support hot update
|
||||
- ⭐【New Features】Double-click the table to open it directly
|
||||
- ⚡️【Optimize】The search table supports size fuzzy matching
|
||||
- ⚡️【Optimize】Sort Database and Schema at the top
|
||||
- ⚡️【Optimize】The queried data supports editing and modification in the large popup window of the view
|
||||
- ⚡️【Optimize】Example Query the page loading effect of data
|
||||
- ⚡️【Optimize】Keep the top focused tab always in the viewable area
|
||||
- ⚡️【Optimize】Query data cell does not have scroll bar problem
|
||||
|
||||
**更新日志**
|
||||
- ⭐【新功能】支持可视化创建数据库
|
||||
- ⭐【新功能】支持热更新
|
||||
- ⭐【新功能】双击表直接打开表
|
||||
- ⚡️【优化】搜索表支持大小模糊匹配
|
||||
- ⚡️【优化】Database 和 Schema 排序
|
||||
- ⚡️【优化】查询的数据支持在查看的大的弹窗中编辑修改
|
||||
- ⚡️【优化】查询数据翻页loading效果
|
||||
- ⚡️【优化】保持顶部聚焦的tab永远在可视区域内
|
||||
- ⚡️【优化】查询数据单元格没有滚动条问题
|
||||
|
||||
|
||||
# 3.0.4
|
||||
`2023-10-20`
|
||||
|
||||
|
@ -97,7 +97,7 @@ export default defineConfig({
|
||||
define: {
|
||||
__ENV__: process.env.UMI_ENV,
|
||||
__BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()),
|
||||
__APP_VERSION__: yarn_config.app_version || '9.9.9',
|
||||
__APP_VERSION__: yarn_config.app_version || '0.0.0',
|
||||
__APP_PORT__: yarn_config.app_port,
|
||||
},
|
||||
esbuildMinifyIIFE: true
|
||||
|
@ -129,7 +129,7 @@
|
||||
}
|
||||
],
|
||||
"publisherName": "Chat2DB",
|
||||
"icon": "src/assets/logo/logo.png"
|
||||
"icon": "src/assets/logo/logo.ico"
|
||||
},
|
||||
"linux": {
|
||||
"maintainer": "Chat2DB, huanyueyaoqin@qq.com",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 27 KiB |
Binary file not shown.
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 17 KiB |
@ -4,7 +4,7 @@ import styles from './index.less';
|
||||
import classnames from 'classnames';
|
||||
import DraggableContainer from '@/components/DraggableContainer';
|
||||
import Console, { IAppendValue } from '@/components/Console';
|
||||
import SearchResult from '@/components/SearchResult';
|
||||
import SearchResult, { ISearchResultRef } from '@/components/SearchResult';
|
||||
import { DatabaseTypeCode, ConsoleStatus } from '@/constants';
|
||||
import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace';
|
||||
import { IAIState } from '@/models/ai';
|
||||
@ -31,7 +31,8 @@ const SQLExecute = memo<IProps>((props) => {
|
||||
const draggableRef = useRef<any>();
|
||||
const [appendValue, setAppendValue] = useState<IAppendValue>();
|
||||
const { curTableList, curWorkspaceParams } = workspaceModel;
|
||||
const [sql, setSql] = useState<string>('');
|
||||
// const [sql, setSql] = useState<string>('');
|
||||
const searchResultRef = useRef<ISearchResultRef>(null);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (!doubleClickTreeNodeData) {
|
||||
@ -67,7 +68,9 @@ const SQLExecute = memo<IProps>((props) => {
|
||||
executeParams={{ ...data }}
|
||||
hasAiChat={true}
|
||||
hasAi2Lang={true}
|
||||
onExecuteSQL={setSql}
|
||||
onExecuteSQL={(sql) => {
|
||||
searchResultRef.current?.handleExecuteSQL(sql);
|
||||
}}
|
||||
onConsoleSave={() => {
|
||||
dispatch({
|
||||
type: 'workspace/fetchGetSavedConsole',
|
||||
@ -89,7 +92,7 @@ const SQLExecute = memo<IProps>((props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.boxRightResult}>
|
||||
<SearchResult executeSqlParams={data} sql={sql} />
|
||||
<SearchResult ref={searchResultRef} executeSqlParams={data} />
|
||||
</div>
|
||||
</DraggableContainer>
|
||||
</div>
|
||||
|
@ -9,7 +9,6 @@ import { DownloadOutlined } from '@ant-design/icons';
|
||||
import { IUpdateDetectionData } from '../index';
|
||||
import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection';
|
||||
import Iconfont from '@/components/Iconfont';
|
||||
|
||||
interface IProps {
|
||||
updateDetectionData: IUpdateDetectionData | null;
|
||||
updateDetectionRef: React.MutableRefObject<IUpdateDetectionRef> | null;
|
||||
@ -35,7 +34,6 @@ export default function AboutUs(props: IProps) {
|
||||
};
|
||||
|
||||
const restartApp = () => {
|
||||
console.log(window.electronApi)
|
||||
window.electronApi?.quitApp();
|
||||
}
|
||||
|
||||
@ -80,6 +78,12 @@ export default function AboutUs(props: IProps) {
|
||||
{i18n('setting.button.restart')}
|
||||
</Button>
|
||||
);
|
||||
// case UpdatedStatusEnum.UPDATED:
|
||||
// return (
|
||||
// <Button icon={<RedoOutlined />} type="primary">
|
||||
// {i18n('setting.button.restart')}
|
||||
// </Button>
|
||||
// );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
@ -37,10 +37,9 @@ const MAX_TIMES = 200;
|
||||
|
||||
const UpdateDetection = memo(
|
||||
forwardRef((props: IProps, ref: ForwardedRef<IUpdateDetectionRef>) => {
|
||||
const { openSettingModal, updateDetectionData, setUpdateDetectionData } = props;
|
||||
const { openSettingModal, setUpdateDetectionData } = props;
|
||||
const [notificationApi, notificationDom] = notification.useNotification();
|
||||
const timesRef = React.useRef(0);
|
||||
console.log(updateDetectionData);
|
||||
|
||||
useEffect(() => {
|
||||
checkUpdate();
|
||||
|
@ -2,7 +2,8 @@ import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef
|
||||
import cs from 'classnames';
|
||||
import { useTheme } from '@/hooks';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { DatabaseTypeCode, editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants';
|
||||
import { DatabaseTypeCode, EditorThemeType, ThemeType } from '@/constants';
|
||||
import { editorDefaultOptions } from './monacoEditorConfig';
|
||||
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput';
|
||||
|
||||
import styles from './index.less';
|
||||
@ -88,6 +89,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef<IExportRefFunction>) {
|
||||
// 'editorLineNumber.foreground': colorPrimary, // 行号颜色
|
||||
'editorLineNumber.activeForeground': colorPrimary, // 当前行号颜色
|
||||
// 'editorCursor.foreground': colorPrimary, // 光标颜色
|
||||
'editorRuler.foreground': colorPrimary + '15',
|
||||
};
|
||||
monaco.editor.defineTheme(ThemeType.Light, {
|
||||
base: 'vs',
|
||||
|
@ -30,13 +30,13 @@ enum IPromptType {
|
||||
ChatRobot = 'ChatRobot',
|
||||
}
|
||||
|
||||
enum IPromptTypeText {
|
||||
NL_2_SQL = '自然语言转换',
|
||||
SQL_EXPLAIN = '解释SQL',
|
||||
SQL_OPTIMIZER = 'SQL优化',
|
||||
SQL_2_SQL = 'SQL转换',
|
||||
ChatRobot = 'Chat机器人',
|
||||
}
|
||||
// enum IPromptTypeText {
|
||||
// NL_2_SQL = '自然语言转换',
|
||||
// SQL_EXPLAIN = '解释SQL',
|
||||
// SQL_OPTIMIZER = 'SQL优化',
|
||||
// SQL_2_SQL = 'SQL转换',
|
||||
// ChatRobot = 'Chat机器人',
|
||||
// }
|
||||
|
||||
export type IAppendValue = {
|
||||
text: any;
|
||||
@ -65,11 +65,12 @@ interface IProps {
|
||||
consoleId?: number;
|
||||
schemaName?: string;
|
||||
consoleName?: string;
|
||||
status?: ConsoleStatus;
|
||||
};
|
||||
tableList?: ITreeNode[];
|
||||
editorOptions?: IEditorOptions;
|
||||
aiModel: IAIState;
|
||||
dispatch: Function;
|
||||
dispatch: any;
|
||||
remainingBtnLoading: boolean;
|
||||
// remainingUse: IAIState['remainingUse'];
|
||||
// onSQLContentChange: (v: string) => void;
|
||||
@ -108,8 +109,9 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
const [isStream, setIsStream] = useState(false);
|
||||
const timerRef = useRef<any>();
|
||||
const aiFetchIntervalRef = useRef<any>();
|
||||
const initializeSuccessful = useRef(false);
|
||||
const closeEventSource = useRef<any>();
|
||||
// 上一次同步的console数据
|
||||
const lastSyncConsole = useRef<any>(defaultValue);
|
||||
|
||||
/**
|
||||
* 当前选择的AI类型是Chat2DBAI
|
||||
@ -133,23 +135,19 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
editorRef: editorRef?.current,
|
||||
}));
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (source !== 'workspace') {
|
||||
return;
|
||||
}
|
||||
// 离开时保存
|
||||
if (!isActive) {
|
||||
if (!isActive && timerRef.current) {
|
||||
// 离开时清除定时器
|
||||
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', {
|
||||
consoleId: executeParams.consoleId!,
|
||||
ddl: editorRef?.current?.getAllContent(),
|
||||
userId: getCookie('CHAT2DB.USER_ID'),
|
||||
});
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
clearInterval(timerRef.current);
|
||||
} else {
|
||||
// 活跃时自动保存
|
||||
indexedDB
|
||||
@ -158,12 +156,14 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
userId: getCookie('CHAT2DB.USER_ID'),
|
||||
})
|
||||
.then((res: any) => {
|
||||
const value = res?.[0]?.ddl || '';
|
||||
if (value) {
|
||||
const value = defaultValue || res?.[0]?.ddl || '';
|
||||
const oldValue = editorRef?.current?.getAllContent();
|
||||
if (value !== oldValue) {
|
||||
editorRef?.current?.setValue(value, 'reset');
|
||||
initializeSuccessful.current = true;
|
||||
timingAutoSave();
|
||||
}
|
||||
setTimeout(() => {
|
||||
timingAutoSave();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
@ -173,14 +173,30 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
};
|
||||
}, [isActive]);
|
||||
|
||||
function timingAutoSave() {
|
||||
function timingAutoSave(status?: ConsoleStatus) {
|
||||
if (timerRef.current) {
|
||||
clearInterval(timerRef.current);
|
||||
}
|
||||
timerRef.current = setInterval(() => {
|
||||
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', {
|
||||
consoleId: executeParams.consoleId!,
|
||||
ddl: editorRef?.current?.getAllContent(),
|
||||
userId: getCookie('CHAT2DB.USER_ID'),
|
||||
});
|
||||
}, 3000);
|
||||
const ddl = editorRef?.current?.getAllContent();
|
||||
if (ddl === lastSyncConsole.current) {
|
||||
return;
|
||||
}
|
||||
lastSyncConsole.current = ddl;
|
||||
if (executeParams.status === ConsoleStatus.RELEASE || status === ConsoleStatus.RELEASE) {
|
||||
const p: any = {
|
||||
id: executeParams.consoleId,
|
||||
ddl,
|
||||
};
|
||||
historyServer.updateSavedConsole(p);
|
||||
} else {
|
||||
indexedDB.updateData('chat2db', 'workspaceConsoleDDL', {
|
||||
consoleId: executeParams.consoleId!,
|
||||
ddl,
|
||||
userId: getCookie('CHAT2DB.USER_ID'),
|
||||
});
|
||||
}
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
const tableListName = useMemo(() => {
|
||||
@ -370,7 +386,7 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
|
||||
const saveConsole = (value?: string) => {
|
||||
// const a = editorRef.current?.getAllContent();
|
||||
let p: any = {
|
||||
const p: any = {
|
||||
id: executeParams.consoleId,
|
||||
status: ConsoleStatus.RELEASE,
|
||||
ddl: value,
|
||||
@ -380,6 +396,7 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', executeParams.consoleId!);
|
||||
message.success(i18n('common.tips.saveSuccessfully'));
|
||||
props.onConsoleSave && props.onConsoleSave();
|
||||
timingAutoSave(ConsoleStatus.RELEASE);
|
||||
});
|
||||
};
|
||||
|
||||
@ -447,10 +464,10 @@ function Console(props: IProps, ref: ForwardedRef<IConsoleRef>) {
|
||||
};
|
||||
|
||||
const handleSelectTableSyncModel = () => {
|
||||
const syncModel: SyncModelType | null = Number(localStorage.getItem('syncTableModel')) ?? null;
|
||||
const syncModel = localStorage.getItem('syncTableModel');
|
||||
const hasAiAccess = aiModel.hasWhite;
|
||||
if (syncModel !== null) {
|
||||
setSyncTableModel(syncModel);
|
||||
setSyncTableModel(Number(syncModel));
|
||||
return;
|
||||
}
|
||||
|
||||
|
39
chat2db-client/src/components/CreateDatabase/index.less
Normal file
39
chat2db-client/src/components/CreateDatabase/index.less
Normal file
@ -0,0 +1,39 @@
|
||||
@import '../../styles/var.less';
|
||||
|
||||
.monacoEditorBox {
|
||||
height: 200px;
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.errorBox {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.previewBox {
|
||||
// 伪元素画一条剧中的线条
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-bottom: 4px;
|
||||
.previewText {
|
||||
background: var(--color-bg-base);
|
||||
flex-shrink: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.previewLine {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
&::after {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0px;
|
||||
top: 50%;
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: var(--color-border);
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
177
chat2db-client/src/components/CreateDatabase/index.tsx
Normal file
177
chat2db-client/src/components/CreateDatabase/index.tsx
Normal file
@ -0,0 +1,177 @@
|
||||
import React, { useCallback, forwardRef, ForwardedRef, useImperativeHandle, useMemo, useState, useEffect } from 'react';
|
||||
import styles from './index.less';
|
||||
import classnames from 'classnames';
|
||||
import { Form, Input, Modal } from 'antd';
|
||||
import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import sqlService from '@/service/sql';
|
||||
import i18n from '@/i18n';
|
||||
import { debounce } from 'lodash';
|
||||
import { DatabaseTypeCode } from '@/constants';
|
||||
|
||||
interface IProps {
|
||||
className?: string;
|
||||
curWorkspaceParams: any;
|
||||
executedCallback?: () => void;
|
||||
}
|
||||
|
||||
export type CreateType = 'database' | 'schema';
|
||||
|
||||
export interface ICreateDatabaseRef {
|
||||
setOpen: (open: boolean, type?: CreateType) => void;
|
||||
}
|
||||
|
||||
export interface ICreateDatabase {
|
||||
databaseName?: string;
|
||||
schemaName?: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
// 创建database不支持注释的数据库
|
||||
const noCommentDatabase = [DatabaseTypeCode.MYSQL];
|
||||
|
||||
export default forwardRef((props: IProps, ref: ForwardedRef<ICreateDatabaseRef>) => {
|
||||
const { className, curWorkspaceParams, executedCallback } = props;
|
||||
const [form] = Form.useForm<ICreateDatabase>();
|
||||
const monacoEditorUuid = useMemo(() => uuid(), []);
|
||||
const monacoEditorRef = React.useRef<IExportRefFunction>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [errorMessage, setErrorMessage] = useState<{ success: boolean; message: string; originalSql: string } | null>(
|
||||
null,
|
||||
);
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const [createType, setCreateType] = useState<CreateType>('database');
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setErrorMessage(null);
|
||||
form.resetFields();
|
||||
monacoEditorRef.current?.setValue('', 'cover');
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const config = useMemo(() => {
|
||||
return createType === 'database'
|
||||
? {
|
||||
title: `${i18n('common.title.create')} Database`,
|
||||
api: sqlService.getCreateDatabaseSql,
|
||||
formName: 'databaseName',
|
||||
}
|
||||
: {
|
||||
title: `${i18n('common.title.create')} Schema`,
|
||||
api: sqlService.getCreateSchemaSql,
|
||||
formName: 'schemaName',
|
||||
};
|
||||
}, [createType]);
|
||||
|
||||
const exposedSetOpen = (_open: boolean, type?: CreateType) => {
|
||||
setOpen(_open);
|
||||
setCreateType(type || 'database');
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setOpen: exposedSetOpen,
|
||||
}));
|
||||
|
||||
const labelCol = { flex: '70px' };
|
||||
|
||||
const handleFieldsChange = useCallback(
|
||||
debounce(() => {
|
||||
const formData: ICreateDatabase = form.getFieldsValue();
|
||||
if (!formData.databaseName && createType === 'database') {
|
||||
return;
|
||||
}
|
||||
if (!formData.schemaName && createType === 'schema') {
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
databaseType: curWorkspaceParams.databaseType,
|
||||
dataSourceId: curWorkspaceParams.dataSourceId,
|
||||
databaseName: curWorkspaceParams.databaseName,
|
||||
...formData,
|
||||
};
|
||||
config.api(params).then((res) => {
|
||||
const { sql } = res;
|
||||
monacoEditorRef.current?.setValue(sql, 'cover');
|
||||
});
|
||||
}, 500),
|
||||
[curWorkspaceParams, createType, monacoEditorRef, config],
|
||||
);
|
||||
|
||||
const executeUpdateDataSql = (sql: string) => {
|
||||
const params: any = {
|
||||
dataSourceId: curWorkspaceParams.dataSourceId,
|
||||
databaseType: curWorkspaceParams.databaseType,
|
||||
databaseName: curWorkspaceParams.databaseName,
|
||||
sql,
|
||||
};
|
||||
setConfirmLoading(true);
|
||||
setErrorMessage(null);
|
||||
return sqlService
|
||||
.executeDDL(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
setOpen(false);
|
||||
executedCallback?.();
|
||||
} else {
|
||||
setErrorMessage(res);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setConfirmLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
const sql = monacoEditorRef.current?.getAllContent() || '';
|
||||
executeUpdateDataSql(sql);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
title={config.title}
|
||||
destroyOnClose
|
||||
confirmLoading={confirmLoading}
|
||||
open={open}
|
||||
onOk={onOk}
|
||||
>
|
||||
<div className={classnames(styles.box, className)}>
|
||||
<Form labelAlign="left" form={form} labelCol={labelCol} onFieldsChange={handleFieldsChange} name="create">
|
||||
<Form.Item label={i18n('common.label.name')} name={config.formName}>
|
||||
<Input autoComplete="off" />
|
||||
</Form.Item>
|
||||
{noCommentDatabase.includes(curWorkspaceParams.databaseType) ? null : (
|
||||
<Form.Item label={i18n('common.label.comment')} name="comment">
|
||||
<Input autoComplete="off" />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
<div className={styles.previewBox}>
|
||||
<div className={styles.previewText}>{i18n('common.title.preview')}</div>
|
||||
<div className={styles.previewLine} />
|
||||
</div>
|
||||
<div className={styles.monacoEditorBox}>
|
||||
<MonacoEditor
|
||||
ref={monacoEditorRef}
|
||||
options={{
|
||||
lineNumbers: 'off',
|
||||
}}
|
||||
id={monacoEditorUuid}
|
||||
/>
|
||||
</div>
|
||||
{errorMessage && (
|
||||
<>
|
||||
<div className={classnames(styles.previewBox, styles.errorBox)}>
|
||||
<div className={styles.previewText}>{i18n('common.title.errorMessage')}</div>
|
||||
<div className={styles.previewLine} />
|
||||
</div>
|
||||
<div>{errorMessage.message}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
});
|
@ -85,6 +85,7 @@
|
||||
padding: 4px 10px;
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-radius: 8px;
|
||||
.f-doc-en-break();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1 @@
|
||||
@import '../../styles/var.less';
|
31
chat2db-client/src/components/Modal/TriggeredModal/index.tsx
Normal file
31
chat2db-client/src/components/Modal/TriggeredModal/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { memo, Fragment, ReactElement, cloneElement } from 'react';
|
||||
import { Modal } from 'antd';
|
||||
|
||||
interface ITriggeredModal extends React.ComponentProps<typeof Modal> {
|
||||
children: ReactElement;
|
||||
modalContent: React.ReactNode;
|
||||
onOk?: any;
|
||||
}
|
||||
|
||||
const TriggeredModal = memo<ITriggeredModal>((props) => {
|
||||
const { children, modalContent, ...orgs } = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const onClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const onOk = () => {
|
||||
orgs?.onOk?.(setOpen);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{cloneElement(children, { onClick: () => setOpen(true) })}
|
||||
<Modal {...orgs} open={open} onCancel={onClose} onOk={onOk}>
|
||||
{modalContent}
|
||||
</Modal>
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
|
||||
export default TriggeredModal;
|
@ -234,7 +234,6 @@ export default function TableBox(props: ITableProps) {
|
||||
}
|
||||
});
|
||||
setTableData(newTableData);
|
||||
console.log(newTableData);
|
||||
|
||||
// 添加更新记录
|
||||
setUpdateData([
|
||||
|
@ -1,4 +1,13 @@
|
||||
import React, { memo, useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
useRef,
|
||||
forwardRef,
|
||||
ForwardedRef,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Tabs from '@/components/Tabs';
|
||||
import Iconfont from '@/components/Iconfont';
|
||||
@ -27,7 +36,11 @@ const defaultResultConfig: IResultConfig = {
|
||||
hasNextPage: true,
|
||||
};
|
||||
|
||||
export default memo<IProps>((props) => {
|
||||
export interface ISearchResultRef {
|
||||
handleExecuteSQL: (sql: string) => void;
|
||||
}
|
||||
|
||||
export default forwardRef((props: IProps, ref: ForwardedRef<ISearchResultRef>) => {
|
||||
const { className, sql, executeSqlParams } = props;
|
||||
// const [currentTab, setCurrentTab] = useState<string | number | undefined>();
|
||||
const [resultDataList, setResultDataList] = useState<IManageResultData[]>();
|
||||
@ -40,6 +53,10 @@ export default memo<IProps>((props) => {
|
||||
}
|
||||
}, [sql]);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleExecuteSQL,
|
||||
}));
|
||||
|
||||
/**
|
||||
* 执行SQL
|
||||
* @param sql
|
||||
|
@ -6,9 +6,7 @@ interface IProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default memo<IProps>(function XXXX_FN(props) {
|
||||
const { className } = props
|
||||
return <div className={classnames(styles.box, className)}>
|
||||
|
||||
</div>
|
||||
})
|
||||
export default memo<IProps>((props) => {
|
||||
const { className } = props;
|
||||
return <div className={classnames(styles.box, className)}>demo</div>;
|
||||
});
|
||||
|
@ -2,7 +2,6 @@ export * from './appConfig';
|
||||
export * from './common';
|
||||
export * from './database';
|
||||
export * from './environment';
|
||||
export * from './monacoEditor';
|
||||
export * from './table';
|
||||
export * from './theme';
|
||||
export * from './tree';
|
||||
|
@ -93,5 +93,12 @@ export default {
|
||||
'common.text.affectedRows': 'Affected rows: {1}',
|
||||
'common.text.selectFile' : 'Select File',
|
||||
'common.text.noTableFoundUp' : 'No tables in this database',
|
||||
'common.text.noTableFoundDown' : 'Switch databases at the top'
|
||||
'common.text.noTableFoundDown': 'Switch databases at the top',
|
||||
'common.title.preview': 'Preview',
|
||||
'common.title.errorMessage': 'Error message',
|
||||
'common.Button.addDatabase': 'Add database',
|
||||
'common.Button.addSchema': 'Add schema',
|
||||
'common.label.comment': 'Comment',
|
||||
'common.label.name': 'Name',
|
||||
'common.title.create': 'Create',
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ export default {
|
||||
'setting.button.restart': 'Restart',
|
||||
'setting.text.discoverNewVersion': 'Discover new version {1}',
|
||||
'setting.text.isLatestVersion': 'This is the latest version',
|
||||
'setting.button.changeLog': 'ChangeLog',
|
||||
'setting.button.changeLog': 'Changelog',
|
||||
'setting.title.updateRule': 'Update rule',
|
||||
'setting.text.autoUpdate': 'The new version automatically downloads and installs updates',
|
||||
'setting.text.manualUpdate': 'Only alert me when a new version is released',
|
||||
|
@ -93,4 +93,11 @@ export default {
|
||||
'common.text.noTableFoundUp' : '当前库没有查询到表',
|
||||
'common.text.noTableFoundDown' : '你可以在顶部切换数据库',
|
||||
'common.text.updateNow' : '立即更新',
|
||||
'common.title.preview' : '预览',
|
||||
'common.title.errorMessage': '错误信息',
|
||||
'common.Button.addDatabase': '添加数据库',
|
||||
'common.Button.addSchema': '添加Schema',
|
||||
'common.label.comment': '备注',
|
||||
'common.label.name': '名称',
|
||||
'common.title.create': '创建',
|
||||
};
|
||||
|
@ -6,7 +6,6 @@ import { v4 as uuidv4 } from 'uuid';
|
||||
import { getAntdThemeConfig, injectThemeVar } from '@/theme';
|
||||
import { IVersionResponse } from '@/typings';
|
||||
import miscService from '@/service/misc';
|
||||
import configService from '@/service/config';
|
||||
import antdEnUS from 'antd/locale/en_US';
|
||||
import antdZhCN from 'antd/locale/zh_CN';
|
||||
import { useTheme } from '@/hooks';
|
||||
@ -34,7 +33,7 @@ declare global {
|
||||
electronApi?: {
|
||||
startServerForSpawn: () => void;
|
||||
quitApp: () => void;
|
||||
beforeQuitApp: (fn: () => void) => void;
|
||||
setBaseURL: (baseUrl:string) => void;
|
||||
registerAppMenu: (data:any) => void;
|
||||
};
|
||||
}
|
||||
@ -111,10 +110,9 @@ function AppContainer() {
|
||||
window.electronApi?.registerAppMenu({
|
||||
version: __APP_VERSION__,
|
||||
})
|
||||
|
||||
window.electronApi?.beforeQuitApp(()=>{
|
||||
configService.stopJavaService()
|
||||
})
|
||||
// 把关闭java服务的的方法传给electron
|
||||
window.electronApi?.setBaseURL?.(window._BaseURL)
|
||||
// console.log(window.electronApi)
|
||||
}
|
||||
|
||||
// 初始化indexedDB
|
||||
|
@ -10,6 +10,8 @@ const { loadMainResource } = require('./utils');
|
||||
|
||||
let mainWindow = null;
|
||||
|
||||
let baseUrl = null;
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
minWidth: 1080,
|
||||
@ -69,7 +71,18 @@ app.on('window-all-closed', () => {
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
mainWindow.webContents.send('before-quit-app');
|
||||
if(baseUrl){
|
||||
try {
|
||||
const request = net.request({
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method: 'POST',
|
||||
url: `${baseUrl}/api/system/stop`,
|
||||
});
|
||||
request.end();
|
||||
} catch (error) {}
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-product-name', () => {
|
||||
@ -86,3 +99,9 @@ ipcMain.on('quit-app', () => {
|
||||
ipcMain.on('register-app-menu', (event, orgs) => {
|
||||
registerAppMenu(mainWindow, orgs);
|
||||
});
|
||||
|
||||
ipcMain.on('set-base-url',(event,_baseUrl)=>{
|
||||
baseUrl = _baseUrl;
|
||||
})
|
||||
|
||||
|
||||
|
@ -43,13 +43,10 @@ contextBridge.exposeInMainWorld('electronApi', {
|
||||
quitApp: () => {
|
||||
ipcRenderer.send('quit-app');
|
||||
},
|
||||
beforeQuitApp: (callback)=>{
|
||||
ipcRenderer.on('before-quit-app', () => {
|
||||
callback();
|
||||
});
|
||||
setBaseURL: (baseUrl) => {
|
||||
ipcRenderer.send('set-base-url', baseUrl);
|
||||
},
|
||||
registerAppMenu: (menuProps)=>{
|
||||
registerAppMenu: (menuProps) => {
|
||||
ipcRenderer.send('register-app-menu', menuProps);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -58,7 +58,7 @@ const WorkspaceModel: IWorkspaceModelType = {
|
||||
|
||||
state: {
|
||||
databaseAndSchema: undefined,
|
||||
curWorkspaceParams: getCurrentWorkspaceDatabase(),
|
||||
curWorkspaceParams: {} as any,
|
||||
doubleClickTreeNodeData: undefined,
|
||||
consoleList: [],
|
||||
openConsoleList: [],
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import indexedDB from '@/indexedDB';
|
||||
|
||||
export enum CustomerTypeEnum {
|
||||
visitor = 'visitor',
|
||||
@ -14,31 +13,10 @@ export enum CustomerTypeEnum2 {
|
||||
export type CustomerType1 = CustomerTypeEnum | CustomerTypeEnum2;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [db, setDb] = useState<any>();
|
||||
|
||||
useEffect(() => {
|
||||
indexedDB.createDB('chat2db', 2).then((db) => {
|
||||
setDb(db);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const add = () => {
|
||||
indexedDB.addData(db, 'workspaceConsoleDDL', {
|
||||
userId: '1',
|
||||
consoleId: '1',
|
||||
ddl: 'select * from user',
|
||||
});
|
||||
};
|
||||
|
||||
const deleteFn = () => {
|
||||
indexedDB.deleteData(db, 'workspaceConsoleDDL', '1');
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={add}>add</button>
|
||||
<button onClick={deleteFn}>deleteFn</button>
|
||||
</div>
|
||||
<div>demo</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -107,7 +107,7 @@ const SaveList = dvaModel((props: any) => {
|
||||
id: data.id,
|
||||
status: ConsoleStatus.DRAFT,
|
||||
};
|
||||
historyServer.updateSavedConsole(params).then((res) => {
|
||||
historyServer.updateSavedConsole(params).then(() => {
|
||||
dispatch({
|
||||
type: 'workspace/fetchGetSavedConsole',
|
||||
payload: {
|
||||
@ -115,10 +115,10 @@ const SaveList = dvaModel((props: any) => {
|
||||
status: ConsoleStatus.RELEASE,
|
||||
...curWorkspaceParams,
|
||||
},
|
||||
callback: (res: any) => {
|
||||
callback: (_res: any) => {
|
||||
dispatch({
|
||||
type: 'workspace/setConsoleList',
|
||||
payload: res.data,
|
||||
payload: _res.data,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -119,3 +119,18 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dropdownFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 4px;
|
||||
padding: 4px 8px;
|
||||
line-height: 20px;
|
||||
i {
|
||||
margin-right: 4px;
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--color-hover-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
@ -6,12 +6,14 @@ import Iconfont from '@/components/Iconfont';
|
||||
import { IConnectionModelType } from '@/models/connection';
|
||||
import { IWorkspaceModelType } from '@/models/workspace';
|
||||
import { IMainPageType } from '@/models/mainPage';
|
||||
import { Cascader, Spin, Modal, Tag } from 'antd';
|
||||
import { databaseMap, TreeNodeType } from '@/constants';
|
||||
import { Cascader, Spin, Modal, Tag, Divider } from 'antd';
|
||||
import { databaseMap, TreeNodeType, DatabaseTypeCode } from '@/constants';
|
||||
import { treeConfig } from '../Tree/treeConfig';
|
||||
import { useUpdateEffect } from '@/hooks/useUpdateEffect';
|
||||
import styles from './index.less';
|
||||
import i18n from '@/i18n';
|
||||
import CreateDatabase, { ICreateDatabaseRef } from '@/components/CreateDatabase';
|
||||
import { getCurrentWorkspaceDatabase } from '@/utils/localStorage';
|
||||
|
||||
interface IProps {
|
||||
className?: string;
|
||||
@ -26,6 +28,14 @@ interface IOption {
|
||||
value: number | string;
|
||||
}
|
||||
|
||||
// 不支持创建数据库的数据库类型
|
||||
const notSupportCreateDatabaseType = [DatabaseTypeCode.H2];
|
||||
|
||||
// 不支持创建schema的数据库类型
|
||||
const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE];
|
||||
|
||||
const localStorageWorkspaceDatabase = getCurrentWorkspaceDatabase();
|
||||
|
||||
const WorkspaceHeader = memo<IProps>((props) => {
|
||||
const { connectionModel, workspaceModel, mainPageModel, dispatch } = props;
|
||||
const { connectionList, curConnection } = connectionModel;
|
||||
@ -37,6 +47,18 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
const [curDBOptions, setCurDBOptions] = useState<IOption[]>([]);
|
||||
const [curSchemaOptions, setCurSchemaOptions] = useState<IOption[]>([]);
|
||||
const [isRefresh, setIsRefresh] = useState(false);
|
||||
const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState<false | undefined>(undefined);
|
||||
const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState<false | undefined>(undefined);
|
||||
const createDatabaseRef = React.useRef<ICreateDatabaseRef>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (openDBCascaderDropdown === false) {
|
||||
setOpenDBCascaderDropdown(undefined);
|
||||
}
|
||||
if (openSchemaCascaderDropdown === false) {
|
||||
setOpenSchemaCascaderDropdown(undefined);
|
||||
}
|
||||
}, [openDBCascaderDropdown, openSchemaCascaderDropdown]);
|
||||
|
||||
useEffect(() => {
|
||||
if (curPage !== 'workspace') {
|
||||
@ -44,7 +66,14 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
}
|
||||
// 如果没有curConnection默认选第一个
|
||||
if (!curConnection?.id && connectionList.length) {
|
||||
connectionChange([connectionList[0].id], [connectionList[0]]);
|
||||
if (
|
||||
localStorageWorkspaceDatabase.dataSourceId &&
|
||||
connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))
|
||||
) {
|
||||
connectionChange([localStorageWorkspaceDatabase.dataSourceId]);
|
||||
return;
|
||||
}
|
||||
connectionChange([connectionList[0].id]);
|
||||
return;
|
||||
}
|
||||
// 如果都有的话
|
||||
@ -52,7 +81,12 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
// 如果curConnection不再connectionList里,也是默认选第一个
|
||||
const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id);
|
||||
if (flag === -1) {
|
||||
connectionChange([connectionList[0].id], [connectionList[0]]);
|
||||
if (localStorageWorkspaceDatabase.dataSourceId &&
|
||||
connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))) {
|
||||
connectionChange([localStorageWorkspaceDatabase.dataSourceId]);
|
||||
return;
|
||||
}
|
||||
connectionChange([connectionList[0].id]);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -71,10 +105,6 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
getDatabaseList(isRefresh);
|
||||
setIsRefresh(false);
|
||||
}
|
||||
// connectionList转换成可用的ConnectionOptions
|
||||
// if (!connectionList.length) {
|
||||
// setNoConnectionModal(true);
|
||||
// }
|
||||
setConnectionOptions(
|
||||
connectionList?.map((t) => {
|
||||
return {
|
||||
@ -118,7 +148,7 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
dataSourceName: curConnection.name,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
.then((res: any) => {
|
||||
const dbList =
|
||||
res?.map((t) => {
|
||||
return {
|
||||
@ -127,14 +157,20 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
};
|
||||
}) || [];
|
||||
setCurDBOptions(dbList);
|
||||
// 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了)
|
||||
const databaseName =
|
||||
curWorkspaceParams.dataSourceId !== curConnection?.id
|
||||
? dbList[0]?.label
|
||||
: curWorkspaceParams.databaseName || dbList[0]?.label;
|
||||
getSchemaList(databaseName, refresh);
|
||||
let databaseName = '';
|
||||
if(dbList.find((t: any) => t.value === localStorageWorkspaceDatabase.databaseName)){
|
||||
databaseName = localStorageWorkspaceDatabase.databaseName!;
|
||||
}else{
|
||||
// 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了)
|
||||
databaseName =
|
||||
curWorkspaceParams.dataSourceId !== curConnection?.id
|
||||
? dbList[0]?.label
|
||||
: curWorkspaceParams.databaseName || dbList[0]?.label;
|
||||
}
|
||||
databaseChange([databaseName], [{ label: databaseName }],refresh);
|
||||
// getSchemaList(databaseName, refresh);
|
||||
})
|
||||
.catch((error) => {
|
||||
.catch(() => {
|
||||
setCascaderLoading(false);
|
||||
});
|
||||
}
|
||||
@ -155,7 +191,7 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
dataSourceName: curConnection.name,
|
||||
},
|
||||
})
|
||||
.then((res) => {
|
||||
.then((res: any) => {
|
||||
const schemaList =
|
||||
res?.map((t) => {
|
||||
return {
|
||||
@ -164,10 +200,17 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
};
|
||||
}) || [];
|
||||
setCurSchemaOptions(schemaList);
|
||||
const schemaName =
|
||||
curWorkspaceParams.dataSourceId !== curConnection?.id
|
||||
? schemaList[0]?.label
|
||||
: curWorkspaceParams.schemaName || schemaList[0]?.label;
|
||||
|
||||
let schemaName = '';
|
||||
if(schemaList.find((t: any) => t.value === localStorageWorkspaceDatabase.schemaName)){
|
||||
schemaName = localStorageWorkspaceDatabase.schemaName!;
|
||||
}else{
|
||||
schemaName =
|
||||
curWorkspaceParams.dataSourceId !== curConnection?.id
|
||||
? schemaList[0]?.label
|
||||
: curWorkspaceParams.schemaName || schemaList[0]?.label;
|
||||
}
|
||||
// schemaChange([schemaName], [{ label: schemaName }]);
|
||||
const data: any = {
|
||||
dataSourceId: curConnection.id,
|
||||
dataSourceName: curConnection.alias,
|
||||
@ -226,17 +269,17 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
}
|
||||
|
||||
// 数据库切换
|
||||
function databaseChange(valueArr: any, selectedOptions: any) {
|
||||
if (selectedOptions[0].label !== curWorkspaceParams.databaseName) {
|
||||
getSchemaList(selectedOptions[0].label);
|
||||
}
|
||||
function databaseChange(valueArr: any, selectedOptions: any,refresh) {
|
||||
// if (selectedOptions[0].label !== curWorkspaceParams.databaseName) {
|
||||
getSchemaList(selectedOptions[0].label,refresh);
|
||||
// }
|
||||
}
|
||||
|
||||
// schema切换
|
||||
function schemaChange(valueArr: any, selectedOptions: any) {
|
||||
if (selectedOptions[0].label !== curWorkspaceParams.schemaName) {
|
||||
// if (selectedOptions[0].label !== curWorkspaceParams.schemaName) {
|
||||
setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value });
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
function handleRefresh() {
|
||||
@ -265,11 +308,34 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
</Cascader>
|
||||
|
||||
{!!curDBOptions?.length && <Iconfont className={styles.arrow} code="" />}
|
||||
|
||||
{!!curDBOptions?.length && (
|
||||
<Cascader
|
||||
popupClassName={styles.cascaderPopup}
|
||||
options={curDBOptions}
|
||||
open={openDBCascaderDropdown}
|
||||
dropdownRender={(menu) => {
|
||||
return (
|
||||
<div>
|
||||
{menu}
|
||||
<Divider style={{ margin: 0 }} />
|
||||
{
|
||||
// 不支持创建数据库的数据库类型
|
||||
!notSupportCreateDatabaseType.includes(curWorkspaceParams?.databaseType) && (
|
||||
<div
|
||||
className={styles.dropdownFooter}
|
||||
onClick={() => {
|
||||
setOpenDBCascaderDropdown(false);
|
||||
createDatabaseRef.current?.setOpen(true, 'database');
|
||||
}}
|
||||
>
|
||||
<Iconfont code="" />
|
||||
{i18n('common.Button.addDatabase')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
onChange={databaseChange}
|
||||
bordered={false}
|
||||
value={[curWorkspaceParams?.databaseName || '']}
|
||||
@ -286,7 +352,31 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
options={curSchemaOptions}
|
||||
onChange={schemaChange}
|
||||
bordered={false}
|
||||
open={openSchemaCascaderDropdown}
|
||||
value={[curWorkspaceParams?.schemaName || '']}
|
||||
dropdownRender={(menu) => {
|
||||
return (
|
||||
<div>
|
||||
{menu}
|
||||
<Divider style={{ margin: 0 }} />
|
||||
{
|
||||
// 不支持创建schema的数据库类型
|
||||
!notSupportCreateSchemaType.includes(curWorkspaceParams?.databaseType) && (
|
||||
<div
|
||||
className={styles.dropdownFooter}
|
||||
onClick={() => {
|
||||
setOpenSchemaCascaderDropdown(false);
|
||||
createDatabaseRef.current?.setOpen(true, 'schema');
|
||||
}}
|
||||
>
|
||||
<Iconfont code="" />
|
||||
{i18n('common.Button.addSchema')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
>
|
||||
<div className={styles.crumbsItem}>
|
||||
<div className={styles.text}>{curWorkspaceParams.schemaName}</div>
|
||||
@ -330,6 +420,11 @@ const WorkspaceHeader = memo<IProps>((props) => {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<CreateDatabase
|
||||
executedCallback={handleRefresh}
|
||||
curWorkspaceParams={curWorkspaceParams}
|
||||
ref={createDatabaseRef}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -64,7 +64,6 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
|
||||
uniqueData: t,
|
||||
};
|
||||
});
|
||||
console.log(workspaceTabList, newTabList);
|
||||
if (workspaceTabList.length) {
|
||||
const newWorkspaceTabList = lodash.cloneDeep(workspaceTabList);
|
||||
const newAddList: any = [];
|
||||
@ -312,11 +311,23 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
|
||||
}
|
||||
|
||||
if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) {
|
||||
|
||||
const { extraParams } = doubleClickTreeNodeData;
|
||||
const { tableName } = extraParams || {};
|
||||
const sql = `SELECT * FROM ${compatibleDataBaseName(tableName!, curWorkspaceParams.databaseType)};\n`;
|
||||
const title = tableName!;
|
||||
const id = uuidV4();
|
||||
let flag = false;
|
||||
workspaceTabList.forEach((t) => {
|
||||
if (t.uniqueData?.sql === sql) {
|
||||
setActiveConsoleId(t.id);
|
||||
flag = true
|
||||
return;
|
||||
}
|
||||
})
|
||||
if(flag){
|
||||
return
|
||||
}
|
||||
setWorkspaceTabList([
|
||||
...(workspaceTabList || []),
|
||||
{
|
||||
@ -355,6 +366,9 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
|
||||
// 更新表名提示
|
||||
useUpdateEffect(() => {
|
||||
const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams;
|
||||
if (dataSourceId === null || dataSourceId === undefined) {
|
||||
return;
|
||||
}
|
||||
sqlService
|
||||
.getAllTableList({
|
||||
dataSourceId,
|
||||
@ -571,6 +585,7 @@ const WorkspaceRight = memo<IProps>((props: IProps) => {
|
||||
schemaName: curWorkspaceParams?.schemaName,
|
||||
consoleId: t.id as number,
|
||||
consoleName: uniqueData.name,
|
||||
status: uniqueData.status,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -78,11 +78,6 @@ const setAppUpdateType = createRequest<ILatestVersion['type'], boolean>('/api/sy
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
// 退出electron时关闭后端服务
|
||||
const stopJavaService = createRequest<void, void>('/api/system/stop', {
|
||||
method: 'post',
|
||||
});
|
||||
|
||||
export default {
|
||||
getSystemConfig,
|
||||
setSystemConfig,
|
||||
@ -92,6 +87,5 @@ export default {
|
||||
getLatestVersion,
|
||||
isUpdateSuccess,
|
||||
updateDesktopVersion,
|
||||
setAppUpdateType,
|
||||
stopJavaService
|
||||
setAppUpdateType
|
||||
};
|
||||
|
@ -1,10 +1,4 @@
|
||||
import createRequest from './base';
|
||||
import { IVersionResponse } from '@/typings';
|
||||
|
||||
const checkVersion = createRequest<void, IVersionResponse>('/api/client/version/check/v2', {
|
||||
errorLevel: false,
|
||||
outside: true,
|
||||
});
|
||||
|
||||
const dynamicUrl = createRequest<string, void>('', {
|
||||
dynamicUrl: true,
|
||||
@ -12,5 +6,4 @@ const dynamicUrl = createRequest<string, void>('', {
|
||||
|
||||
export default {
|
||||
dynamicUrl,
|
||||
checkVersion,
|
||||
};
|
||||
|
@ -288,7 +288,22 @@ const executeUpdateDataSql = createRequest<IExecuteSqlParams, { success: boolean
|
||||
/** 获取修改表数据的接口 */
|
||||
const getExecuteUpdateSql = createRequest<any, string>('/api/rdb/dml/get_update_sql', { method: 'post' });
|
||||
|
||||
/** 创建数据库 */
|
||||
const getCreateDatabaseSql = createRequest<{
|
||||
dataSourceId: number;
|
||||
databaseName: string;
|
||||
}, { sql: string }>('/api/rdb/database/create_database_sql', { method: 'post' });
|
||||
|
||||
/** 创建schema */
|
||||
const getCreateSchemaSql = createRequest<{
|
||||
dataSourceId: number;
|
||||
databaseName?: string;
|
||||
schemaName?: string;
|
||||
}, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' });
|
||||
|
||||
export default {
|
||||
getCreateSchemaSql,
|
||||
getCreateDatabaseSql,
|
||||
executeUpdateDataSql,
|
||||
executeDDL,
|
||||
getExecuteUpdateSql,
|
||||
|
@ -51,6 +51,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 文档英文强制换行
|
||||
.f-doc-en-break {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
@keyframes loading-animation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
|
@ -244,9 +244,9 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) {
|
||||
// 桌面端用hash模式,web端用history模式,路由跳转
|
||||
export function navigate(path: string) {
|
||||
if (__ENV__ === 'desktop') {
|
||||
window.location.href = `#${path}`;
|
||||
window.location.replace(`#${path}`)
|
||||
} else {
|
||||
window.location.href = path;
|
||||
window.location.replace(path)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,32 @@
|
||||
package ai.chat2db.plugin.h2;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import ai.chat2db.spi.DBManage;
|
||||
import ai.chat2db.spi.jdbc.DefaultDBManage;
|
||||
import ai.chat2db.spi.sql.Chat2DBContext;
|
||||
import ai.chat2db.spi.sql.ConnectInfo;
|
||||
import ai.chat2db.spi.sql.SQLExecutor;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class H2DBManage extends DefaultDBManage implements DBManage {
|
||||
|
||||
@Override
|
||||
public void connectDatabase(Connection connection, String database) {
|
||||
ConnectInfo connectInfo = Chat2DBContext.getConnectInfo();
|
||||
if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) {
|
||||
return;
|
||||
}
|
||||
String schemaName = connectInfo.getSchemaName();
|
||||
try {
|
||||
SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\"");
|
||||
} catch (SQLException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
|
||||
|
@ -129,7 +129,7 @@ public class MysqlSqlBuilder extends DefaultSqlBuilder implements SqlBuilder {
|
||||
@Override
|
||||
public String buildCreateDatabaseSql(Database database) {
|
||||
StringBuilder sqlBuilder = new StringBuilder();
|
||||
sqlBuilder.append("CREATE DATABASE "+database.getName());
|
||||
sqlBuilder.append("CREATE DATABASE `"+database.getName()+"`");
|
||||
if (StringUtils.isNotBlank(database.getCharset())) {
|
||||
sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset());
|
||||
}
|
||||
|
@ -4,17 +4,22 @@ import java.sql.Connection;
|
||||
|
||||
import ai.chat2db.spi.DBManage;
|
||||
import ai.chat2db.spi.jdbc.DefaultDBManage;
|
||||
import ai.chat2db.spi.sql.Chat2DBContext;
|
||||
import ai.chat2db.spi.sql.ConnectInfo;
|
||||
import ai.chat2db.spi.sql.SQLExecutor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
|
||||
@Override
|
||||
public void connectDatabase(Connection connection, String database) {
|
||||
//try {
|
||||
// SQLExecutor.getInstance().execute(connection,"SELECT pg_database_size('"+database+"');");
|
||||
//} catch (SQLException e) {
|
||||
// throw new RuntimeException(e);
|
||||
//}
|
||||
try {
|
||||
ConnectInfo connectInfo = Chat2DBContext.getConnectInfo();
|
||||
if (!StringUtils.isEmpty(connectInfo.getSchemaName())) {
|
||||
SQLExecutor.getInstance().execute(connection, "SET search_path TO \"" + connectInfo.getSchemaName() + "\"");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -26,7 +31,7 @@ public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
|
||||
}
|
||||
connectInfo.setUrl(url);
|
||||
|
||||
return super.getConnection(connectInfo);
|
||||
return super.getConnection(connectInfo);
|
||||
}
|
||||
|
||||
|
||||
@ -53,8 +58,8 @@ public class PostgreSQLDBManage extends DefaultDBManage implements DBManage {
|
||||
|
||||
@Override
|
||||
public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) {
|
||||
String sql = "DROP TABLE "+ tableName;
|
||||
SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null);
|
||||
String sql = "DROP TABLE " + tableName;
|
||||
SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ public class SqlServerDBManage extends DefaultDBManage implements DBManage {
|
||||
@Override
|
||||
public void connectDatabase(Connection connection, String database) {
|
||||
try {
|
||||
SQLExecutor.getInstance().execute(connection,"use [" + database + "];");
|
||||
SQLExecutor.getInstance().execute(connection, "use [" + database + "];");
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
|
||||
sqlBuilder.append("\ngo\n");
|
||||
if (StringUtils.isNotBlank(database.getComment())) {
|
||||
sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','")
|
||||
.append(database.getComment()).append("'").append("'\ngo\n");
|
||||
.append(database.getComment()).append("'").append("\ngo\n");
|
||||
}
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
@ -166,7 +166,7 @@ public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder
|
||||
if (StringUtils.isNotBlank(schema.getComment())) {
|
||||
sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','")
|
||||
.append(schema.getComment()).append("'").append(",'SCHEMA'")
|
||||
.append(",'").append(schema.getName()).append("'").append("'\ngo\n");
|
||||
.append(",'").append(schema.getName()).append("'").append("\ngo\n");
|
||||
}
|
||||
return sqlBuilder.toString();
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer {
|
||||
* 全局放行的url
|
||||
*/
|
||||
private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**",
|
||||
"/api/system", "/login"};
|
||||
"/api/system", "/login", "/api/system/get_latest_version"};
|
||||
|
||||
@Resource
|
||||
private UserService userService;
|
||||
|
@ -93,7 +93,7 @@ public class RdbDmlController {
|
||||
try {
|
||||
boolean flag = true;
|
||||
ExecuteResultVO executeResult = null;
|
||||
connection.setAutoCommit(false);
|
||||
//connection.setAutoCommit(false);
|
||||
ListResult<ExecuteResult> resultDTOListResult = dlTemplateService.execute(param);
|
||||
List<ExecuteResultVO> resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData());
|
||||
if (!CollectionUtils.isEmpty(resultVOS)) {
|
||||
@ -107,7 +107,7 @@ public class RdbDmlController {
|
||||
}
|
||||
}
|
||||
if (flag) {
|
||||
connection.commit();
|
||||
//connection.commit();
|
||||
return DataResult.of(resultVOS.get(0));
|
||||
}else {
|
||||
connection.rollback();
|
||||
|
@ -108,7 +108,7 @@ public class DatabaseExportService {
|
||||
try {
|
||||
export(outputStream, exportOptions);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("导出失败!请联系开发者,邮箱:963565242@qq.com" + e);
|
||||
throw new RuntimeException("导出失败!请联系开发者" + e);
|
||||
}
|
||||
init();
|
||||
}
|
||||
|
Reference in New Issue
Block a user