PRA-only-demo/src/renderer/src/pages/SettingsPage.tsx

76 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useEffect, useMemo, useState } from "react";
import { Cpu, Database, FolderCog, Globe2, HardDrive, Info, ShieldCheck } from "lucide-react";
import type { LucideIcon } from "lucide-react";
import type { ReactNode } from "react";
import type { AppInfo, Catalog } from "@shared/types";
type SettingsTab = "about" | "catalog" | "execution" | "browser";
export function SettingsPage({ catalog }: { catalog: Catalog }) {
const [tab, setTab] = useState<SettingsTab>("about");
const [info, setInfo] = useState<AppInfo | null>(null);
const [handlers, setHandlers] = useState<{ count: number; ids: string[] } | null>(null);
useEffect(() => {
void window.rpa.app.info().then(setInfo).catch(() => setInfo(null));
void window.rpa.run.capabilities().then(setHandlers).catch(() => setHandlers(null));
}, []);
const executable = catalog.components.filter((item) => item.implemented).length;
const groups = useMemo(() => catalog.groups.map((group) => {
const components = catalog.components.filter((component) => component.group === group.id);
const connected = components.filter((component) => component.implemented).length;
return { ...group, connected, pending: components.length - connected };
}), [catalog]);
return (
<div className="settings">
<aside className="panel setting-nav">
<button type="button" className={tab === "about" ? "active" : ""} onClick={() => setTab("about")}>关于与运行环境</button>
<button type="button" className={tab === "catalog" ? "active" : ""} onClick={() => setTab("catalog")}>组件目录</button>
<button type="button" className={tab === "execution" ? "active" : ""} onClick={() => setTab("execution")}>执行与证据</button>
<button type="button" className={tab === "browser" ? "active" : ""} onClick={() => setTab("browser")}>浏览器白名单</button>
</aside>
<section className="panel setting-body">
{tab === "about" && (
<>
<SettingCard icon={Info} title="版本与运行环境">
<dl className="kv"><dt>应用版本</dt><dd>{info?.version || "0.1.0"}</dd><dt>Electron / Chromium</dt><dd>{info?.electron || "-"} / {info?.chrome || "-"}</dd><dt>Node</dt><dd>{info?.node || "-"}</dd><dt>运行模式</dt><dd>{info?.packaged ? "打包运行(离线)" : "开发模式"}</dd><dt>数据目录</dt><dd>{info?.userData || "-"}</dd><dt>目录文件</dt><dd>{info?.catalogPath || "-"}</dd></dl>
</SettingCard>
<SettingCard icon={FolderCog} title="产品边界"><p className="field-hint setting-copy">组件元数据来自项目内 1016 项目录。移动端、机械臂、U 盾、远程桌面、SAP/IE 专用通道、云服务、数据库和 FTP 保持待接入,不随当前版本承诺执行。</p></SettingCard>
</>
)}
{tab === "catalog" && (
<>
<SettingCard icon={Database} title="组件接入概览">
<dl className="kv"><dt>目录总数</dt><dd>{catalog.counts.total} 项</dd><dt>已接入执行器</dt><dd>{executable} 项({((executable / catalog.counts.total) * 100).toFixed(1)}%)</dd><dt>待接入</dt><dd>{catalog.counts.total - executable} 项</dd><dt>注册处理器 ID</dt><dd>{handlers?.count ?? "—"}</dd></dl>
</SettingCard>
<div className="setting-card"><h3><Database className="icon" />分类覆盖</h3><div className="setting-group-table">{groups.map((group) => <div key={group.id}><strong>{group.label}</strong><span>{group.connected} 已接入</span><span>{group.pending} 待接入</span><progress max={group.count} value={group.connected} /></div>)}</div></div>
</>
)}
{tab === "execution" && (
<>
<SettingCard icon={Cpu} title="执行方式"><dl className="kv"><dt>执行位置</dt><dd>本机主进程</dd><dt>浏览器</dt><dd>独立 Electron 运行窗口 + CDP</dd><dt>桌面</dt><dd>PowerShell / user32 本地桥</dd><dt>Office</dt><dd>ExcelJS / PDF.js / pdf-lib / Word COM</dd><dt>运行前检查</dt><dd>待接入、必填、多出口、断链</dd></dl></SettingCard>
<SettingCard icon={HardDrive} title="数据与证据"><p className="field-hint setting-copy">流程保存到数据目录的 flows/;运行结果、错误和截图写入 runs/&lt;runId&gt;/。任务列表会读取最近一次运行的状态、时间、耗时和失败原因。</p></SettingCard>
<SettingCard icon={ShieldCheck} title="安全边界"><p className="field-hint setting-copy">运行期默认离线。桌面动作只在流程显式运行时执行;未接入能力不会静默成功,而是在运行前检查或节点执行时返回明确错误。</p></SettingCard>
</>
)}
{tab === "browser" && (
<>
<SettingCard icon={Globe2} title="浏览器域名白名单"><dl className="kv"><dt>默认允许</dt><dd>127.0.0.1、localhost、example.com</dd><dt>扩展方式</dt><dd>环境变量 RPA_ALLOWED_DOMAINS</dd><dt>格式</dt><dd>逗号分隔域名,不含协议和路径</dd></dl></SettingCard>
<SettingCard icon={ShieldCheck} title="限制说明"><p className="field-hint setting-copy">导航只允许 HTTP/HTTPS。未列入白名单的域名会返回 blocked_url;文件协议、脚本协议和任意本地路径不会由浏览器导航组件打开。</p></SettingCard>
</>
)}
</section>
</div>
);
}
function SettingCard({ icon: Icon, title, children }: { icon: LucideIcon; title: string; children: ReactNode }) {
return <div className="setting-card"><h3><Icon className="icon" />{title}</h3>{children}</div>;
}