添加配置保存和加解密
This commit is contained in:
parent
f21297fa3a
commit
88fa8d3ad0
43
package-lock.json
generated
43
package-lock.json
generated
@ -10,16 +10,20 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xyflow/react": "^12.10.0",
|
"@xyflow/react": "^12.10.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-switch": "^7.1.0",
|
"react-switch": "^7.1.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
"tweetnacl": "^1.0.3"
|
"tweetnacl": "^1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/base64-js": "^1.3.2",
|
"@types/base64-js": "^1.3.2",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
@ -61,7 +65,6 @@
|
|||||||
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.28.6",
|
"@babel/code-frame": "^7.28.6",
|
||||||
"@babel/generator": "^7.28.6",
|
"@babel/generator": "^7.28.6",
|
||||||
@ -1564,6 +1567,13 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/crypto-js": {
|
||||||
|
"version": "4.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
|
||||||
|
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/d3-color": {
|
"node_modules/@types/d3-color": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
|
||||||
@ -1638,7 +1648,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz",
|
||||||
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
"integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"csstype": "^3.2.2"
|
"csstype": "^3.2.2"
|
||||||
}
|
}
|
||||||
@ -1721,7 +1730,6 @@
|
|||||||
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
},
|
},
|
||||||
@ -1861,7 +1869,6 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"baseline-browser-mapping": "^2.9.0",
|
"baseline-browser-mapping": "^2.9.0",
|
||||||
"caniuse-lite": "^1.0.30001759",
|
"caniuse-lite": "^1.0.30001759",
|
||||||
@ -1923,6 +1930,12 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/class-transformer": {
|
||||||
|
"version": "0.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
|
||||||
|
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/classcat": {
|
"node_modules/classcat": {
|
||||||
"version": "5.0.5",
|
"version": "5.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz",
|
||||||
@ -2003,12 +2016,17 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/crypto-js": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.2.3",
|
"version": "3.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/d3-color": {
|
"node_modules/d3-color": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
@ -2067,7 +2085,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
|
||||||
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
"integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
@ -2236,7 +2253,6 @@
|
|||||||
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@eslint-community/eslint-utils": "^4.8.0",
|
"@eslint-community/eslint-utils": "^4.8.0",
|
||||||
"@eslint-community/regexpp": "^4.12.1",
|
"@eslint-community/regexpp": "^4.12.1",
|
||||||
@ -3055,7 +3071,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@ -3128,7 +3143,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
|
||||||
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
@ -3138,7 +3152,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
|
||||||
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"scheduler": "^0.27.0"
|
"scheduler": "^0.27.0"
|
||||||
},
|
},
|
||||||
@ -3229,6 +3242,12 @@
|
|||||||
"react-dom": ">=16.6.0"
|
"react-dom": ">=16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reflect-metadata": {
|
||||||
|
"version": "0.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
|
||||||
|
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.11",
|
"version": "1.22.11",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
|
||||||
@ -3524,7 +3543,6 @@
|
|||||||
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"esbuild": "^0.27.0",
|
"esbuild": "^0.27.0",
|
||||||
"fdir": "^6.5.0",
|
"fdir": "^6.5.0",
|
||||||
@ -3646,7 +3664,6 @@
|
|||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,16 +12,20 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xyflow/react": "^12.10.0",
|
"@xyflow/react": "^12.10.0",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
"react-hot-toast": "^2.6.0",
|
"react-hot-toast": "^2.6.0",
|
||||||
"react-select": "^5.10.2",
|
"react-select": "^5.10.2",
|
||||||
"react-switch": "^7.1.0",
|
"react-switch": "^7.1.0",
|
||||||
|
"reflect-metadata": "^0.2.2",
|
||||||
"tweetnacl": "^1.0.3"
|
"tweetnacl": "^1.0.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.39.1",
|
"@eslint/js": "^9.39.1",
|
||||||
"@types/base64-js": "^1.3.2",
|
"@types/base64-js": "^1.3.2",
|
||||||
|
"@types/crypto-js": "^4.2.2",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
"@types/react-dom": "^19.2.3",
|
"@types/react-dom": "^19.2.3",
|
||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
|
|||||||
82
src/App.tsx
82
src/App.tsx
@ -19,15 +19,19 @@ import {
|
|||||||
} from '@xyflow/react';
|
} from '@xyflow/react';
|
||||||
import '@xyflow/react/dist/style.css';
|
import '@xyflow/react/dist/style.css';
|
||||||
import { AppNode, AppEdge, NodeData, EdgeData, NodeDataUpdate, EdgeDataUpdate } from './types/graph';
|
import { AppNode, AppEdge, NodeData, EdgeData, NodeDataUpdate, EdgeDataUpdate } from './types/graph';
|
||||||
import {Settings, initialSettings, SettingsContext} from './types/settings'
|
import {Settings, SettingsContext} from './types/settings'
|
||||||
import CustomNode from './components/CustomNode';
|
import CustomNode from './components/CustomNode';
|
||||||
import NodeEditor from './components/NodeEditor';
|
import NodeEditor from './components/NodeEditor';
|
||||||
import EdgeEditor from './components/EdgeEditor'
|
import EdgeEditor from './components/EdgeEditor'
|
||||||
import SettingsEditor from './components/SettingsEditor'
|
import SettingsEditor from './components/SettingsEditor'
|
||||||
import Toggle from "./components/Toggle"
|
import Toggle from "./components/Toggle"
|
||||||
import { generateWireGuardPrivateKey } from './utils/wireguardConfig';
|
import { generateWireGuardPrivateKey } from './utils/wireguardConfig';
|
||||||
|
import CryptoJS from 'crypto-js';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { Toaster } from 'react-hot-toast';
|
import toast, { Toaster } from 'react-hot-toast';
|
||||||
|
import SaveConfig from './types/saveConfig'
|
||||||
|
import { plainToInstance, instanceToPlain } from 'class-transformer';
|
||||||
|
|
||||||
|
|
||||||
const initialNodes: AppNode[] = [];
|
const initialNodes: AppNode[] = [];
|
||||||
const initialEdges: AppEdge[] = [];
|
const initialEdges: AppEdge[] = [];
|
||||||
@ -49,7 +53,7 @@ function generateNodeData(count: number) : NodeData | null {
|
|||||||
function FlowContent(): ReactNode {
|
function FlowContent(): ReactNode {
|
||||||
const [nodes, setNodes] = useState<AppNode[]>(initialNodes);
|
const [nodes, setNodes] = useState<AppNode[]>(initialNodes);
|
||||||
const [edges, setEdges] = useState<AppEdge[]>(initialEdges);
|
const [edges, setEdges] = useState<AppEdge[]>(initialEdges);
|
||||||
const [settings, setSettings] = useState<Settings>(initialSettings);
|
const [settings, setSettings] = useState<Settings>(new Settings());
|
||||||
|
|
||||||
const [editingNode, setEditingNode] = useState<NodeData | undefined>(undefined);
|
const [editingNode, setEditingNode] = useState<NodeData | undefined>(undefined);
|
||||||
const [editingEdge, setEditingEdge] = useState<EdgeData | undefined>(undefined);
|
const [editingEdge, setEditingEdge] = useState<EdgeData | undefined>(undefined);
|
||||||
@ -143,6 +147,70 @@ function FlowContent(): ReactNode {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSaveConfig = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const pass = window.prompt('输入用于加密私钥的密码(留空则不加密):');
|
||||||
|
const saveConfig: SaveConfig = {settings: settings, nodes: [], edges: [], encrypted: false}
|
||||||
|
if(pass) saveConfig.encrypted = true;
|
||||||
|
|
||||||
|
nodes.forEach(node => {
|
||||||
|
const nodeData = node.data;
|
||||||
|
let privateKey = nodeData.privateKey;
|
||||||
|
if(pass) privateKey = CryptoJS.AES.encrypt(privateKey, pass).toString();
|
||||||
|
saveConfig.nodes.push({...node, data: {...nodeData, privateKey: privateKey}});
|
||||||
|
});
|
||||||
|
|
||||||
|
edges.forEach(edge => {
|
||||||
|
saveConfig.edges.push(edge);
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = instanceToPlain(saveConfig);
|
||||||
|
const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = 'wg-config.json';
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('保存失败: ' + e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLoadConfig = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.accept = 'application/json';
|
||||||
|
input.onchange = async () => {
|
||||||
|
const file = input.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
const jsonString = await file.text();
|
||||||
|
const plainObject = JSON.parse(jsonString);
|
||||||
|
const saveConfig: SaveConfig = plainToInstance(SaveConfig, plainObject);
|
||||||
|
|
||||||
|
if (saveConfig.encrypted) {
|
||||||
|
const pass = window.prompt('输入用于解密私钥的密码:');
|
||||||
|
if (!pass) { toast.error('需要密码以解密私钥'); return; }
|
||||||
|
try {
|
||||||
|
for(let node of saveConfig.nodes) {
|
||||||
|
node.data.privateKey = CryptoJS.AES.decrypt(node.data.privateKey, pass).toString(CryptoJS.enc.Utf8);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
toast.error('解密失败: ' + err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setSettings(saveConfig.settings);
|
||||||
|
setNodes(saveConfig.nodes);
|
||||||
|
setEdges(saveConfig.edges);
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
|
} catch (e) {
|
||||||
|
toast.error('加载失败: ' + e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width: '100vw', height: '100vh' }}>
|
<div style={{ width: '100vw', height: '100vh' }}>
|
||||||
<Toaster/>
|
<Toaster/>
|
||||||
@ -188,6 +256,14 @@ function FlowContent(): ReactNode {
|
|||||||
📋 设置
|
📋 设置
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button className="toolbar-btn" onClick={handleSaveConfig} title="保存配置">
|
||||||
|
💾 保存配置
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button className="toolbar-btn" onClick={handleLoadConfig} title="加载配置">
|
||||||
|
📂 加载配置
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
import { StrictMode } from 'react'
|
import { StrictMode } from 'react'
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|||||||
14
src/types/saveConfig.ts
Normal file
14
src/types/saveConfig.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { Settings } from './settings';
|
||||||
|
import { AppNode, AppEdge } from './graph';
|
||||||
|
|
||||||
|
export default class SaveConfig {
|
||||||
|
encrypted: boolean = false;
|
||||||
|
|
||||||
|
@Type(() => Settings)
|
||||||
|
settings: Settings = new Settings();
|
||||||
|
|
||||||
|
nodes: AppNode[] = [];
|
||||||
|
|
||||||
|
edges: AppEdge[] = [];
|
||||||
|
}
|
||||||
@ -1,22 +1,28 @@
|
|||||||
|
import { Type } from 'class-transformer';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
import { CIDR } from '../utils/iputils';
|
import { CIDR } from '../utils/iputils';
|
||||||
|
|
||||||
export interface SubnetInfo {
|
export class NodeToCIDR {
|
||||||
subnet: CIDR;
|
nodeId: string = '';
|
||||||
nodes: Array<{nodeId: string, cidr: CIDR | undefined}>;
|
|
||||||
|
@Type(() => CIDR) // 必须显式标记,即使是 undefined 也要处理
|
||||||
|
cidr: CIDR | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export class SubnetInfo {
|
||||||
listenPort: number;
|
@Type(() => CIDR)
|
||||||
mtu: number;
|
subnet!: CIDR;
|
||||||
|
|
||||||
subnets: SubnetInfo[];
|
@Type(() => NodeToCIDR)
|
||||||
|
nodes: NodeToCIDR[] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialSettings : Settings = {
|
export class Settings {
|
||||||
listenPort: 38894,
|
listenPort: number = 38894;
|
||||||
mtu: 1420,
|
mtu: number = 1420;
|
||||||
subnets: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SettingsContext = createContext<Settings>(initialSettings);
|
@Type(() => SubnetInfo)
|
||||||
|
subnets: SubnetInfo[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettingsContext = createContext<Settings>(new Settings());
|
||||||
|
|||||||
@ -20,7 +20,9 @@
|
|||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user