356 lines
13 KiB
TypeScript
356 lines
13 KiB
TypeScript
import { useState, useEffect } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import styles from './Settings.module.css';
|
||
|
||
interface Settings {
|
||
messageRouterUrl: string;
|
||
accountServiceUrl: string;
|
||
autoBackup: boolean;
|
||
backupPath: string;
|
||
}
|
||
|
||
export default function Settings() {
|
||
const navigate = useNavigate();
|
||
|
||
const [settings, setSettings] = useState<Settings>({
|
||
messageRouterUrl: 'mpc-grpc.szaiai.com:443', // 生产环境默认地址
|
||
accountServiceUrl: 'https://rwaapi.szaiai.com', // Account 服务默认地址
|
||
autoBackup: false,
|
||
backupPath: '',
|
||
});
|
||
const [kavaNetwork, setKavaNetwork] = useState<'mainnet' | 'testnet'>('mainnet');
|
||
const [isLoading, setIsLoading] = useState(true);
|
||
const [isSaving, setIsSaving] = useState(false);
|
||
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||
|
||
useEffect(() => {
|
||
loadSettings();
|
||
}, []);
|
||
|
||
const loadSettings = async () => {
|
||
try {
|
||
const result = await window.electronAPI.storage.getSettings();
|
||
const accountUrl = await window.electronAPI.account.getUrl();
|
||
const networkResult = await window.electronAPI.kava.getNetwork();
|
||
if (result) {
|
||
setSettings({
|
||
...result,
|
||
accountServiceUrl: accountUrl || 'https://rwaapi.szaiai.com',
|
||
});
|
||
}
|
||
setKavaNetwork(networkResult.network);
|
||
} catch (err) {
|
||
console.error('Failed to load settings:', err);
|
||
} finally {
|
||
setIsLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
setIsSaving(true);
|
||
setMessage(null);
|
||
|
||
try {
|
||
await window.electronAPI.storage.saveSettings(settings);
|
||
await window.electronAPI.account.updateUrl(settings.accountServiceUrl);
|
||
setMessage({ type: 'success', text: '设置已保存' });
|
||
} catch (err) {
|
||
setMessage({ type: 'error', text: '保存设置失败' });
|
||
} finally {
|
||
setIsSaving(false);
|
||
}
|
||
};
|
||
|
||
const handleTestConnection = async () => {
|
||
setMessage(null);
|
||
|
||
try {
|
||
const result = await window.electronAPI.grpc.testConnection(settings.messageRouterUrl);
|
||
if (result.success) {
|
||
setMessage({ type: 'success', text: 'Message Router 连接成功' });
|
||
} else {
|
||
setMessage({ type: 'error', text: result.error || 'Message Router 连接失败' });
|
||
}
|
||
} catch (err) {
|
||
setMessage({ type: 'error', text: 'Message Router 连接测试失败' });
|
||
}
|
||
};
|
||
|
||
const handleTestAccountConnection = async () => {
|
||
setMessage(null);
|
||
|
||
try {
|
||
// 先更新 URL
|
||
await window.electronAPI.account.updateUrl(settings.accountServiceUrl);
|
||
const result = await window.electronAPI.account.testConnection();
|
||
if (result.success) {
|
||
setMessage({ type: 'success', text: 'Account 服务连接成功' });
|
||
} else {
|
||
setMessage({ type: 'error', text: result.error || 'Account 服务连接失败' });
|
||
}
|
||
} catch (err) {
|
||
setMessage({ type: 'error', text: 'Account 服务连接测试失败' });
|
||
}
|
||
};
|
||
|
||
const handleSelectBackupPath = async () => {
|
||
try {
|
||
const path = await window.electronAPI.dialog.selectDirectory();
|
||
if (path) {
|
||
setSettings(prev => ({ ...prev, backupPath: path }));
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to select directory:', err);
|
||
}
|
||
};
|
||
|
||
if (isLoading) {
|
||
return (
|
||
<div className={styles.container}>
|
||
<div className={styles.loading}>
|
||
<div className={styles.spinner}></div>
|
||
<p>加载设置...</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className={styles.container}>
|
||
<div className={styles.header}>
|
||
<h1 className={styles.title}>设置</h1>
|
||
</div>
|
||
|
||
<div className={styles.content}>
|
||
{/* 连接设置 */}
|
||
<section className={styles.section}>
|
||
<h2 className={styles.sectionTitle}>连接设置</h2>
|
||
<div className={styles.card}>
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>Message Router 地址</label>
|
||
<div className={styles.inputWithButton}>
|
||
<input
|
||
type="text"
|
||
value={settings.messageRouterUrl}
|
||
onChange={(e) => setSettings(prev => ({ ...prev, messageRouterUrl: e.target.value }))}
|
||
placeholder="mpc-grpc.szaiai.com:443"
|
||
className={styles.input}
|
||
/>
|
||
<button
|
||
className={styles.testButton}
|
||
onClick={handleTestConnection}
|
||
>
|
||
测试连接
|
||
</button>
|
||
</div>
|
||
<p className={styles.hint}>
|
||
输入 Message Router 服务的 gRPC 地址 (生产环境: mpc-grpc.szaiai.com:443)
|
||
</p>
|
||
</div>
|
||
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>Account 服务地址</label>
|
||
<div className={styles.inputWithButton}>
|
||
<input
|
||
type="text"
|
||
value={settings.accountServiceUrl}
|
||
onChange={(e) => setSettings(prev => ({ ...prev, accountServiceUrl: e.target.value }))}
|
||
placeholder="https://rwaapi.szaiai.com"
|
||
className={styles.input}
|
||
/>
|
||
<button
|
||
className={styles.testButton}
|
||
onClick={handleTestAccountConnection}
|
||
>
|
||
测试连接
|
||
</button>
|
||
</div>
|
||
<p className={styles.hint}>
|
||
输入 Account 服务的 HTTP 地址 (生产环境: https://rwaapi.szaiai.com)
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 区块链网络设置 */}
|
||
<section className={styles.section}>
|
||
<h2 className={styles.sectionTitle}>区块链网络</h2>
|
||
<div className={styles.card}>
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>Kava 网络</label>
|
||
<div className={styles.networkToggle}>
|
||
<button
|
||
className={`${styles.networkButton} ${kavaNetwork === 'testnet' ? styles.networkButtonActive : ''}`}
|
||
onClick={async () => {
|
||
const result = await window.electronAPI.kava.switchNetwork('testnet');
|
||
if (result.success) {
|
||
setKavaNetwork('testnet');
|
||
// 同步到 localStorage 供前端工具函数使用
|
||
localStorage.setItem('kava_network', 'testnet');
|
||
// 触发自定义事件通知 Layout 更新网络状态显示
|
||
window.dispatchEvent(new CustomEvent('kava-network-change', { detail: { network: 'testnet' } }));
|
||
setMessage({ type: 'success', text: '已切换到 Kava 测试网' });
|
||
}
|
||
}}
|
||
>
|
||
测试网
|
||
</button>
|
||
<button
|
||
className={`${styles.networkButton} ${kavaNetwork === 'mainnet' ? styles.networkButtonActive : ''}`}
|
||
onClick={async () => {
|
||
const result = await window.electronAPI.kava.switchNetwork('mainnet');
|
||
if (result.success) {
|
||
setKavaNetwork('mainnet');
|
||
// 同步到 localStorage 供前端工具函数使用
|
||
localStorage.setItem('kava_network', 'mainnet');
|
||
// 触发自定义事件通知 Layout 更新网络状态显示
|
||
window.dispatchEvent(new CustomEvent('kava-network-change', { detail: { network: 'mainnet' } }));
|
||
setMessage({ type: 'success', text: '已切换到 Kava 主网' });
|
||
}
|
||
}}
|
||
>
|
||
主网
|
||
</button>
|
||
</div>
|
||
<p className={styles.hint}>
|
||
{kavaNetwork === 'testnet'
|
||
? '当前使用测试网 (kava_2221-16000),适合开发测试'
|
||
: '当前使用主网 (kava_2222-10),请谨慎操作'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 备份设置 */}
|
||
<section className={styles.section}>
|
||
<h2 className={styles.sectionTitle}>备份设置</h2>
|
||
<div className={styles.card}>
|
||
<div className={styles.field}>
|
||
<div className={styles.checkboxField}>
|
||
<input
|
||
type="checkbox"
|
||
id="autoBackup"
|
||
checked={settings.autoBackup}
|
||
onChange={(e) => setSettings(prev => ({ ...prev, autoBackup: e.target.checked }))}
|
||
className={styles.checkbox}
|
||
/>
|
||
<label htmlFor="autoBackup" className={styles.checkboxLabel}>
|
||
自动备份密钥份额
|
||
</label>
|
||
</div>
|
||
<p className={styles.hint}>
|
||
创建新的密钥份额后自动备份到指定目录
|
||
</p>
|
||
</div>
|
||
|
||
{settings.autoBackup && (
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>备份目录</label>
|
||
<div className={styles.inputWithButton}>
|
||
<input
|
||
type="text"
|
||
value={settings.backupPath}
|
||
onChange={(e) => setSettings(prev => ({ ...prev, backupPath: e.target.value }))}
|
||
placeholder="选择备份目录"
|
||
className={styles.input}
|
||
readOnly
|
||
/>
|
||
<button
|
||
className={styles.browseButton}
|
||
onClick={handleSelectBackupPath}
|
||
>
|
||
浏览
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
|
||
{/* 数据管理 */}
|
||
<section className={styles.section}>
|
||
<h2 className={styles.sectionTitle}>数据管理</h2>
|
||
<div className={styles.card}>
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>导出所有数据</label>
|
||
<p className={styles.hint}>
|
||
将所有密钥份额导出为加密文件
|
||
</p>
|
||
<button className={styles.actionButton}>
|
||
导出数据
|
||
</button>
|
||
</div>
|
||
|
||
<div className={styles.divider}></div>
|
||
|
||
<div className={styles.field}>
|
||
<label className={styles.label}>导入数据</label>
|
||
<p className={styles.hint}>
|
||
从加密备份文件导入密钥份额
|
||
</p>
|
||
<button className={styles.actionButton}>
|
||
导入数据
|
||
</button>
|
||
</div>
|
||
|
||
<div className={styles.divider}></div>
|
||
|
||
<div className={styles.field}>
|
||
<label className={styles.labelDanger}>清除所有数据</label>
|
||
<p className={styles.hint}>
|
||
删除所有本地存储的密钥份额。此操作不可恢复!
|
||
</p>
|
||
<button className={styles.dangerButton}>
|
||
清除数据
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 关于 */}
|
||
<section className={styles.section}>
|
||
<h2 className={styles.sectionTitle}>关于</h2>
|
||
<div className={styles.card}>
|
||
<div className={styles.aboutInfo}>
|
||
<div className={styles.aboutItem}>
|
||
<span className={styles.aboutLabel}>应用名称</span>
|
||
<span className={styles.aboutValue}>Service Party</span>
|
||
</div>
|
||
<div className={styles.aboutItem}>
|
||
<span className={styles.aboutLabel}>版本</span>
|
||
<span className={styles.aboutValue}>1.0.0</span>
|
||
</div>
|
||
<div className={styles.aboutItem}>
|
||
<span className={styles.aboutLabel}>项目</span>
|
||
<span className={styles.aboutValue}>RWADurian MPC System</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{message && (
|
||
<div className={`${styles.message} ${styles[message.type]}`}>
|
||
{message.text}
|
||
</div>
|
||
)}
|
||
|
||
<div className={styles.actions}>
|
||
<button
|
||
className={styles.secondaryButton}
|
||
onClick={() => navigate('/')}
|
||
>
|
||
返回
|
||
</button>
|
||
<button
|
||
className={styles.primaryButton}
|
||
onClick={handleSave}
|
||
disabled={isSaving}
|
||
>
|
||
{isSaving ? '保存中...' : '保存设置'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|