feat: add initial frontend setup with styles, types, and testing framework
- Created a global CSS file for styling the frontend with responsive design. - Introduced TypeScript types for various entities including Research, Alpha, and Account. - Implemented Playwright tests for account management and data synchronization workflows. - Configured TypeScript with strict settings and included necessary libraries. - Set up Vite as the build tool with React plugin and API proxy configuration. - Added a Python script to initialize environment variables securely.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Button,
|
||||
Empty,
|
||||
Progress,
|
||||
SideSheet,
|
||||
Tag,
|
||||
Toast,
|
||||
} from "@douyinfe/semi-ui-19";
|
||||
import { api, formatTime, jobLabels, jobStateLabels, post } from "../api";
|
||||
import type { Job } from "../types";
|
||||
|
||||
export function JobPanel({
|
||||
visible,
|
||||
jobs,
|
||||
timezone,
|
||||
onClose,
|
||||
onChange,
|
||||
onAccount,
|
||||
}: {
|
||||
visible: boolean;
|
||||
jobs: Job[];
|
||||
timezone?: string;
|
||||
onClose: () => void;
|
||||
onChange: () => void;
|
||||
onAccount: () => void;
|
||||
}) {
|
||||
const [errors, setErrors] = useState<
|
||||
Record<string, { alpha_id: string; error: string }[]>
|
||||
>({});
|
||||
const [busy, setBusy] = useState("");
|
||||
async function action(job: Job, name: string) {
|
||||
setBusy(job.id);
|
||||
try {
|
||||
await post(`/sync-jobs/${job.id}/${name}`);
|
||||
onChange();
|
||||
} catch (e) {
|
||||
Toast.error((e as Error).message);
|
||||
} finally {
|
||||
setBusy("");
|
||||
}
|
||||
}
|
||||
return (
|
||||
<SideSheet
|
||||
closeOnEsc
|
||||
className="job-panel"
|
||||
title="同步任务"
|
||||
visible={visible}
|
||||
width={520}
|
||||
onCancel={onClose}
|
||||
>
|
||||
<p className="muted">同步进度保存在服务端,关闭此面板不会中断任务。</p>
|
||||
{!jobs.length && <Empty description="暂时没有同步任务" />}
|
||||
{jobs.map((job) => (
|
||||
<article className="job-card" key={job.id}>
|
||||
<div className="job-header">
|
||||
<strong>{jobLabels[job.kind] ?? job.kind}</strong>
|
||||
<Tag
|
||||
color={
|
||||
job.status === "completed"
|
||||
? "green"
|
||||
: ["failed", "completed_with_errors"].includes(job.status)
|
||||
? "red"
|
||||
: "blue"
|
||||
}
|
||||
>
|
||||
{jobStateLabels[job.status] ?? job.status}
|
||||
</Tag>
|
||||
</div>
|
||||
<p className="muted">{formatTime(job.created_at, timezone)}</p>
|
||||
<div className="job-numbers">
|
||||
<span>
|
||||
已处理 <b>{job.processed}</b>
|
||||
</span>
|
||||
<span>
|
||||
失败 <b>{job.failed}</b>
|
||||
</span>
|
||||
<span>
|
||||
总数 <b>{job.total ?? "计算中"}</b>
|
||||
</span>
|
||||
</div>
|
||||
{job.total !== null && job.total > 0 && (
|
||||
<Progress
|
||||
percent={Math.min(
|
||||
100,
|
||||
Math.round(((job.processed + job.failed) / job.total) * 100),
|
||||
)}
|
||||
showInfo={false}
|
||||
/>
|
||||
)}
|
||||
{job.error && <p className="error-text">{job.error}</p>}
|
||||
{job.next_retry_at && (
|
||||
<p className="muted">
|
||||
下次重试:{formatTime(job.next_retry_at, timezone)}
|
||||
</p>
|
||||
)}
|
||||
<div className="inline-actions">
|
||||
{[
|
||||
"queued",
|
||||
"running",
|
||||
"waiting_auth",
|
||||
"waiting_connection",
|
||||
].includes(job.status) && (
|
||||
<Button
|
||||
size="small"
|
||||
loading={busy === job.id}
|
||||
onClick={() => void action(job, "cancel")}
|
||||
>
|
||||
取消
|
||||
</Button>
|
||||
)}
|
||||
{["failed", "cancelled", "completed_with_errors"].includes(
|
||||
job.status,
|
||||
) && (
|
||||
<Button
|
||||
size="small"
|
||||
loading={busy === job.id}
|
||||
onClick={() => void action(job, "retry")}
|
||||
>
|
||||
重试未完成部分
|
||||
</Button>
|
||||
)}
|
||||
{["waiting_auth", "waiting_connection"].includes(job.status) && (
|
||||
<Button size="small" onClick={onAccount}>
|
||||
处理账户连接
|
||||
</Button>
|
||||
)}
|
||||
{job.failed > 0 && (
|
||||
<Button
|
||||
size="small"
|
||||
theme="borderless"
|
||||
onClick={() =>
|
||||
api<{ alpha_id: string; error: string }[]>(
|
||||
`/sync-jobs/${job.id}/errors`,
|
||||
)
|
||||
.then((rows) => setErrors({ ...errors, [job.id]: rows }))
|
||||
.catch((e) => Toast.error(e.message))
|
||||
}
|
||||
>
|
||||
查看失败记录
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
{errors[job.id]?.map((e) => (
|
||||
<p className="error-text" key={e.alpha_id}>
|
||||
{e.alpha_id}:{e.error}
|
||||
</p>
|
||||
))}
|
||||
</article>
|
||||
))}
|
||||
</SideSheet>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user