mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-08-28 19:46:41 +08:00
feat(ConfigGroup, SystemConfig): add support for secret fields in configuration forms
This commit is contained in:
@@ -195,6 +195,7 @@ export interface ConfigResponse {
|
|||||||
key: string;
|
key: string;
|
||||||
value: string;
|
value: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
isSecret: boolean;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Form, Input, Button, Space, Row, Col, Tooltip } from 'antd';
|
import { Form, Input, Button, Space, Row, Col, Tooltip } from 'antd';
|
||||||
import { SaveOutlined, QuestionCircleOutlined } from '@ant-design/icons';
|
import { SaveOutlined, QuestionCircleOutlined, LockOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
interface ConfigGroupProps {
|
interface ConfigGroupProps {
|
||||||
groupName: string;
|
groupName: string;
|
||||||
@@ -11,6 +11,7 @@ interface ConfigGroupProps {
|
|||||||
descriptions: {
|
descriptions: {
|
||||||
[key: string]: string;
|
[key: string]: string;
|
||||||
};
|
};
|
||||||
|
secretFields?: string[];
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
|||||||
configs,
|
configs,
|
||||||
onSave,
|
onSave,
|
||||||
descriptions,
|
descriptions,
|
||||||
|
secretFields = [],
|
||||||
isMobile = false
|
isMobile = false
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -45,6 +47,10 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isSecretField = (key: string): boolean => {
|
||||||
|
return secretFields.includes(key);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
@@ -52,49 +58,61 @@ const ConfigGroup: React.FC<ConfigGroupProps> = ({
|
|||||||
initialValues={configs}
|
initialValues={configs}
|
||||||
size={isMobile ? "middle" : "large"}
|
size={isMobile ? "middle" : "large"}
|
||||||
>
|
>
|
||||||
{Object.keys(configs).map(key => (
|
{Object.keys(configs).map(key => {
|
||||||
<Row key={key} gutter={isMobile ? [8, 8] : [16, 16]} align="middle">
|
const isSecret = isSecretField(key);
|
||||||
<Col xs={24} lg={16}>
|
|
||||||
<Form.Item
|
return (
|
||||||
name={key}
|
<Row key={key} gutter={isMobile ? [8, 8] : [16, 16]} align="middle">
|
||||||
label={
|
<Col xs={24} lg={16}>
|
||||||
<Space>
|
<Form.Item
|
||||||
{key}
|
name={key}
|
||||||
{descriptions[key] && (
|
label={
|
||||||
<Tooltip title={descriptions[key]}>
|
<Space>
|
||||||
<QuestionCircleOutlined />
|
{key}
|
||||||
</Tooltip>
|
{isSecret && <LockOutlined style={{ color: '#faad14' }} />}
|
||||||
)}
|
{descriptions[key] && (
|
||||||
</Space>
|
<Tooltip title={descriptions[key]}>
|
||||||
}
|
<QuestionCircleOutlined />
|
||||||
>
|
</Tooltip>
|
||||||
{key.toLowerCase().includes('secret') || key.toLowerCase().includes('key') || key.toLowerCase().includes('password') ? (
|
)}
|
||||||
<Input.Password placeholder={`请输入${key}`} />
|
</Space>
|
||||||
) : (
|
}
|
||||||
<Input placeholder={`请输入${key}`} />
|
extra={isSecret &&
|
||||||
)}
|
<div style={{ fontSize: '12px', color: '#faad14', marginTop: '4px' }}>
|
||||||
</Form.Item>
|
此为私密字段,出于安全考虑不显示实际值。如需修改,请输入新值。
|
||||||
</Col>
|
</div>
|
||||||
<Col xs={24} lg={8} style={{
|
}
|
||||||
textAlign: isMobile ? 'left' : 'right',
|
>
|
||||||
marginTop: isMobile ? -10 : 0,
|
{isSecret ? (
|
||||||
marginBottom: isMobile ? 10 : 0
|
<Input.Password
|
||||||
}}>
|
placeholder={configs[key] === '' ? '请输入新值' : '******(已设置,输入新值以更新)'}
|
||||||
<Button
|
/>
|
||||||
type="primary"
|
) : (
|
||||||
icon={<SaveOutlined />}
|
<Input placeholder={`请输入${key}`} />
|
||||||
onClick={() => handleSaveSingle(key)}
|
)}
|
||||||
style={{
|
</Form.Item>
|
||||||
marginBottom: isMobile ? 16 : 24,
|
</Col>
|
||||||
width: isMobile ? '100%' : 'auto'
|
<Col xs={24} lg={8} style={{
|
||||||
}}
|
textAlign: isMobile ? 'left' : 'right',
|
||||||
size={isMobile ? "middle" : "large"}
|
marginTop: isMobile ? -10 : 0,
|
||||||
>
|
marginBottom: isMobile ? 10 : 0
|
||||||
保存
|
}}>
|
||||||
</Button>
|
<Button
|
||||||
</Col>
|
type="primary"
|
||||||
</Row>
|
icon={<SaveOutlined />}
|
||||||
))}
|
onClick={() => handleSaveSingle(key)}
|
||||||
|
style={{
|
||||||
|
marginBottom: isMobile ? 16 : 24,
|
||||||
|
width: isMobile ? '100%' : 'auto'
|
||||||
|
}}
|
||||||
|
size={isMobile ? "middle" : "large"}
|
||||||
|
>
|
||||||
|
保存
|
||||||
|
</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
const [restoreLoading, setRestoreLoading] = useState(false);
|
const [restoreLoading, setRestoreLoading] = useState(false);
|
||||||
const [restoreModalVisible, setRestoreModalVisible] = useState(false);
|
const [restoreModalVisible, setRestoreModalVisible] = useState(false);
|
||||||
const [restoreConfig, setRestoreConfig] = useState<Record<string, string> | null>(null);
|
const [restoreConfig, setRestoreConfig] = useState<Record<string, string> | null>(null);
|
||||||
|
const [secretFields, setSecretFields] = useState<Record<string, string[]>>({}); // 新增状态管理私密字段
|
||||||
|
|
||||||
// 获取所有配置项
|
// 获取所有配置项
|
||||||
const fetchConfigs = async () => {
|
const fetchConfigs = async () => {
|
||||||
@@ -32,15 +33,27 @@ const SystemConfig: React.FC = () => {
|
|||||||
const response = await getAllConfigs();
|
const response = await getAllConfigs();
|
||||||
if (response.success && response.data) {
|
if (response.success && response.data) {
|
||||||
const configGroups: ConfigStructure = {};
|
const configGroups: ConfigStructure = {};
|
||||||
|
const secretFieldsMap: Record<string, string[]> = {}; // 记录每个组的私密字段
|
||||||
|
|
||||||
response.data.forEach(config => {
|
response.data.forEach(config => {
|
||||||
const [group, key] = config.key.split(':');
|
const [group, key] = config.key.split(':');
|
||||||
if (!configGroups[group]) {
|
if (!configGroups[group]) {
|
||||||
configGroups[group] = {};
|
configGroups[group] = {};
|
||||||
|
secretFieldsMap[group] = [];
|
||||||
}
|
}
|
||||||
configGroups[group][key] = config.value;
|
configGroups[group][key] = config.value;
|
||||||
|
|
||||||
|
// 记录私密字段
|
||||||
|
if (config.isSecret) {
|
||||||
|
if (!secretFieldsMap[group]) {
|
||||||
|
secretFieldsMap[group] = [];
|
||||||
|
}
|
||||||
|
secretFieldsMap[group].push(key);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setConfigs(configGroups);
|
setConfigs(configGroups);
|
||||||
|
setSecretFields(secretFieldsMap);
|
||||||
|
|
||||||
// 设置初始存储类型
|
// 设置初始存储类型
|
||||||
if (configGroups.Storage?.DefaultStorage) {
|
if (configGroups.Storage?.DefaultStorage) {
|
||||||
@@ -245,6 +258,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
Model: 'AI 模型名称',
|
Model: 'AI 模型名称',
|
||||||
EmbeddingModel: '嵌入向量模型名称'
|
EmbeddingModel: '嵌入向量模型名称'
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.AI || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
@@ -265,6 +279,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
Issuer: 'JWT 签发者',
|
Issuer: 'JWT 签发者',
|
||||||
Audience: 'JWT 接收者',
|
Audience: 'JWT 接收者',
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Jwt || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
@@ -282,6 +297,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥',
|
"GitHubClientSecret": 'GitHub OAuth 应用客户端密钥',
|
||||||
"GitHubCallbackUrl": 'GitHub OAuth 认证回调地址'
|
"GitHubCallbackUrl": 'GitHub OAuth 认证回调地址'
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Authentication || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
</TabPane>
|
</TabPane>
|
||||||
@@ -543,6 +559,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
"TelegramStorageBotToken": 'Telegram 机器人令牌',
|
"TelegramStorageBotToken": 'Telegram 机器人令牌',
|
||||||
"TelegramStorageChatId": 'Telegram 聊天ID'
|
"TelegramStorageChatId": 'Telegram 聊天ID'
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Storage || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -569,6 +586,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
"S3StorageCdnUrl": 'CDN URL (可选,用于加速文件访问)',
|
"S3StorageCdnUrl": 'CDN URL (可选,用于加速文件访问)',
|
||||||
"S3StorageUsePathStyleUrls": '使用路径形式URLs (true/false,兼容非AWS服务)'
|
"S3StorageUsePathStyleUrls": '使用路径形式URLs (true/false,兼容非AWS服务)'
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Storage || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -593,6 +611,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
"CosStorageRegion": 'COS区域 (例如:ap-shanghai)',
|
"CosStorageRegion": 'COS区域 (例如:ap-shanghai)',
|
||||||
"CosStorageCdnUrl": 'CDN URL (可选,用于加速文件访问)',
|
"CosStorageCdnUrl": 'CDN URL (可选,用于加速文件访问)',
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Storage || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -615,6 +634,7 @@ const SystemConfig: React.FC = () => {
|
|||||||
"WebDAVBasePath": 'WebDAV 基础路径 (例如: files/upload)',
|
"WebDAVBasePath": 'WebDAV 基础路径 (例如: files/upload)',
|
||||||
"WebDAVPublicUrl": 'WebDAV 公共访问 URL (可选,用于文件访问)',
|
"WebDAVPublicUrl": 'WebDAV 公共访问 URL (可选,用于文件访问)',
|
||||||
}}
|
}}
|
||||||
|
secretFields={secretFields.Storage || []}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user