返回 DeepSeek-Reasonix
RemoteSecretDialog.tsx
根目录 / desktop / frontend / src / components / RemoteSecretDialog.tsx
1 import { useEffect, useRef, useState } 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 /** Global, one-shot SSH password/private-key passphrase prompt. The secret is
9 * sent directly to Go and never enters the shared store or a status event. */
10 export function RemoteSecretDialog() {
11 const t = useT();
12 const prompt = useRemoteStore((s) => s.pendingSecretPrompt);
13 const clear = useRemoteStore((s) => s.clearPendingSecretPrompt);
14 const [secret, setSecret] = useState("");
15 const inputRef = useRef<HTMLInputElement>(null);
16 const resolvingRef = useRef(false);
17
18 useEffect(() => {
19 setSecret("");
20 resolvingRef.current = false;
21 if (prompt) queueMicrotask(() => inputRef.current?.focus());
22 }, [prompt]);
23
24 if (!prompt) return null;
25
26 const resolve = async (accept: boolean) => {
27 if (resolvingRef.current) return;
28 resolvingRef.current = true;
29 try {
30 await app.ConfirmRemoteSecret(prompt.hostId, prompt.promptId, accept ? secret : "", accept);
31 } finally {
32 clear(prompt);
33 setSecret("");
34 resolvingRef.current = false;
35 }
36 };
37
38 return createPortal(
39 <div className="remote-hostkey-overlay" role="dialog" aria-modal="true" aria-labelledby="remote-secret-title">
40 <form
41 className="remote-hostkey-dialog"
42 onSubmit={(event) => {
43 event.preventDefault();
44 void resolve(true);
45 }}
46 >
47 <h2 id="remote-secret-title" className="remote-hostkey-dialog__title">
48 {t(`remote.secret.${prompt.kind}.title`)}
49 </h2>
50 <p>{t(`remote.secret.${prompt.kind}.body`, { host: prompt.host })}</p>
51 {prompt.kind === "passphrase" && prompt.identity ? (
52 <p>{t("remote.secret.passphrase.identity", { identity: prompt.identity })}</p>
53 ) : null}
54 <input
55 ref={inputRef}
56 className="remote-secret-dialog__input"
57 type="password"
58 value={secret}
59 autoComplete="off"
60 aria-label={t(`remote.secret.${prompt.kind}.label`)}
61 onChange={(event) => setSecret(event.target.value)}
62 onKeyDown={(event) => {
63 if (event.key === "Escape") {
64 event.preventDefault();
65 void resolve(false);
66 }
67 }}
68 />
69 <div className="remote-hostkey-dialog__actions">
70 <button type="button" className="btn" onClick={() => void resolve(false)}>
71 {t("remote.secret.cancel")}
72 </button>
73 <button type="submit" className="btn btn--primary">
74 {t("remote.secret.continue")}
75 </button>
76 </div>
77 </form>
78 </div>,
79 document.body,
80 );
81 }
82
82 lines Plain Text