|
|
@@ -1,10 +1,12 @@
|
|
|
import React, { useState } from 'react';
|
|
|
import { createRoot } from 'react-dom/client';
|
|
|
-import { Button, Space, Alert, Spin, Typography } from 'antd';
|
|
|
+import { Button, Space, Alert, Spin, Typography, Table } from 'antd';
|
|
|
import axios from 'axios';
|
|
|
+import dayjs from 'dayjs';
|
|
|
import {
|
|
|
QueryClient,
|
|
|
QueryClientProvider,
|
|
|
+ useQuery,
|
|
|
} from '@tanstack/react-query';
|
|
|
|
|
|
const { Title } = Typography;
|
|
|
@@ -18,10 +20,26 @@ interface MigrationResponse {
|
|
|
failedResult?: any;
|
|
|
}
|
|
|
|
|
|
+interface MigrationHistory {
|
|
|
+ id: string;
|
|
|
+ name: string;
|
|
|
+ status: string;
|
|
|
+ timestamp: string;
|
|
|
+ batch: string;
|
|
|
+}
|
|
|
+
|
|
|
const MigrationsApp: React.FC = () => {
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
const [migrationResult, setMigrationResult] = useState<MigrationResponse | null>(null);
|
|
|
|
|
|
+ const { data: historyData, isLoading: isHistoryLoading, error: historyError } = useQuery({
|
|
|
+ queryKey: ['migrations-history'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await axios.get('/api/migrations/history');
|
|
|
+ return response.data.history;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
const runMigrations = async () => {
|
|
|
try {
|
|
|
setLoading(true);
|
|
|
@@ -40,13 +58,43 @@ const MigrationsApp: React.FC = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ const columns = [
|
|
|
+ {
|
|
|
+ title: '迁移名称',
|
|
|
+ dataIndex: 'name',
|
|
|
+ key: 'name',
|
|
|
+ sorter: (a: MigrationHistory, b: MigrationHistory) => a.name.localeCompare(b.name),
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '批次',
|
|
|
+ dataIndex: 'batch',
|
|
|
+ key: 'batch',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '状态',
|
|
|
+ dataIndex: 'status',
|
|
|
+ key: 'status',
|
|
|
+ render: (status: string) => (
|
|
|
+ <span style={{ color: status === 'completed' ? 'green' : 'red' }}>
|
|
|
+ {status === 'completed' ? '已完成' : '失败'}
|
|
|
+ </span>
|
|
|
+ )
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '时间',
|
|
|
+ dataIndex: 'timestamp',
|
|
|
+ key: 'timestamp',
|
|
|
+ render: (timestamp: string) => dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
return (
|
|
|
<div className="p-4">
|
|
|
<Title level={3}>数据库迁移管理</Title>
|
|
|
|
|
|
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
|
|
|
- <Button
|
|
|
- type="primary"
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
onClick={runMigrations}
|
|
|
loading={loading}
|
|
|
disabled={loading}
|
|
|
@@ -58,13 +106,13 @@ const MigrationsApp: React.FC = () => {
|
|
|
|
|
|
{migrationResult && (
|
|
|
migrationResult.success ? (
|
|
|
- <Alert
|
|
|
- message="迁移成功"
|
|
|
- type="success"
|
|
|
- showIcon
|
|
|
+ <Alert
|
|
|
+ message="迁移成功"
|
|
|
+ type="success"
|
|
|
+ showIcon
|
|
|
/>
|
|
|
) : (
|
|
|
- <Alert
|
|
|
+ <Alert
|
|
|
message="迁移失败"
|
|
|
description={
|
|
|
<>
|
|
|
@@ -76,11 +124,39 @@ const MigrationsApp: React.FC = () => {
|
|
|
)}
|
|
|
</>
|
|
|
}
|
|
|
- type="error"
|
|
|
- showIcon
|
|
|
+ type="error"
|
|
|
+ showIcon
|
|
|
/>
|
|
|
)
|
|
|
)}
|
|
|
+
|
|
|
+ <Title level={4}>迁移历史记录</Title>
|
|
|
+
|
|
|
+ {isHistoryLoading ? (
|
|
|
+ <Spin tip="加载历史记录中..." />
|
|
|
+ ) : historyError ? (
|
|
|
+ <Alert
|
|
|
+ message="加载历史记录失败"
|
|
|
+ description={historyError.message}
|
|
|
+ type="error"
|
|
|
+ showIcon
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <Table
|
|
|
+ columns={columns}
|
|
|
+ dataSource={historyData}
|
|
|
+ rowKey="id"
|
|
|
+ pagination={{
|
|
|
+ pageSize: 10,
|
|
|
+ showSizeChanger: true,
|
|
|
+ pageSizeOptions: ['10', '20', '50', '100'],
|
|
|
+ showTotal: (total) => `共 ${total} 条记录`,
|
|
|
+ }}
|
|
|
+ bordered
|
|
|
+ className="migration-history-table"
|
|
|
+ style={{ marginTop: 16 }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
</Space>
|
|
|
</div>
|
|
|
);
|