Commit 67b9ea7e authored by 王昆's avatar 王昆

import from dbs in ziwei

parent ebe1acd9
async function decryptDataWithPrivateKey(privateKey, encryptedBase64) {
// 将 Base64 编码的加密数据解码为 ArrayBuffer
const encryptedData = base64ToArrayBuffer(encryptedBase64);
// 使用 RSA-OAEP 解密数据
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: "RSA-OAEP" },
privateKey, // 使用私钥解密
encryptedData
);
// 将解密后的数据转换为字符串
return new TextDecoder().decode(decryptedBuffer);
}
// 将 Base64 转换为 ArrayBuffer
function base64ToArrayBuffer(base64) {
const binaryString = atob(base64);
const length = binaryString.length;
const arrayBuffer = new ArrayBuffer(length);
const uint8Array = new Uint8Array(arrayBuffer);
for (let i = 0; i < length; i++) {
uint8Array[i] = binaryString.charCodeAt(i);
}
return arrayBuffer;
}
async function importPrivateKey(privateKeyBase64) {
// 1. Base64 解码(转换为 ArrayBuffer)
const binaryDer = atob(privateKeyBase64);
const keyBuffer = new Uint8Array(binaryDer.length).map((_, i) => binaryDer.charCodeAt(i));
// 2. 使用 Web Crypto API 解析 PKCS#8 私钥
return await crypto.subtle.importKey(
"pkcs8",
keyBuffer.buffer,
{ name: "RSA-OAEP", hash: "SHA-256" }, // 确保 Java 端使用 "RSA-OAEP" 加密
true,
["decrypt"]
);
}
async function decryptMessage(privateKey, encryptedAesKey) {
// 导入私钥
const pk = await importPrivateKey(privateKey);
// console.log("私钥解析成功: ", pk);
// 用私钥解密数据
const decryptedData = await decryptDataWithPrivateKey(pk, encryptedAesKey);
// console.log("解密后的数据:", decryptedData);
return decryptedData;
}
export { decryptMessage }
\ No newline at end of file
import { Modal, Notification, Select, Tabs } from '@arco-design/web-react'; import { Modal, Notification, Select, Tabs, Spin } from '@arco-design/web-react';
import { Parser } from '@dbml/core'; import { Parser } from '@dbml/core';
import { useRef, useState } from 'react'; import { useRef, useState } from 'react';
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
...@@ -12,6 +12,8 @@ import { useEffect } from 'react'; ...@@ -12,6 +12,8 @@ import { useEffect } from 'react';
import { useMemo } from 'react'; import { useMemo } from 'react';
import ConnectDb from '@/client/api/connectDb'; import ConnectDb from '@/client/api/connectDb';
import useSWRMutation from 'swr/mutation'; import useSWRMutation from 'swr/mutation';
import { useSearchParams } from 'next/navigation';
import { decryptMessage } from './decrypt';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
...@@ -62,8 +64,8 @@ export function DBForm({ ...@@ -62,8 +64,8 @@ export function DBForm({
style={{ width: '100%' }} style={{ width: '100%' }}
initialValues={initialValues} initialValues={initialValues}
autoComplete="off" autoComplete="off"
onValuesChange={(v, vs) => {}} onValuesChange={(v, vs) => { }}
onSubmit={v => {}} onSubmit={v => { }}
className="p-[20px]" className="p-[20px]"
> >
<FormItem label={t('Connection Information')} rules={[{ required: true }]}> <FormItem label={t('Connection Information')} rules={[{ required: true }]}>
...@@ -92,6 +94,10 @@ export function DBForm({ ...@@ -92,6 +94,10 @@ export function DBForm({
label: 'mysql', label: 'mysql',
value: 'mysql2', value: 'mysql2',
}, },
{
label: 'oracle',
value: 'oracle',
},
]} ]}
></Select> ></Select>
</Form.Item> </Form.Item>
...@@ -168,7 +174,7 @@ function bfs(nodes) { ...@@ -168,7 +174,7 @@ function bfs(nodes) {
* It's a modal that allows you to import a graph from a string * It's a modal that allows you to import a graph from a string
* @returns Modal component * @returns Modal component
*/ */
export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, type, importDBML }) { export default function ImportModal({ showModal, onCloseModal, cb = _p => { }, type, importDBML }) {
const { t } = useTranslation('modal'); const { t } = useTranslation('modal');
const { theme, setTableDict, setLinkDict, tableList } = graphState.useContainer(); const { theme, setTableDict, setLinkDict, tableList } = graphState.useContainer();
const { calcXY } = tableModel(); const { calcXY } = tableModel();
...@@ -205,6 +211,8 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty ...@@ -205,6 +211,8 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty
} else if (type === 4) { } else if (type === 4) {
console.log('excel'); console.log('excel');
setImportType('excel'); setImportType('excel');
} else if (type === 5) {
setImportType('ziwei');
} }
}, [type]); }, [type]);
...@@ -222,7 +230,12 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty ...@@ -222,7 +230,12 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty
console.log(lists, 'lists'); console.log(lists, 'lists');
if (!value && !(lists.length && type === 4)) { if (!value && !(lists.length && type === 4)) {
if (type === 5) {
const formValues = form.getFields();
trigger(formValues);
} else {
onCloseModal(); onCloseModal();
}
return; return;
} }
try { try {
...@@ -417,6 +430,62 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty ...@@ -417,6 +430,62 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty
); );
const [lists, setList] = useState([]); const [lists, setList] = useState([]);
const [selectedDbIndex, setSelectedDbIndex] = useState(0);
const [dbList, setDbList] = useState([]);
const searchParams = useSearchParams();
const applicationId = searchParams.get('applicationId');
useEffect(() => {
if (type === 5 && applicationId) {
setLoading(true); // 开始加载时显示loading
fetch(`/ziwei/api/v1/datasources?applicationId=${applicationId}`, {
method: "GET",
credentials: "include",
}).finally(() => {
setLoading(false); // 请求完成后隐藏loading
})
.then(res => res.json())
.then(async data => {
if (data?.data && Array.isArray(data?.data)) {
const formattedData = await Promise.all(data.data.map(async item => {
// 从 datasourceConfiguration 中获取连接信息
const endpoint = item.datasourceConfiguration?.endpoints?.[0] || {};
// 获取认证信息,包含加密的密钥
const auth = item.datasourceConfiguration?.authentication || {};
// 如果存在加密密钥,需要进行解密
if (auth.privateKey && auth.encryptedAesKey) {
try {
// 解密密码
auth.password = await decryptMessage(auth.privateKey, auth.encryptedAesKey)
} catch (error) {
console.error('Failed to decrypt keys:', error);
Message.error(t('Failed to decrypt database credentials'));
}
}
return {
host: endpoint.host || '127.0.0.1',
port: endpoint.port || 3306,
user: auth.username || 'root',
password: auth.password || '',
database: auth.databaseName || '',
name: item.name || '',
client: item.dbType === 1 ? 'oracle' : 'mysql2' // 根据 dbType 判断数据库类型
};
}));
setDbList(formattedData);
if (formattedData.length > 0) {
form.setFieldsValue(formattedData[0]);
}
}
})
.catch(error => {
console.error('Error fetching datasources:', error);
Message.error(t('Failed to fetch datasources'));
});
}
}, [type, applicationId]);
const tabs = useMemo(() => { const tabs = useMemo(() => {
const result = []; const result = [];
if (type === 1 || !type) { if (type === 1 || !type) {
...@@ -449,9 +518,43 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty ...@@ -449,9 +518,43 @@ export default function ImportModal({ showModal, onCloseModal, cb = _p => {}, ty
<ImportExcel setList={setList} /> <ImportExcel setList={setList} />
</TabPane> </TabPane>
); );
} else if (type === 5) {
result.push(
<TabPane key="ziwei" title="紫微数据库">
<div className="space-y-4">
{loading ? (
<div className="flex items-center justify-center h-[360px]">
<Spin />
</div>
) : (
<>
<Select
placeholder="请选择数据库"
value={selectedDbIndex}
onChange={value => {
setSelectedDbIndex(value);
form.setFieldsValue(dbList[value]);
}}
style={{ width: '100%', marginBottom: '16px' }}
>
{dbList.map((db, index) => (
<Select.Option key={index} value={index}>
{db.name} ({db.host}:{db.port})
</Select.Option>
))}
</Select>
<DBForm
form={form}
initialValues={dbList[selectedDbIndex]}
/>
</>
)}
</div>
</TabPane>
);
} }
return result; return result;
}, [type]); }, [type, selectedDbIndex, dbList, loading]);
return ( return (
<Modal <Modal
title={null} title={null}
......
...@@ -38,7 +38,10 @@ export default function ListNav({ ...@@ -38,7 +38,10 @@ export default function ListNav({
key={0} key={0}
trigger={'hover'} trigger={'hover'}
droplist={ droplist={
<Menu> <Menu style={{ maxHeight: 'none', overflow: 'visible' }}>
<Menu.Item key="ziwei" onClick={() => importGraph(5)}>
{t('Import from Ziwei')}
</Menu.Item>
<Menu.Item key="3" onClick={() => importGraph(3)}> <Menu.Item key="3" onClick={() => importGraph(3)}>
{t('Import Database')} {t('Import Database')}
</Menu.Item> </Menu.Item>
...@@ -49,12 +52,9 @@ export default function ListNav({ ...@@ -49,12 +52,9 @@ export default function ListNav({
{t('Import DDL')} {t('Import DDL')}
</Menu.Item> </Menu.Item>
<Menu.Item key="1" onClick={() => setSimpleModeVisible(true)}> <Menu.Item key="1" onClick={() => setSimpleModeVisible(true)}>
{/* <span className=" text-lime-600 mr-[10px]">
<IconRobot />
</span> */}
{t('Created by AI')} {t('Created by AI')}
</Menu.Item> </Menu.Item>
<Menu.Item key="1" onClick={() => addGraph()}> <Menu.Item key="5" onClick={() => addGraph()}>
{t('Create from Blank')} {t('Create from Blank')}
</Menu.Item> </Menu.Item>
</Menu> </Menu>
......
...@@ -30,6 +30,7 @@ i18n.use(LanguageDetector) ...@@ -30,6 +30,7 @@ i18n.use(LanguageDetector)
'Import DBML': '导入DBML', 'Import DBML': '导入DBML',
'Import DDL': '导入DDL', 'Import DDL': '导入DDL',
'Import excel': '导入excel', 'Import excel': '导入excel',
'Import from Ziwei': '从紫微导入',
'Create from Blank': '从空白新建', 'Create from Blank': '从空白新建',
'New Model': '新建模型', 'New Model': '新建模型',
'Import Example': '导入示例', 'Import Example': '导入示例',
......
...@@ -21,6 +21,10 @@ const rewrites = async () => { ...@@ -21,6 +21,10 @@ const rewrites = async () => {
destination: `${process.env.NEXT_PUBLIC_BACKEND_URL}/:path*`, destination: `${process.env.NEXT_PUBLIC_BACKEND_URL}/:path*`,
// destination: 'http://139.198.179.193:30981/:path*', // destination: 'http://139.198.179.193:30981/:path*',
}, },
{
source: '/ziwei/:path*',
destination: `https://staging.fusiontech.cn/:path*`,
},
]; ];
return { return {
......
...@@ -27,7 +27,7 @@ ...@@ -27,7 +27,7 @@
"@antv/x6-vue-shape": "^2.0.0", "@antv/x6-vue-shape": "^2.0.0",
"@arco-design/web-react": "^2.45.0", "@arco-design/web-react": "^2.45.0",
"@dbml/core": "^2.5.1", "@dbml/core": "^2.5.1",
"@digitalocean/do-markdownit": "^1.9.0", "@digitalocean/do-markdownit": "1.16.0",
"@formily/core": "^2.2.29", "@formily/core": "^2.2.29",
"@formily/react": "^2.2.29", "@formily/react": "^2.2.29",
"@koale/useworker": "^4.0.2", "@koale/useworker": "^4.0.2",
...@@ -63,6 +63,7 @@ ...@@ -63,6 +63,7 @@
"pinyin": "3.0.0-alpha.5", "pinyin": "3.0.0-alpha.5",
"postcss-flexbugs-fixes": "^5.0.2", "postcss-flexbugs-fixes": "^5.0.2",
"postcss-preset-env": "^8.0.1", "postcss-preset-env": "^8.0.1",
"prismjs": "^1.30.0",
"react": "18.2.0", "react": "18.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1", "react-dnd-html5-backend": "^16.0.1",
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment