fix(P1-2): generation.py _download_library_assets 改用 Worker SessionLocal 替代直接 create_engine
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled

This commit is contained in:
CI Test
2026-06-28 10:25:38 +08:00
parent d1f2dbffa4
commit ecfa0d33e4
11 changed files with 4 additions and 1452 deletions
-245
View File
@@ -1,245 +0,0 @@
/**
* Admin Analytics 数据分析页面
*/
import React from 'react';
import { Card, Row, Col, Statistic, Table, DatePicker, Space } from 'antd';
import {
LineChart,
Line,
BarChart,
Bar,
PieChart,
Pie,
Cell,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
} from 'recharts';
import {
ArrowUpOutlined,
UserAddOutlined,
DollarOutlined,
ProjectOutlined,
} from '@ant-design/icons';
import './Admin.css';
const { RangePicker } = DatePicker;
// V21 配色
const V21_COLORS = {
primary: '#4f46e5',
primaryLight: '#6366f1',
success: '#10b981',
successLight: '#34d399',
warning: '#f59e0b',
warningLight: '#fbbf24',
purple: '#8b5cf6',
purpleLight: '#a78bfa',
};
const Analytics: React.FC = () => {
// 模拟用户增长数据
const userGrowthData = [
{ month: '1月', users: 120, paid: 15 },
{ month: '2月', users: 180, paid: 28 },
{ month: '3月', users: 240, paid: 42 },
{ month: '4月', users: 320, paid: 58 },
{ month: '5月', users: 450, paid: 89 },
{ month: '6月', users: 620, paid: 135 },
];
// 模拟营收数据
const revenueData = [
{ month: '1月', revenue: 1480 },
{ month: '2月', revenue: 2770 },
{ month: '3月', revenue: 4160 },
{ month: '4月', revenue: 5740 },
{ month: '5月', revenue: 8810 },
{ month: '6月', revenue: 13365 },
];
// 订阅计划分布
const planDistribution = [
{ name: 'Free', value: 485, color: '#a78bfa' },
{ name: 'Pro', value: 120, color: '#34d399' },
{ name: 'Enterprise', value: 15, color: '#fbbf24' },
];
// 活跃度统计
const activityStats = [
{ metric: '日活跃用户 (DAU)', value: 892, growth: '+12.5%', icon: <UserAddOutlined />, colorClass: 'primary' },
{ metric: '月活跃用户 (MAU)', value: 3456, growth: '+8.3%', icon: <ArrowUpOutlined />, colorClass: 'success' },
{ metric: '本月收入', value: 13365, prefix: '¥', growth: '+51.6%', icon: <DollarOutlined />, colorClass: 'warning' },
{ metric: '活跃项目数', value: 2341, growth: '+18.2%', icon: <ProjectOutlined />, colorClass: 'purple' },
];
// 用户留存表格
const retentionData = [
{ cohort: '2024-01', day1: '100%', day7: '68%', day30: '45%', day90: '28%' },
{ cohort: '2024-02', day1: '100%', day7: '72%', day30: '48%', day90: '31%' },
{ cohort: '2024-03', day1: '100%', day7: '75%', day30: '52%', day90: '-' },
{ cohort: '2024-04', day1: '100%', day7: '78%', day30: '55%', day90: '-' },
{ cohort: '2024-05', day1: '100%', day7: '80%', day30: '-', day90: '-' },
];
const retentionColumns = [
{ title: 'Cohort', dataIndex: 'cohort', key: 'cohort' },
{ title: 'Day 1', dataIndex: 'day1', key: 'day1' },
{ title: 'Day 7', dataIndex: 'day7', key: 'day7' },
{ title: 'Day 30', dataIndex: 'day30', key: 'day30' },
{ title: 'Day 90', dataIndex: 'day90', key: 'day90' },
];
const colorMap: Record<string, string> = {
primary: '#4f46e5',
success: '#10b981',
warning: '#f59e0b',
purple: '#8b5cf6',
};
return (
<div className="analytics-page">
<div className="xx-page-head">
<div className="xx-page-head-content">
<h2></h2>
<p></p>
</div>
<div className="xx-page-head-actions">
<Space>
<RangePicker className="xx-date-picker" />
</Space>
</div>
</div>
{/* 关键指标 */}
<Row gutter={24} className="xx-grid-4">
{activityStats.map((stat, index) => (
<Col span={6} key={index}>
<div className="xx-stat-card">
<div className="xx-stat-card-header">
<div className={`xx-stat-card-icon ${stat.colorClass}`}>
{stat.icon}
</div>
</div>
<div className="xx-stat-card-label">{stat.metric}</div>
<div
className="xx-stat-card-value"
style={{ color: colorMap[stat.colorClass] }}
>
{stat.prefix}{stat.value.toLocaleString()}
</div>
<div className="xx-stat-card-growth">
{stat.growth} vs
</div>
</div>
</Col>
))}
</Row>
{/* 图表区域 */}
<Row gutter={24} style={{ marginBottom: 24 }}>
{/* 用户增长趋势 */}
<Col span={16}>
<div className="xx-chart-container">
<Card title="用户增长趋势">
<ResponsiveContainer width="100%" height={300}>
<LineChart data={userGrowthData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Line
type="monotone"
dataKey="users"
stroke={V21_COLORS.primary}
name="总用户"
strokeWidth={3}
/>
<Line
type="monotone"
dataKey="paid"
stroke={V21_COLORS.success}
name="付费用户"
strokeWidth={3}
/>
</LineChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
{/* 订阅计划分布 */}
<Col span={8}>
<div className="xx-chart-container">
<Card title="订阅计划分布">
<ResponsiveContainer width="100%" height={300}>
<PieChart>
<Pie
data={planDistribution}
cx="50%"
cy="50%"
labelLine={false}
label={(entry) => `${entry.name}: ${entry.value}`}
outerRadius={80}
fill="#8884d8"
dataKey="value"
>
{planDistribution.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Tooltip />
</PieChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
</Row>
{/* 营收趋势 */}
<Row gutter={24} style={{ marginBottom: 24 }}>
<Col span={24}>
<div className="xx-chart-container">
<Card title="营收趋势">
<ResponsiveContainer width="100%" height={300}>
<BarChart data={revenueData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="month" />
<YAxis />
<Tooltip />
<Legend />
<Bar
dataKey="revenue"
fill={V21_COLORS.warning}
name="收入 (¥)"
radius={[8, 8, 0, 0]}
/>
</BarChart>
</ResponsiveContainer>
</Card>
</div>
</Col>
</Row>
{/* 用户留存表 */}
<div className="xx-table-wrapper">
<Card title="用户留存率 (Cohort Analysis)">
<Table
columns={retentionColumns}
dataSource={retentionData}
rowKey="cohort"
pagination={false}
/>
</Card>
</div>
</div>
);
};
export default Analytics;
export const Component = Analytics;
-86
View File
@@ -1,86 +0,0 @@
/**
* Admin Dashboard 仪表盘
*/
import React from 'react';
import { Card, Row, Col, Statistic, Table } from 'antd';
import {
UserOutlined,
CrownOutlined,
DollarOutlined,
RiseOutlined,
} from '@ant-design/icons';
import './Admin.css';
const Dashboard: React.FC = () => {
// 模拟数据
const stats = [
{ title: '总用户数', value: 1234, icon: <UserOutlined />, colorClass: 'primary' },
{ title: '付费用户', value: 156, icon: <CrownOutlined />, colorClass: 'success' },
{ title: '月收入', value: 15444, prefix: '¥', icon: <DollarOutlined />, colorClass: 'warning' },
{ title: '活跃用户', value: 892, icon: <RiseOutlined />, colorClass: 'purple' },
];
const recentUsers = [
{ id: '1', username: 'user1', email: 'user1@example.com', created_at: '2024-06-17' },
{ id: '2', username: 'user2', email: 'user2@example.com', created_at: '2024-06-16' },
{ id: '3', username: 'user3', email: 'user3@example.com', created_at: '2024-06-15' },
];
const columns = [
{ title: '用户名', dataIndex: 'username', key: 'username' },
{ title: '邮箱', dataIndex: 'email', key: 'email' },
{ title: '注册时间', dataIndex: 'created_at', key: 'created_at' },
];
const colorMap: Record<string, string> = {
primary: '#4f46e5',
success: '#10b981',
warning: '#f59e0b',
purple: '#8b5cf6',
};
return (
<div className="dashboard-page">
<div className="xx-page-head-simple">
<h2>Dashboard</h2>
<p></p>
</div>
<Row gutter={24} className="xx-grid-4">
{stats.map((stat, index) => (
<Col span={6} key={index}>
<div className="xx-stat-card">
<div className="xx-stat-card-header">
<div className={`xx-stat-card-icon ${stat.colorClass}`}>
{stat.icon}
</div>
</div>
<div className="xx-stat-card-label">{stat.title}</div>
<div
className="xx-stat-card-value"
style={{ color: colorMap[stat.colorClass] }}
>
{stat.prefix}{stat.value.toLocaleString()}
</div>
</div>
</Col>
))}
</Row>
<div className="xx-table-wrapper">
<Card title="最近注册用户" className="xx-card">
<Table
columns={columns}
dataSource={recentUsers}
rowKey="id"
pagination={false}
/>
</Card>
</div>
</div>
);
};
export default Dashboard;
export const Component = Dashboard;
-344
View File
@@ -1,344 +0,0 @@
/**
* 日志查看器
*/
import React, { useState } from 'react';
import { Card, Table, Tag, Input, Select, DatePicker, Space, Button, Drawer } from 'antd';
import {
SearchOutlined,
FilterOutlined,
DownloadOutlined,
EyeOutlined,
} from '@ant-design/icons';
import type { ColumnsType } from 'antd/es/table';
import './Admin.css';
const { Search } = Input;
const { RangePicker } = DatePicker;
const { Option } = Select;
interface LogEntry {
id: string;
timestamp: string;
level: 'INFO' | 'WARN' | 'ERROR' | 'DEBUG';
service: string;
user?: string;
action: string;
details: string;
ip?: string;
request_id?: string;
}
const LogViewer: React.FC = () => {
const [selectedLog, setSelectedLog] = useState<LogEntry | null>(null);
const [drawerVisible, setDrawerVisible] = useState(false);
const [filterLevel, setFilterLevel] = useState<string>('all');
const [filterService, setFilterService] = useState<string>('all');
// 模拟日志数据
const logs: LogEntry[] = [
{
id: '1',
timestamp: '2024-06-17 17:15:32',
level: 'INFO',
service: 'API',
user: 'user@example.com',
action: 'User Login',
details: 'User successfully logged in',
ip: '192.168.1.100',
request_id: 'req_abc123',
},
{
id: '2',
timestamp: '2024-06-17 17:14:28',
level: 'WARN',
service: 'API',
user: 'admin@example.com',
action: 'Failed Login Attempt',
details: 'Invalid password provided (attempt 2/5)',
ip: '192.168.1.101',
request_id: 'req_def456',
},
{
id: '3',
timestamp: '2024-06-17 17:12:45',
level: 'ERROR',
service: 'Database',
action: 'Connection Timeout',
details: 'PostgreSQL connection pool exhausted, timeout after 5s',
request_id: 'req_ghi789',
},
{
id: '4',
timestamp: '2024-06-17 17:10:15',
level: 'INFO',
service: 'Celery',
action: 'Task Completed',
details: 'Video processing task completed successfully',
request_id: 'task_jkl012',
},
{
id: '5',
timestamp: '2024-06-17 17:08:52',
level: 'WARN',
service: 'ObjectStorage',
action: 'Slow Upload',
details: 'File upload took 3.2s (>1s threshold)',
ip: '192.168.1.102',
request_id: 'req_mno345',
},
{
id: '6',
timestamp: '2024-06-17 17:05:33',
level: 'ERROR',
service: 'API',
user: 'user2@example.com',
action: 'Permission Denied',
details: 'User attempted to access admin endpoint without permission',
ip: '192.168.1.103',
request_id: 'req_pqr678',
},
{
id: '7',
timestamp: '2024-06-17 17:03:21',
level: 'INFO',
service: 'API',
user: 'user3@example.com',
action: 'Project Created', details: 'New project "My Project" created',
ip: '192.168.1.104',
request_id: 'req_stu901',
},
{
id: '8',
timestamp: '2024-06-17 17:01:08',
level: 'DEBUG',
service: 'Redis',
action: 'Cache Miss',
details: 'Cache key "project:123" not found, fetching from DB',
request_id: 'req_vwx234',
},
];
const getLevelTagClass = (level: string) => {
const classes: Record<string, string> = {
INFO: 'xx-tag info',
WARN: 'xx-tag warning',
ERROR: 'xx-tag error',
DEBUG: 'xx-tag debug',
};
return classes[level] || 'xx-tag debug';
};
const handleViewDetails = (log: LogEntry) => {
setSelectedLog(log);
setDrawerVisible(true);
};
const columns: ColumnsType<LogEntry> = [
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
width: 180,
sorter: (a, b) => a.timestamp.localeCompare(b.timestamp),
},
{
title: '级别',
dataIndex: 'level',
key: 'level',
width: 100,
render: (level: string) => <Tag className={getLevelTagClass(level)}>{level}</Tag>,
filters: [
{ text: 'INFO', value: 'INFO' },
{ text: 'WARN', value: 'WARN' },
{ text: 'ERROR', value: 'ERROR' },
{ text: 'DEBUG', value: 'DEBUG' },
],
onFilter: (value, record) => record.level === value,
},
{
title: '服务',
dataIndex: 'service',
key: 'service',
width: 120,
filters: [
{ text: 'API', value: 'API' },
{ text: 'Database', value: 'Database' },
{ text: 'Celery', value: 'Celery' },
{ text: 'ObjectStorage', value: 'ObjectStorage' },
{ text: 'Redis', value: 'Redis' },
],
onFilter: (value, record) => record.service === value,
},
{
title: '用户',
dataIndex: 'user',
key: 'user',
width: 180,
render: (user?: string) => user || '-',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
width: 200,
},
{
title: '详情',
dataIndex: 'details',
key: 'details',
ellipsis: true,
},
{
title: '操作',
key: 'actions',
width: 100,
render: (_, record) => (
<Button
type="link"
icon={<EyeOutlined />}
onClick={() => handleViewDetails(record)}
>
</Button>
),
},
];
return (
<div className="log-viewer-page">
<div className="xx-page-head-simple">
<h2></h2>
<p></p>
</div>
<div className="xx-filter-bar">
<Search
placeholder="搜索日志内容..."
allowClear
enterButton={<SearchOutlined />}
className="xx-search-input"
style={{ width: 280 }}
/>
<Select
placeholder="日志级别"
className="xx-select"
style={{ width: 130 }}
value={filterLevel}
onChange={setFilterLevel}
>
<Option value="all"></Option>
<Option value="INFO">INFO</Option>
<Option value="WARN">WARN</Option>
<Option value="ERROR">ERROR</Option>
<Option value="DEBUG">DEBUG</Option>
</Select>
<Select
placeholder="服务"
className="xx-select"
style={{ width: 140 }}
value={filterService}
onChange={setFilterService}
>
<Option value="all"></Option>
<Option value="API">API</Option>
<Option value="Database">Database</Option>
<Option value="Celery">Celery</Option>
<Option value="ObjectStorage">Object Storage</Option>
<Option value="Redis">Redis</Option>
</Select>
<RangePicker showTime className="xx-date-picker" />
<Button icon={<FilterOutlined />} disabled className="xx-ghost-btn">
</Button>
<Button icon={<DownloadOutlined />} disabled className="xx-ghost-btn">
</Button>
</div>
<div className="xx-table-wrapper">
<Table
columns={columns}
dataSource={logs}
rowKey="id"
pagination={{
total: logs.length,
pageSize: 10,
showSizeChanger: true,
showTotal: (total) => `${total} 条日志`,
}}
scroll={{ x: 1200 }}
/>
</div>
{/* 日志详情抽屉 */}
<Drawer
title="日志详情"
placement="right"
width={600}
open={drawerVisible}
onClose={() => setDrawerVisible(false)}
className="xx-drawer"
>
{selectedLog && (
<div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.timestamp}</div>
</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">
<Tag className={getLevelTagClass(selectedLog.level)}>{selectedLog.level}</Tag>
</div>
</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.service}</div>
</div>
{selectedLog.user && (
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.user}</div>
</div>
)}
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-value">{selectedLog.action}</div>
</div>
<div className="xx-log-detail">
<div className="xx-log-detail-label"></div>
<div className="xx-log-detail-code">{selectedLog.details}</div>
</div>
{selectedLog.ip && (
<div className="xx-log-detail">
<div className="xx-log-detail-label">IP </div>
<div className="xx-log-detail-value">{selectedLog.ip}</div>
</div>
)}
{selectedLog.request_id && (
<div className="xx-log-detail">
<div className="xx-log-detail-label">Request ID</div>
<div className="xx-log-detail-code">{selectedLog.request_id}</div>
</div>
)}
</div>
)}
</Drawer>
</div>
);
};
export default LogViewer;
export const Component = LogViewer;
-224
View File
@@ -1,224 +0,0 @@
/**
* 系统监控页面
*/
import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Progress, Badge, Table, Tag, Button, Space } from 'antd';
import {
CheckCircleOutlined,
CloseCircleOutlined,
SyncOutlined,
ReloadOutlined,
DatabaseOutlined,
ApiOutlined,
CloudServerOutlined,
ThunderboltOutlined,
} from '@ant-design/icons';
import './Admin.css';
const SystemMonitor: React.FC = () => {
const [refreshTime, setRefreshTime] = useState(new Date());
// 模拟实时数据
const [systemMetrics, setSystemMetrics] = useState({
cpu: 35,
memory: 62,
disk: 48,
network: 28,
});
// 服务健康状态
const services = [
{ name: 'API 服务', status: 'healthy', uptime: '99.98%', responseTime: '45ms' },
{ name: 'PostgreSQL', status: 'healthy', uptime: '99.99%', responseTime: '8ms' },
{ name: 'Redis', status: 'healthy', uptime: '99.97%', responseTime: '2ms' },
{ name: 'Celery Worker', status: 'healthy', uptime: '99.95%', responseTime: '-' },
{ name: 'Object Storage', status: 'warning', uptime: '99.85%', responseTime: '120ms' },
];
// API 请求统计
const apiStats = [
{ endpoint: 'POST /api/v1/auth/login', requests: 15420, avgTime: '48ms', errors: 12 },
{ endpoint: 'GET /api/v1/projects', requests: 89340, avgTime: '32ms', errors: 5 },
{ endpoint: 'POST /api/v1/projects', requests: 6780, avgTime: '156ms', errors: 8 },
{ endpoint: 'GET /api/v1/assets', requests: 124560, avgTime: '68ms', errors: 23 },
{ endpoint: 'POST /api/v1/subscriptions/subscribe', requests: 234, avgTime: '890ms', errors: 2 },
];
// 错误日志
const recentErrors = [
{ time: '2024-06-17 16:45:32', level: 'ERROR', service: 'API', message: 'Database connection timeout' },
{ time: '2024-06-17 16:42:15', level: 'WARN', service: 'Object Storage', message: 'Slow response detected (>1s)' },
{ time: '2024-06-17 16:38:41', level: 'ERROR', service: 'Celery', message: 'Task retry limit exceeded' },
];
const serviceColumns = [
{
title: '服务名称',
dataIndex: 'name',
key: 'name',
render: (text: string) => <><CloudServerOutlined /> {text}</>,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => {
const config: Record<string, { color: string; icon: React.ReactNode; text: string }> = {
healthy: { color: 'success', icon: <CheckCircleOutlined />, text: '正常' },
warning: { color: 'warning', icon: <SyncOutlined spin />, text: '警告' },
error: { color: 'error', icon: <CloseCircleOutlined />, text: '故障' },
};
const { color, text } = config[status] || config.healthy;
return <Badge status={color as any} text={text} />;
},
},
{ title: '可用性', dataIndex: 'uptime', key: 'uptime' },
{ title: '响应时间', dataIndex: 'responseTime', key: 'responseTime' },
];
const apiColumns = [
{ title: 'API 端点', dataIndex: 'endpoint', key: 'endpoint' },
{ title: '请求数', dataIndex: 'requests', key: 'requests', sorter: (a: any, b: any) => a.requests - b.requests },
{ title: '平均响应时间', dataIndex: 'avgTime', key: 'avgTime' },
{
title: '错误数',
dataIndex: 'errors',
key: 'errors',
render: (errors: number) => (
<Tag className={errors > 10 ? 'xx-tag error' : errors > 5 ? 'xx-tag warning' : 'xx-tag success'}>{errors}</Tag>
),
},
];
const errorColumns = [
{ title: '时间', dataIndex: 'time', key: 'time', width: 180 },
{
title: '级别',
dataIndex: 'level',
key: 'level',
width: 100,
render: (level: string) => (
<Tag className={level === 'ERROR' ? 'xx-tag error' : 'xx-tag warning'}>{level}</Tag>
),
},
{ title: '服务', dataIndex: 'service', key: 'service', width: 120 },
{ title: '错误信息', dataIndex: 'message', key: 'message' },
];
const refreshMetrics = () => {
setSystemMetrics({
cpu: Math.floor(Math.random() * 30) + 30,
memory: Math.floor(Math.random() * 20) + 55,
disk: Math.floor(Math.random() * 10) + 45,
network: Math.floor(Math.random() * 40) + 20,
});
setRefreshTime(new Date());
};
// 模拟实时更新
useEffect(() => {
const interval = setInterval(refreshMetrics, 5000);
return () => clearInterval(interval);
}, []);
const getProgressColor = (value: number) => {
if (value > 80) return '#ef4444';
if (value > 60) return '#f59e0b';
return '#10b981';
};
const getProgressClass = (value: number) => {
if (value > 80) return 'xx-progress-warning';
if (value > 60) return 'xx-progress-warning';
return 'xx-progress-success';
};
const resourceMetrics = [
{ key: 'cpu', label: 'CPU 使用率', value: systemMetrics.cpu, icon: <ThunderboltOutlined />, bg: 'linear-gradient(135deg, #6366f1, #4f46e5)' },
{ key: 'memory', label: '内存使用率', value: systemMetrics.memory, icon: <DatabaseOutlined />, bg: 'linear-gradient(135deg, #34d399, #10b981)' },
{ key: 'disk', label: '磁盘使用率', value: systemMetrics.disk, icon: <CloudServerOutlined />, bg: 'linear-gradient(135deg, #fbbf24, #f59e0b)' },
{ key: 'network', label: '网络使用率', value: systemMetrics.network, icon: <ApiOutlined />, bg: 'linear-gradient(135deg, #a78bfa, #8b5cf6)' },
];
return (
<div className="system-monitor-page">
<div className="xx-page-head">
<div className="xx-page-head-content">
<h2></h2>
<p> API </p>
</div>
<div className="xx-page-head-actions">
<Space>
<span className="xx-refresh-time">: {refreshTime.toLocaleTimeString()}</span>
<Button icon={<ReloadOutlined />} onClick={refreshMetrics} className="xx-primary-btn">
</Button>
</Space>
</div>
</div>
{/* 系统资源监控 */}
<Row gutter={24} className="xx-grid-4">
{resourceMetrics.map((metric) => (
<Col span={6} key={metric.key}>
<div className="xx-resource-card">
<div
className="xx-resource-card-icon"
style={{ background: metric.bg, color: 'white' }}
>
{metric.icon}
</div>
<div className="xx-resource-card-label">{metric.label}</div>
<Progress
type="circle"
percent={metric.value}
strokeColor={getProgressColor(metric.value)}
className={getProgressClass(metric.value)}
/>
</div>
</Col>
))}
</Row>
{/* 服务健康状态 */}
<div className="xx-table-wrapper" style={{ marginBottom: 24 }}>
<Card title="服务健康状态" className="xx-card">
<Table
columns={serviceColumns}
dataSource={services}
rowKey="name"
pagination={false}
/>
</Card>
</div>
{/* API 请求统计 */}
<div className="xx-table-wrapper" style={{ marginBottom: 24 }}>
<Card title="API 请求统计 (最近 1 小时)" className="xx-card">
<Table
columns={apiColumns}
dataSource={apiStats}
rowKey="endpoint"
pagination={false}
/>
</Card>
</div>
{/* 最近错误 */}
<div className="xx-table-wrapper">
<Card title="最近错误日志" className="xx-card">
<Table
columns={errorColumns}
dataSource={recentErrors}
rowKey="time"
pagination={false}
/>
</Card>
</div>
</div>
);
};
export default SystemMonitor;
export const Component = SystemMonitor;
-117
View File
@@ -1,117 +0,0 @@
/**
* Admin 用户管理页面
*/
import React from 'react';
import { Table, Button, Space, Tag, Input, Card } from 'antd';
import { SearchOutlined, LockOutlined, UnlockOutlined } from '@ant-design/icons';
import './Admin.css';
const { Search } = Input;
const UserManagement: React.FC = () => {
// 模拟数据
const users = [
{
id: '1',
username: 'user1',
email: 'user1@example.com',
is_email_verified: true,
status: 'active',
created_at: '2024-01-15',
},
{
id: '2',
username: 'user2',
email: 'user2@example.com',
is_email_verified: false,
status: 'active',
created_at: '2024-02-20',
},
];
const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '邮箱验证',
dataIndex: 'is_email_verified',
key: 'is_email_verified',
render: (verified: boolean) => (
<Tag className={verified ? 'xx-tag success' : 'xx-tag debug'}>
{verified ? '已验证' : '未验证'}
</Tag>
),
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
render: (status: string) => (
<Tag className={status === 'active' ? 'xx-tag success' : 'xx-tag error'}>
{status === 'active' ? '正常' : '已封禁'}
</Tag>
),
},
{
title: '注册时间',
dataIndex: 'created_at',
key: 'created_at',
},
{
title: '操作',
key: 'action',
render: (_: any, record: any) => (
<Space>
<Button type="link" size="small" disabled>
</Button>
<Button
type="link"
size="small"
danger={record.status === 'active'}
icon={record.status === 'active' ? <LockOutlined /> : <UnlockOutlined />}
disabled
>
{record.status === 'active' ? '封禁暂未开放' : '解封暂未开放'}
</Button>
</Space>
),
},
];
return (
<div className="user-management-page">
<div className="xx-page-head-simple">
<h2></h2>
<p></p>
</div>
<div className="xx-table-wrapper">
<Card title="用户列表" className="xx-card">
<div style={{ marginBottom: 16 }}>
<Search
placeholder="搜索用户名或邮箱"
allowClear
enterButton={<SearchOutlined />}
className="xx-search-input"
style={{ width: 320 }}
/>
</div>
<Table columns={columns} dataSource={users} rowKey="id" />
</Card>
</div>
</div>
);
};
export default UserManagement;
export const Component = UserManagement;
@@ -1,111 +0,0 @@
/**
* 账号安全设置页面
*/
import React from 'react';
import { Form, Input, Button, Alert } from 'antd';
import { LockOutlined, MailOutlined } from '@ant-design/icons';
import './ProfileSettings.css';
const AccountSecurity: React.FC = () => {
const [passwordForm] = Form.useForm();
const [emailForm] = Form.useForm();
const onPasswordSubmit = () => undefined;
const onEmailSubmit = () => undefined;
return (
<div className="xx-settings-page">
<div className="xx-settings-card">
<h3></h3>
<Alert
type="info"
showIcon
style={{ marginBottom: 20 }}
message="账号安全设置暂未开放"
description="当前后端尚未提供登录态修改密码/邮箱接口,避免假成功,入口暂时禁用。"
/>
<Form form={passwordForm} layout="vertical" onFinish={onPasswordSubmit}>
<Form.Item
name="currentPassword"
label="当前密码"
rules={[{ required: true, message: '请输入当前密码' }]}
>
<Input.Password prefix={<LockOutlined />} />
</Form.Item>
<Form.Item
name="newPassword"
label="新密码"
rules={[
{ required: true, message: '请输入新密码' },
{ min: 8, message: '密码至少 8 个字符' },
]}
>
<Input.Password prefix={<LockOutlined />} />
</Form.Item>
<Form.Item
name="confirmPassword"
label="确认新密码"
dependencies={['newPassword']}
rules={[
{ required: true, message: '请确认新密码' },
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('newPassword') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入的密码不一致'));
},
}),
]}
>
<Input.Password prefix={<LockOutlined />} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled>
</Button>
</Form.Item>
</Form>
</div>
<div className="xx-settings-card">
<h3></h3>
<Form form={emailForm} layout="vertical" onFinish={onEmailSubmit}>
<Form.Item
name="newEmail"
label="新邮箱地址"
rules={[
{ required: true, message: '请输入新邮箱' },
{ type: 'email', message: '请输入有效的邮箱地址' },
]}
>
<Input prefix={<MailOutlined />} />
</Form.Item>
<Form.Item
name="password"
label="确认密码"
rules={[{ required: true, message: '请输入密码以确认修改' }]}
>
<Input.Password prefix={<LockOutlined />} />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled>
</Button>
</Form.Item>
</Form>
</div>
</div>
);
};
export default AccountSecurity;
export const Component = AccountSecurity;
@@ -1,96 +0,0 @@
/**
* 通知设置页面
*/
import React from 'react';
import { Form, Switch, Button, Divider, Alert } from 'antd';
import './ProfileSettings.css';
const NotificationSettings: React.FC = () => {
const [form] = Form.useForm();
const onFinish = () => undefined;
return (
<div className="xx-settings-page">
<div className="xx-settings-card">
<Alert
type="info"
showIcon
style={{ marginBottom: 20 }}
message="通知偏好暂未开放"
description="当前后端尚未提供通知偏好保存接口,避免假成功,保存入口暂时禁用。"
/>
<Form
form={form}
layout="vertical"
onFinish={onFinish}
initialValues={{
emailNotifications: true,
inviteNotifications: true,
projectNotifications: false,
marketingEmails: false,
}}
>
<h3 className="xx-section-title"></h3>
<Form.Item
name="emailNotifications"
label="启用邮件通知"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Divider />
<h3 className="xx-section-title"></h3>
<Form.Item
name="inviteNotifications"
label="工作空间邀请通知"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name="projectNotifications"
label="项目更新通知"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name="subscriptionNotifications"
label="订阅和账单通知"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Divider />
<h3 className="xx-section-title"></h3>
<Form.Item
name="marketingEmails"
label="接收产品更新和优惠信息"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" disabled>
</Button>
</Form.Item>
</Form>
</div>
</div>
);
};
export default NotificationSettings;
export const Component = NotificationSettings;
@@ -1,21 +0,0 @@
import React from 'react';
import { Result } from 'antd';
import './ProfileSettings.css';
const SessionManagement: React.FC = () => {
return (
<div className="xx-settings-page">
<div className="xx-result-page">
<Result
status="info"
title="会话管理暂未开放"
subTitle="当前版本未接入会话列表、设备管理和远程登出后端服务,因此不展示模拟设备,也不会伪造登出操作。"
/>
</div>
</div>
);
};
export default SessionManagement;
export const Component = SessionManagement;
@@ -1,196 +0,0 @@
/**
* 工作空间详情页面
*/
import React, { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Tabs, Button, Descriptions, Card, List, Modal, Form, Input, message, Alert } from 'antd';
import { PlusOutlined, TeamOutlined } from '@ant-design/icons';
import { useWorkspace } from '@/hooks/useWorkspace';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { createProject, getProjects } from '@/api/projects';
import MemberList from '@/components/business/MemberList';
import InviteMemberModal from '@/components/business/InviteMemberModal';
import PermissionMatrix from '@/components/business/PermissionMatrix';
const WorkspaceDetail: React.FC = () => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const { data: workspace, isLoading } = useWorkspace(id!);
const queryClient = useQueryClient();
const [inviteModalOpen, setInviteModalOpen] = useState(false);
const [createProjectOpen, setCreateProjectOpen] = useState(false);
const [projectForm] = Form.useForm<{ name: string; description?: string }>();
const projectsQuery = useQuery({
queryKey: ['projects', id],
queryFn: () => getProjects(),
enabled: !!id,
});
const createProjectMutation = useMutation({
mutationFn: createProject,
onSuccess: (project) => {
message.success('项目创建成功');
setCreateProjectOpen(false);
projectForm.resetFields();
queryClient.invalidateQueries({ queryKey: ['projects', id] });
navigate(`/projects/${project.id}/assets`, { state: { workspaceId: id } });
},
onError: (error: any) => {
message.error(error.response?.data?.detail || '项目创建失败,请稍后重试');
},
});
const handleCreateProject = async () => {
const values = await projectForm.validateFields();
createProjectMutation.mutate({
name: values.name,
description: values.description || '',
});
};
if (isLoading) return <div>...</div>;
if (!workspace) return <div></div>;
const items = [
{
key: 'overview',
label: '概览',
children: (
<div>
<Card
title="工作空间信息"
style={{ marginBottom: 16 }}
extra={
<Button type="primary" onClick={() => setCreateProjectOpen(true)}>
</Button>
}
>
<Descriptions column={2}>
<Descriptions.Item label="名称">{workspace.name}</Descriptions.Item>
<Descriptions.Item label="订阅计划">{workspace.subscription_plan}</Descriptions.Item>
<Descriptions.Item label="状态">{workspace.subscription_status}</Descriptions.Item>
<Descriptions.Item label="创建时间">
{workspace.created_at
? new Date(workspace.created_at).toLocaleDateString()
: '暂未返回'}
</Descriptions.Item>
</Descriptions>
</Card>
<Card title="项目" style={{ marginBottom: 16 }}>
{projectsQuery.isError && (
<Alert type="error" showIcon message="项目列表加载失败" style={{ marginBottom: 16 }} />
)}
<List
loading={projectsQuery.isLoading}
dataSource={projectsQuery.data || []}
locale={{ emptyText: '还没有项目,请先创建项目再上传素材' }}
renderItem={(project) => (
<List.Item
actions={[
<Button key="assets" type="link" onClick={() => navigate(`/projects/${project.id}/assets`, { state: { workspaceId: id } })}>
</Button>,
<Button key="titles" type="link" onClick={() => navigate(`/projects/${project.id}/titles`, { state: { workspaceId: id } })}>
</Button>,
<Button key="generation" type="link" onClick={() => navigate(`/projects/${project.id}/generation`, { state: { workspaceId: id } })}>
</Button>,
<Button key="tasks" type="link" onClick={() => navigate(`/projects/${project.id}/tasks`, { state: { workspaceId: id } })}>
</Button>,
<Button key="results" type="link" onClick={() => navigate(`/projects/${project.id}/results`, { state: { workspaceId: id } })}>
</Button>,
]}
>
<List.Item.Meta title={project.name} description={project.description || '暂无描述'} />
</List.Item>
)}
/>
</Card>
<Alert
type="info"
showIcon
message="配额与订阅功能暂未开放"
description="当前版本未接入订阅和配额后端服务,因此不展示模拟配额数据,也不会调用不存在的配额接口。"
/>
</div>
),
},
{
key: 'members',
label: (
<>
<TeamOutlined />
</>
),
children: (
<div>
<div style={{ marginBottom: 16 }}>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setInviteModalOpen(true)}
>
</Button>
</div>
<MemberList workspaceId={id!} />
<div style={{ marginTop: 24 }}>
<PermissionMatrix />
</div>
</div>
),
},
{
key: 'settings',
label: '设置',
children: <div></div>,
},
];
return (
<div>
<h1>{workspace.name}</h1>
<Tabs items={items} />
<Modal
title="创建项目"
open={createProjectOpen}
okText="创建"
cancelText="取消"
confirmLoading={createProjectMutation.isPending}
onOk={handleCreateProject}
onCancel={() => setCreateProjectOpen(false)}
>
<Form form={projectForm} layout="vertical" preserve={false}>
<Form.Item
name="name"
label="项目名称"
rules={[
{ required: true, message: '请输入项目名称' },
{ max: 100, message: '项目名称最多 100 个字符' },
]}
>
<Input placeholder="例如:618 宣传片" />
</Form.Item>
<Form.Item name="description" label="项目描述">
<Input.TextArea placeholder="可选" rows={3} maxLength={500} />
</Form.Item>
</Form>
</Modal>
<InviteMemberModal
workspaceId={id!}
open={inviteModalOpen}
onClose={() => setInviteModalOpen(false)}
/>
</div>
);
};
export default WorkspaceDetail;
export const Component = WorkspaceDetail;
+2 -2
View File
@@ -17,8 +17,8 @@
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
/* Path alias */
+2 -10
View File
@@ -138,17 +138,9 @@ def _download_library_assets(
# 导入模型和会话
try:
from packages.adapters.sqlalchemy_impl.models import AssetModel
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from worker_app.db import SessionLocal
db_url = os.getenv("DATABASE_URL")
if not db_url:
logger.warning("DATABASE_URL not set, cannot fetch assets")
return []
engine = create_engine(db_url)
Session = sessionmaker(bind=engine)
session = Session()
session = SessionLocal()
try:
# 查询素材库中的视频素材