返回 DeepSeek-Reasonix
RemoteHostKeyDialog.tsx
根目录 / desktop / frontend / src / components / RemoteHostKeyDialog.tsx
1 import { useEffect, useRef } from "react";
2 import { createPortal } from "react-dom";
3
4 import { app } from "../lib/bridge";
5 import { useT } from "../lib/i18n";
6 import { useRemoteStore } from "../store/remote";
7
8 /** RemoteHostKeyDialog is the global TOFU confirmation modal. It renders off
9 * the remote store's pendingFingerprint so it appears regardless of which
10 * surface initiated the connection, and resolves via ConfirmRemoteHostKey. */
11 export function RemoteHostKeyDialog() {
12 const t = useT();
13 const fp = useRemoteStore((s) => s.pendingFingerprint);
14 const clear = useRemoteStore((s) => s.clearPendingFingerprint);
15 const acceptRef = useRef<HTMLButtonElement>(null);
16 const resolvingRef = useRef(false);
17
18 useEffect(() => {
19 if (fp) acceptRef.current?.focus();
20 }, [fp]);
21
22 useEffect(() => {
23 if (!fp) return;
24 const onKey = (e: KeyboardEvent) => {
25 if (e.key === "Escape") {
26 e.preventDefault();
27 void resolve(false);
28 }
29 };
30 window.addEventListener("keydown", onKey);
31 return () => window.removeEventListener("keydown", onKey);
32 // eslint-disable-next-line react-hooks/exhaustive-deps
33 }, [fp]);
34
35 if (!fp) return null;
36
37 const resolve = async (accept: boolean) => {
38 if (resolvingRef.current) return;
39 resolvingRef.current = true;
40 try {
41 await app.ConfirmRemoteHostKey(fp.hostId, accept);
42 } finally {
43 clear(fp);
44 resolvingRef.current = false;
45 }
46 };
47
48 return createPortal(
49 <div className="remote-hostkey-overlay" role="dialog" aria-modal="true" aria-labelledby="remote-hostkey-title">
50 <div className="remote-hostkey-dialog">
51 <h2 id="remote-hostkey-title" className="remote-hostkey-dialog__title">
52 {t("remote.fingerprint.title")}
53 </h2>
54 <p>{t("remote.fingerprint.body", { host: fp.hostId })}</p>
55 <dl className="remote-hostkey-dialog__facts">
56 <dt>{t("remote.fingerprint.type")}</dt>
57 <dd>{fp.keyType}</dd>
58 <dt>{t("remote.fingerprint.sha256")}</dt>
59 <dd className="remote-hostkey-dialog__fp">{fp.sha256}</dd>
60 </dl>
61 <div className="remote-hostkey-dialog__actions">
62 <button className="btn" onClick={() => void resolve(false)}>
63 {t("remote.fingerprint.reject")}
64 </button>
65 <button ref={acceptRef} className="btn btn--danger" onClick={() => void resolve(true)}>
66 {t("remote.fingerprint.accept")}
67 </button>
68 </div>
69 </div>
70 </div>,
71 document.body,
72 );
73 }
74
74 lines Plain Text