338 lines
9.5 KiB
TypeScript
338 lines
9.5 KiB
TypeScript
/**
|
||
* 配置历史记录模态框组件
|
||
* @author MHXY Development Team
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
import React, { useState, useEffect } from 'react';
|
||
import { Modal, Table, Tag, Space, Button, Typography, Tooltip, Empty } from 'antd';
|
||
import {
|
||
HistoryOutlined,
|
||
UserOutlined,
|
||
ClockCircleOutlined,
|
||
EyeOutlined,
|
||
InfoCircleOutlined
|
||
} from '@ant-design/icons';
|
||
// import { ConfigHistory } from '../types/systemConfig';
|
||
import systemConfigService from '../services/systemConfigService';
|
||
|
||
const { Text, Paragraph } = Typography;
|
||
const { confirm } = Modal;
|
||
|
||
interface ConfigHistoryModalProps {
|
||
visible: boolean;
|
||
configKey?: string;
|
||
onClose: () => void;
|
||
}
|
||
|
||
const ConfigHistoryModal: React.FC<ConfigHistoryModalProps> = ({
|
||
visible,
|
||
configKey,
|
||
onClose
|
||
}) => {
|
||
const [historyData, setHistoryData] = useState<any[]>([]);
|
||
const [loading, setLoading] = useState<boolean>(false);
|
||
|
||
// 加载历史记录
|
||
const loadHistory = async () => {
|
||
try {
|
||
setLoading(true);
|
||
const history = await systemConfigService.getConfigHistory(configKey);
|
||
setHistoryData(history);
|
||
} catch (error) {
|
||
console.error('加载配置历史失败:', error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (visible) {
|
||
loadHistory();
|
||
}
|
||
}, [visible, configKey]);
|
||
|
||
// 查看配置变更详情
|
||
const handleViewDetails = (record: ConfigHistory) => {
|
||
confirm({
|
||
title: '配置变更详情',
|
||
width: 600,
|
||
content: (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>配置项:</Text>
|
||
<Text code>{record.config_key}</Text>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>变更者:</Text>
|
||
<Text>{record.admin_user.real_name} ({record.admin_user.username})</Text>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>变更时间:</Text>
|
||
<Text>{new Date(record.created_at).toLocaleString('zh-CN')}</Text>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>变更原因:</Text>
|
||
<Paragraph>{record.changed_reason}</Paragraph>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>原值:</Text>
|
||
<div style={{
|
||
marginTop: '8px',
|
||
padding: '8px',
|
||
background: '#fff2f0',
|
||
border: '1px solid #ffccc7',
|
||
borderRadius: '4px',
|
||
fontFamily: 'monospace',
|
||
fontSize: '12px'
|
||
}}>
|
||
<Paragraph copyable style={{ margin: 0, color: '#cf1322' }}>
|
||
{record.old_value}
|
||
</Paragraph>
|
||
</div>
|
||
</div>
|
||
|
||
<div style={{ marginBottom: '16px' }}>
|
||
<Text strong>新值:</Text>
|
||
<div style={{
|
||
marginTop: '8px',
|
||
padding: '8px',
|
||
background: '#f6ffed',
|
||
border: '1px solid #b7eb8f',
|
||
borderRadius: '4px',
|
||
fontFamily: 'monospace',
|
||
fontSize: '12px'
|
||
}}>
|
||
<Paragraph copyable style={{ margin: 0, color: '#389e0d' }}>
|
||
{record.new_value}
|
||
</Paragraph>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
),
|
||
okText: '关闭',
|
||
cancelButtonProps: { style: { display: 'none' } }
|
||
});
|
||
};
|
||
|
||
// 表格列定义
|
||
const columns = [
|
||
{
|
||
title: '配置项',
|
||
dataIndex: 'config_key',
|
||
key: 'config_key',
|
||
width: 200,
|
||
render: (text: string) => (
|
||
<Text code style={{ fontSize: '12px' }}>{text}</Text>
|
||
)
|
||
},
|
||
{
|
||
title: '变更者',
|
||
dataIndex: ['admin_user', 'real_name'],
|
||
key: 'changed_by',
|
||
width: 120,
|
||
render: (text: string, record: ConfigHistory) => (
|
||
<div>
|
||
<div style={{ fontWeight: 500 }}>{text}</div>
|
||
<div style={{ fontSize: '11px', color: '#666' }}>
|
||
@{record.admin_user.username}
|
||
</div>
|
||
</div>
|
||
)
|
||
},
|
||
{
|
||
title: '变更内容',
|
||
key: 'change_content',
|
||
width: 300,
|
||
render: (record: ConfigHistory) => (
|
||
<div>
|
||
<div style={{ marginBottom: '4px' }}>
|
||
<Tag color="red" size="small">原值</Tag>
|
||
<Text code style={{ fontSize: '11px' }}>
|
||
{record.old_value.length > 20
|
||
? `${record.old_value.substring(0, 20)}...`
|
||
: record.old_value
|
||
}
|
||
</Text>
|
||
</div>
|
||
<div>
|
||
<Tag color="green" size="small">新值</Tag>
|
||
<Text code style={{ fontSize: '11px' }}>
|
||
{record.new_value.length > 20
|
||
? `${record.new_value.substring(0, 20)}...`
|
||
: record.new_value
|
||
}
|
||
</Text>
|
||
</div>
|
||
</div>
|
||
)
|
||
},
|
||
{
|
||
title: '变更原因',
|
||
dataIndex: 'changed_reason',
|
||
key: 'changed_reason',
|
||
width: 200,
|
||
render: (text: string) => (
|
||
<Paragraph
|
||
ellipsis={{ rows: 2, expandable: false }}
|
||
style={{ margin: 0, fontSize: '12px' }}
|
||
>
|
||
{text}
|
||
</Paragraph>
|
||
)
|
||
},
|
||
{
|
||
title: '变更时间',
|
||
dataIndex: 'created_at',
|
||
key: 'created_at',
|
||
width: 160,
|
||
render: (text: string) => (
|
||
<div>
|
||
<div style={{ fontSize: '12px' }}>
|
||
{new Date(text).toLocaleDateString('zh-CN')}
|
||
</div>
|
||
<div style={{ fontSize: '11px', color: '#666' }}>
|
||
{new Date(text).toLocaleTimeString('zh-CN', {
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
second: '2-digit'
|
||
})}
|
||
</div>
|
||
</div>
|
||
)
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
width: 100,
|
||
render: (record: ConfigHistory) => (
|
||
<Space>
|
||
<Tooltip title="查看详情">
|
||
<Button
|
||
type="text"
|
||
size="small"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => handleViewDetails(record)}
|
||
/>
|
||
</Tooltip>
|
||
</Space>
|
||
)
|
||
}
|
||
];
|
||
|
||
// 判断是否为敏感配置
|
||
const isSensitiveConfig = (configKey: string) => {
|
||
return configKey.includes('secret') || configKey.includes('key') || configKey.includes('psk');
|
||
};
|
||
|
||
return (
|
||
<Modal
|
||
title={
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||
<HistoryOutlined />
|
||
配置历史记录
|
||
{configKey && (
|
||
<>
|
||
<span style={{ color: '#666' }}>-</span>
|
||
<Text code>{configKey}</Text>
|
||
</>
|
||
)}
|
||
</div>
|
||
}
|
||
open={visible}
|
||
onCancel={onClose}
|
||
width={900}
|
||
footer={[
|
||
<Button key="close" onClick={onClose}>
|
||
关闭
|
||
</Button>
|
||
]}
|
||
>
|
||
<div style={{ marginBottom: '16px' }}>
|
||
{configKey ? (
|
||
<div style={{
|
||
padding: '12px',
|
||
background: '#f0f2f5',
|
||
borderRadius: '6px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '8px'
|
||
}}>
|
||
<InfoCircleOutlined style={{ color: '#1890ff' }} />
|
||
<span style={{ fontSize: '12px' }}>
|
||
以下是配置项 <Text code>{configKey}</Text> 的变更历史记录
|
||
</span>
|
||
{isSensitiveConfig(configKey) && (
|
||
<Tag color="red" size="small">敏感配置</Tag>
|
||
)}
|
||
</div>
|
||
) : (
|
||
<div style={{
|
||
padding: '12px',
|
||
background: '#f0f2f5',
|
||
borderRadius: '6px',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: '8px'
|
||
}}>
|
||
<InfoCircleOutlined style={{ color: '#1890ff' }} />
|
||
<span style={{ fontSize: '12px' }}>
|
||
以下是所有系统配置的变更历史记录
|
||
</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<Table
|
||
columns={columns}
|
||
dataSource={historyData}
|
||
rowKey="id"
|
||
loading={loading}
|
||
pagination={{
|
||
pageSize: 10,
|
||
showSizeChanger: false,
|
||
showQuickJumper: false,
|
||
showTotal: (total) => `共 ${total} 条记录`
|
||
}}
|
||
locale={{
|
||
emptyText: (
|
||
<Empty
|
||
description="暂无历史记录"
|
||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||
/>
|
||
)
|
||
}}
|
||
size="small"
|
||
scroll={{ y: 400 }}
|
||
/>
|
||
|
||
{/* 说明信息 */}
|
||
<div style={{
|
||
marginTop: '16px',
|
||
padding: '12px',
|
||
background: '#fafafa',
|
||
border: '1px solid #d9d9d9',
|
||
borderRadius: '6px',
|
||
fontSize: '12px',
|
||
color: '#666'
|
||
}}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
|
||
<InfoCircleOutlined style={{ color: '#1890ff' }} />
|
||
<strong>说明</strong>
|
||
</div>
|
||
<ul style={{ margin: 0, paddingLeft: '20px' }}>
|
||
<li>仅显示配置变更的关键信息,查看详情可获取完整的变更内容</li>
|
||
<li>敏感配置(如密钥、密码)的值在列表中会被部分隐藏</li>
|
||
<li>配置历史记录可以帮助您追踪系统配置的变化过程</li>
|
||
<li>建议定期检查重要配置的历史记录以确保安全性</li>
|
||
</ul>
|
||
</div>
|
||
</Modal>
|
||
);
|
||
};
|
||
|
||
export default ConfigHistoryModal; |