You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

165 lines
5.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"use client";
import { useCallback, useEffect, useState } from "react";
import { AdminLayout } from "@/components/layout/admin-layout";
import { PageHeader } from "@/components/layout/page-header";
import { Card } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
import { ErrorBanner } from "@/components/ui/error-banner";
import { PrimaryButton, SecondaryButton } from "@/components/ui/primary-button";
import { InputField } from "@/components/ui/input-field";
import {
adminGetRpaStatus,
adminPauseWorker,
} from "@/lib/frontend/api-client";
import { useAuth } from "@/hooks/use-auth";
import type { DataPageStatus, RpaStatus } from "@/lib/frontend/types";
export default function RpaAdminPage() {
const { token } = useAuth();
const [status, setStatus] = useState<DataPageStatus>("loading");
const [data, setData] = useState<RpaStatus | null>(null);
const [pauseWorkerId, setPauseWorkerId] = useState("");
const [pauseMinutes, setPauseMinutes] = useState("10");
const [pausing, setPausing] = useState(false);
const [message, setMessage] = useState<string | null>(null);
const load = useCallback(async () => {
if (!token) return;
setStatus("loading");
const res = await adminGetRpaStatus("", token);
if (res.code !== 0) {
setStatus("error");
return;
}
setData(res.data);
if (res.data.workers[0] && !pauseWorkerId) {
setPauseWorkerId(res.data.workers[0].worker_id);
}
setStatus("success");
}, [token, pauseWorkerId]);
useEffect(() => {
void load();
const id = setInterval(() => void load(), 10_000);
return () => clearInterval(id);
}, [load]);
const handlePause = async () => {
if (!token || !pauseWorkerId) return;
setPausing(true);
setMessage(null);
const res = await adminPauseWorker(
"",
token,
pauseWorkerId,
Number(pauseMinutes),
);
setPausing(false);
if (res.code !== 0) {
setMessage(res.message);
return;
}
setMessage("Worker 已暂停");
void load();
};
return (
<AdminLayout>
<PageHeader
title="RPA 状态"
subtitle="Worker 在线状态、队列深度与熔断"
/>
{status === "loading" && <Skeleton className="h-40 w-full" />}
{status === "error" && (
<ErrorBanner
action={<SecondaryButton onClick={() => void load()}></SecondaryButton>}
>
RPA
</ErrorBanner>
)}
{status === "success" && data && (
<div className="space-y-6">
<div className="grid gap-4 sm:grid-cols-3">
<Card>
<p className="text-sm text-text-secondary"></p>
<p className="mt-1 text-2xl font-semibold num">{data.queue_depth}</p>
</Card>
<Card>
<p className="text-sm text-text-secondary">24h RPA </p>
<p className="mt-1 text-2xl font-semibold num">
{data.success_rate_24h}%
</p>
</Card>
<Card>
<p className="text-sm text-text-secondary"></p>
<p
className={`mt-1 text-2xl font-semibold ${
data.circuit_open ? "text-error" : "text-success"
}`}
>
{data.circuit_open ? "已熔断" : "正常"}
</p>
</Card>
</div>
<Card>
<h3 className="mb-4 font-semibold">Worker </h3>
{data.workers.length === 0 ? (
<p className="text-sm text-text-secondary"> Worker </p>
) : (
<ul className="space-y-3">
{data.workers.map((w) => (
<li
key={w.worker_id}
className="flex flex-wrap items-center justify-between gap-2 rounded-md border border-border px-4 py-3 text-sm"
>
<span className="font-mono">{w.worker_id}</span>
<span className={w.online ? "text-success" : "text-text-disabled"}>
{w.online ? "在线" : "离线"}
</span>
{w.paused_seconds > 0 && (
<span className="text-warning">
{w.paused_seconds}s
</span>
)}
</li>
))}
</ul>
)}
</Card>
<Card>
<h3 className="mb-4 font-semibold"> Worker</h3>
<div className="grid gap-4 sm:grid-cols-3">
<InputField
label="Worker ID"
name="worker_id"
value={pauseWorkerId}
onChange={(e) => setPauseWorkerId(e.target.value)}
/>
<InputField
label="暂停时长(分钟)"
name="duration"
type="number"
value={pauseMinutes}
onChange={(e) => setPauseMinutes(e.target.value)}
/>
<div className="flex items-end">
<PrimaryButton loading={pausing} onClick={() => void handlePause()}>
Worker
</PrimaryButton>
</div>
</div>
{message && (
<p className="mt-3 text-sm text-text-secondary">{message}</p>
)}
</Card>
</div>
)}
</AdminLayout>
);
}