aidevops 3.32.5 → 3.32.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-api/src/status-adapter.ts +1 -0
- package/packages/gui-api/src/status-vault.ts +61 -18
- package/packages/gui-desktop/scripts/install-macos-app.sh +88 -3
- package/packages/gui-shared/src/fixtures.ts +1 -6
- package/packages/gui-web/src/App.tsx +10 -56
- package/packages/gui-web/src/AppWorkspace.tsx +5 -5
- package/packages/gui-web/src/DesktopStatusBar.tsx +1 -1
- package/packages/gui-web/src/SecuritySurface.tsx +187 -0
- package/packages/gui-web/src/StatusSurfaces.tsx +4 -170
- package/packages/gui-web/src/VaultAccessModal.tsx +123 -0
- package/packages/gui-web/src/VaultBadges.tsx +96 -36
- package/packages/gui-web/src/VaultSurface.tsx +282 -0
- package/packages/gui-web/src/status-client.ts +72 -24
- package/packages/gui-web/src/styles.css +385 -34
- package/packages/gui-web/src/useAppNavigation.ts +55 -0
- package/packages/gui-web/src/useGuiStatus.ts +79 -0
- package/packages/gui-web/src/useVaultAccessDialog.ts +160 -0
- package/packages/gui-web/src/vault-command-bridge.ts +27 -0
- package/setup.sh +3 -1
- package/packages/gui-web/src/VaultPassphraseModal.tsx +0 -147
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { type RefObject, useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { VaultDialogIntent } from "./VaultBadges";
|
|
3
|
+
import { type NativeVaultAction, postNativeVaultCommand, vaultCommandText } from "./vault-command-bridge";
|
|
4
|
+
|
|
5
|
+
export type VaultLaunchStatus = "idle" | "requesting" | "opened" | "copied" | "failed";
|
|
6
|
+
|
|
7
|
+
const terminalActions: Record<VaultDialogIntent, NativeVaultAction | null> = {
|
|
8
|
+
lock: "lock",
|
|
9
|
+
recover: "lost-passphrase",
|
|
10
|
+
setup: "init",
|
|
11
|
+
unavailable: null,
|
|
12
|
+
unlock: "unlock",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function terminalActionForIntent(intent: VaultDialogIntent): NativeVaultAction | null {
|
|
16
|
+
return terminalActions[intent];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useVaultDialogFocus({ dialogRef, intent, onClose, primaryActionRef }: {
|
|
20
|
+
dialogRef: RefObject<HTMLElement | null>;
|
|
21
|
+
intent: VaultDialogIntent;
|
|
22
|
+
onClose: () => void;
|
|
23
|
+
primaryActionRef: RefObject<HTMLButtonElement | null>;
|
|
24
|
+
}): void {
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
27
|
+
return () => previousFocus?.focus();
|
|
28
|
+
}, []);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const primaryAction = primaryActionRef.current;
|
|
32
|
+
if (primaryAction?.dataset.vaultIntent === intent) {
|
|
33
|
+
primaryAction.focus();
|
|
34
|
+
}
|
|
35
|
+
}, [intent, primaryActionRef]);
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const handleDialogKeys = (event: KeyboardEvent) => {
|
|
39
|
+
if (event.key === "Escape") {
|
|
40
|
+
onClose();
|
|
41
|
+
}
|
|
42
|
+
trapDialogFocus(event, dialogRef.current);
|
|
43
|
+
};
|
|
44
|
+
window.addEventListener("keydown", handleDialogKeys);
|
|
45
|
+
return () => window.removeEventListener("keydown", handleDialogKeys);
|
|
46
|
+
}, [dialogRef, onClose]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useVaultCommandLaunch({ intent, onRefresh, onTerminalLaunch }: {
|
|
50
|
+
intent: VaultDialogIntent;
|
|
51
|
+
onRefresh: () => Promise<void> | void;
|
|
52
|
+
onTerminalLaunch: () => void;
|
|
53
|
+
}): { launchStatus: VaultLaunchStatus; launchTerminal: () => Promise<void> } {
|
|
54
|
+
const [launchState, setLaunchState] = useState<{ intent: VaultDialogIntent; status: VaultLaunchStatus }>({ intent, status: "idle" });
|
|
55
|
+
const nativeResultTimeout = useRef<number | null>(null);
|
|
56
|
+
const timeoutIntent = useRef(intent);
|
|
57
|
+
const launchStatus = launchState.intent === intent ? launchState.status : "idle";
|
|
58
|
+
const setLaunchStatus = useCallback((status: VaultLaunchStatus) => setLaunchState({ intent, status }), [intent]);
|
|
59
|
+
const clearNativeResultTimeout = useCallback(() => clearScheduledTimeout(nativeResultTimeout), []);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
const handleNativeResult = (event: Event) => {
|
|
63
|
+
clearNativeResultTimeout();
|
|
64
|
+
const result = (event as CustomEvent<unknown>).detail;
|
|
65
|
+
setLaunchStatus(result === "opened" ? "opened" : "failed");
|
|
66
|
+
};
|
|
67
|
+
window.addEventListener("aidevops:vault-command-result", handleNativeResult);
|
|
68
|
+
return () => window.removeEventListener("aidevops:vault-command-result", handleNativeResult);
|
|
69
|
+
}, [clearNativeResultTimeout, setLaunchStatus]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (timeoutIntent.current !== intent) {
|
|
73
|
+
clearNativeResultTimeout();
|
|
74
|
+
timeoutIntent.current = intent;
|
|
75
|
+
}
|
|
76
|
+
return clearNativeResultTimeout;
|
|
77
|
+
}, [clearNativeResultTimeout, intent]);
|
|
78
|
+
|
|
79
|
+
const launchTerminal = async () => {
|
|
80
|
+
const terminalAction = terminalActionForIntent(intent);
|
|
81
|
+
if (terminalAction === null) {
|
|
82
|
+
await onRefresh();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const status = await requestVaultCommand(terminalAction);
|
|
87
|
+
setLaunchStatus(status);
|
|
88
|
+
if (status !== "failed") {
|
|
89
|
+
onTerminalLaunch();
|
|
90
|
+
}
|
|
91
|
+
if (status === "requesting") {
|
|
92
|
+
clearNativeResultTimeout();
|
|
93
|
+
nativeResultTimeout.current = scheduleNativeResultTimeout(intent, setLaunchState);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return { launchStatus, launchTerminal };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function requestVaultCommand(action: NativeVaultAction): Promise<"requesting" | "copied" | "failed"> {
|
|
101
|
+
if (postNativeVaultCommand(action)) {
|
|
102
|
+
return "requesting";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const copied = await copyVaultCommand(action);
|
|
106
|
+
return copied ? "copied" : "failed";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function copyVaultCommand(action: NativeVaultAction): Promise<boolean> {
|
|
110
|
+
let copied = false;
|
|
111
|
+
if (typeof navigator !== "undefined" && navigator.clipboard !== undefined) {
|
|
112
|
+
try {
|
|
113
|
+
await navigator.clipboard.writeText(vaultCommandText(action));
|
|
114
|
+
copied = true;
|
|
115
|
+
} catch {
|
|
116
|
+
copied = false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return copied;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function scheduleNativeResultTimeout(intent: VaultDialogIntent, setLaunchState: (update: (current: { intent: VaultDialogIntent; status: VaultLaunchStatus }) => { intent: VaultDialogIntent; status: VaultLaunchStatus }) => void): number {
|
|
123
|
+
return window.setTimeout(() => {
|
|
124
|
+
setLaunchState((current) => current.intent === intent && current.status === "requesting" ? { ...current, status: "failed" } : current);
|
|
125
|
+
}, 3000);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function clearScheduledTimeout(timeoutRef: { current: number | null }): void {
|
|
129
|
+
if (timeoutRef.current !== null) {
|
|
130
|
+
window.clearTimeout(timeoutRef.current);
|
|
131
|
+
timeoutRef.current = null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function trapDialogFocus(event: KeyboardEvent, dialog: HTMLElement | null): void {
|
|
136
|
+
if (event.key !== "Tab" || dialog === null) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const focusable = [...dialog.querySelectorAll<HTMLButtonElement>("button:not([disabled])")];
|
|
141
|
+
const first = focusable[0];
|
|
142
|
+
const last = focusable.at(-1);
|
|
143
|
+
if (first === undefined || last === undefined) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const activeIndex = focusable.indexOf(document.activeElement as HTMLButtonElement);
|
|
147
|
+
const targetIndex = dialogFocusTargetIndex(activeIndex, focusable.length, event.shiftKey);
|
|
148
|
+
if (targetIndex !== null) {
|
|
149
|
+
event.preventDefault();
|
|
150
|
+
focusable[targetIndex]?.focus();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function dialogFocusTargetIndex(activeIndex: number, focusableCount: number, shiftKey: boolean): number | null {
|
|
155
|
+
if (focusableCount === 0) return null;
|
|
156
|
+
if (activeIndex < 0) return shiftKey ? focusableCount - 1 : 0;
|
|
157
|
+
if (shiftKey && activeIndex === 0) return focusableCount - 1;
|
|
158
|
+
if (!shiftKey && activeIndex === focusableCount - 1) return 0;
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type NativeVaultAction = "init" | "unlock" | "lock" | "status" | "lost-passphrase";
|
|
2
|
+
|
|
3
|
+
interface WebKitVaultCommandWindow extends Window {
|
|
4
|
+
webkit?: {
|
|
5
|
+
messageHandlers?: {
|
|
6
|
+
vaultCommand?: {
|
|
7
|
+
postMessage: (action: NativeVaultAction) => void;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function postNativeVaultCommand(action: NativeVaultAction): boolean {
|
|
14
|
+
if (typeof window === "undefined") return false;
|
|
15
|
+
const handler = (window as WebKitVaultCommandWindow).webkit?.messageHandlers?.vaultCommand;
|
|
16
|
+
if (handler === undefined) return false;
|
|
17
|
+
try {
|
|
18
|
+
handler.postMessage(action);
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function vaultCommandText(action: NativeVaultAction): string {
|
|
26
|
+
return `aidevops vault ${action}`;
|
|
27
|
+
}
|
package/setup.sh
CHANGED
|
@@ -12,7 +12,7 @@ shopt -s inherit_errexit 2>/dev/null || true
|
|
|
12
12
|
# AI Assistant Server Access Framework Setup Script
|
|
13
13
|
# Helps developers set up the framework for their infrastructure
|
|
14
14
|
#
|
|
15
|
-
# Version: 3.32.
|
|
15
|
+
# Version: 3.32.7
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|
|
@@ -1435,6 +1435,7 @@ _setup_run_non_interactive() {
|
|
|
1435
1435
|
# (GH#21102 / t2926: missing setsid kills workers on every pulse restart).
|
|
1436
1436
|
_time_step "setup_setsid_advisory" setup_setsid_advisory
|
|
1437
1437
|
_time_step "check_python_upgrade_available" check_python_upgrade_available
|
|
1438
|
+
_time_step "setup_vault_python_env" setup_vault_python_env || print_warning "Vault crypto runtime setup encountered issues; Vault status remains metadata-only"
|
|
1438
1439
|
_time_step "set_permissions" set_permissions
|
|
1439
1440
|
_time_step "migrate_old_backups" migrate_old_backups
|
|
1440
1441
|
_time_step "migrate_loop_state_directories" migrate_loop_state_directories
|
|
@@ -1544,6 +1545,7 @@ _setup_run_non_interactive() {
|
|
|
1544
1545
|
# prompt order around runtime-specific installers and config updates.
|
|
1545
1546
|
_setup_run_interactive_runtime_tools() {
|
|
1546
1547
|
confirm_step "Deploy aidevops agents to runtime agent directories" && deploy_agents_to_runtimes
|
|
1548
|
+
confirm_step "Setup isolated Vault crypto runtime" && setup_vault_python_env
|
|
1547
1549
|
confirm_step "Setup Python environment (DSPy, crawl4ai)" && setup_python_env
|
|
1548
1550
|
confirm_step "Setup Node.js environment" && setup_nodejs_env
|
|
1549
1551
|
confirm_step "Install MCP packages globally (fast startup)" && install_mcp_packages
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState, type ReactElement } from "react";
|
|
2
|
-
import { FiAlertTriangle, FiCheckCircle, FiLock, FiShield, FiUnlock } from "react-icons/fi";
|
|
3
|
-
import type { GuiVaultStatusData } from "@aidevops/gui-shared";
|
|
4
|
-
import type { VaultDialogIntent } from "./VaultBadges";
|
|
5
|
-
|
|
6
|
-
export function VaultPassphraseModal({ intent, onClose, vault }: {
|
|
7
|
-
intent: VaultDialogIntent;
|
|
8
|
-
onClose: () => void;
|
|
9
|
-
vault: GuiVaultStatusData;
|
|
10
|
-
}): ReactElement {
|
|
11
|
-
const [step, setStep] = useState(0);
|
|
12
|
-
const [passphrase, setPassphrase] = useState("");
|
|
13
|
-
const [passphraseConfirm, setPassphraseConfirm] = useState("");
|
|
14
|
-
const [unlockPassphrase, setUnlockPassphrase] = useState("");
|
|
15
|
-
const [savedWarningAccepted, setSavedWarningAccepted] = useState(false);
|
|
16
|
-
const setupMode = intent === "setup";
|
|
17
|
-
const title = setupMode ? "Set Vault encryption passphrase" : intent === "unlock" ? "Unlock Vault" : "Lock Vault";
|
|
18
|
-
const actionLabel = setupMode ? "Use passphrase and continue" : intent === "unlock" ? "Unlock Vault" : "Lock Vault";
|
|
19
|
-
const setupPassphrasesMatch = passphrase.length > 0 && passphrase === passphraseConfirm;
|
|
20
|
-
const finalPassphraseMatches = unlockPassphrase.length > 0 && unlockPassphrase === passphrase;
|
|
21
|
-
const canContinue = useMemo(() => {
|
|
22
|
-
if (!setupMode) {
|
|
23
|
-
return intent === "lock" || unlockPassphrase.length > 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (step === 0) {
|
|
27
|
-
return setupPassphrasesMatch;
|
|
28
|
-
}
|
|
29
|
-
if (step === 1) {
|
|
30
|
-
return savedWarningAccepted;
|
|
31
|
-
}
|
|
32
|
-
return finalPassphraseMatches;
|
|
33
|
-
}, [finalPassphraseMatches, intent, savedWarningAccepted, setupMode, setupPassphrasesMatch, step, unlockPassphrase.length]);
|
|
34
|
-
|
|
35
|
-
useEffect(() => {
|
|
36
|
-
setStep(0);
|
|
37
|
-
setPassphrase("");
|
|
38
|
-
setPassphraseConfirm("");
|
|
39
|
-
setUnlockPassphrase("");
|
|
40
|
-
setSavedWarningAccepted(false);
|
|
41
|
-
}, [intent]);
|
|
42
|
-
|
|
43
|
-
const continueFlow = () => {
|
|
44
|
-
if (setupMode && step < 2) {
|
|
45
|
-
setStep((current) => current + 1);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
onClose();
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
<div className="vault-modal-backdrop" role="presentation">
|
|
54
|
-
<section
|
|
55
|
-
aria-label={title}
|
|
56
|
-
aria-modal="true"
|
|
57
|
-
className="vault-modal"
|
|
58
|
-
role="dialog"
|
|
59
|
-
>
|
|
60
|
-
<form
|
|
61
|
-
onSubmit={(event) => {
|
|
62
|
-
event.preventDefault();
|
|
63
|
-
if (canContinue) {
|
|
64
|
-
continueFlow();
|
|
65
|
-
}
|
|
66
|
-
}}
|
|
67
|
-
style={{ display: "contents" }}
|
|
68
|
-
>
|
|
69
|
-
<header className="vault-modal-header">
|
|
70
|
-
<span className="vault-modal-icon" aria-hidden="true">{intent === "lock" ? <FiLock /> : <FiUnlock />}</span>
|
|
71
|
-
<div>
|
|
72
|
-
<p className="eyebrow">aidevops Vault</p>
|
|
73
|
-
<h2>{title}</h2>
|
|
74
|
-
</div>
|
|
75
|
-
</header>
|
|
76
|
-
{setupMode ? <SetupStep accepted={savedWarningAccepted} confirm={passphraseConfirm} match={setupPassphrasesMatch} onAcceptChange={setSavedWarningAccepted} onConfirmChange={setPassphraseConfirm} onPassphraseChange={setPassphrase} onUnlockPassphraseChange={setUnlockPassphrase} passphrase={passphrase} step={step} unlockMatch={finalPassphraseMatches} unlockPassphrase={unlockPassphrase} /> : <UnlockStep intent={intent} onUnlockPassphraseChange={setUnlockPassphrase} unlockPassphrase={unlockPassphrase} vault={vault} />}
|
|
77
|
-
<footer className="vault-modal-actions">
|
|
78
|
-
<button className="secondary-action" onClick={onClose} type="button">Cancel</button>
|
|
79
|
-
<button className="primary-action" disabled={!canContinue} type="submit">{setupMode && step < 2 ? "Continue" : actionLabel}</button>
|
|
80
|
-
</footer>
|
|
81
|
-
<p className="vault-modal-footnote">Passphrases stay in this local dialog state only and are never shown in logs, issues, command arguments, or AI chat.</p>
|
|
82
|
-
</form>
|
|
83
|
-
</section>
|
|
84
|
-
</div>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function SetupStep({ accepted, confirm, match, onAcceptChange, onConfirmChange, onPassphraseChange, onUnlockPassphraseChange, passphrase, step, unlockMatch, unlockPassphrase }: {
|
|
89
|
-
accepted: boolean;
|
|
90
|
-
confirm: string;
|
|
91
|
-
match: boolean;
|
|
92
|
-
onAcceptChange: (accepted: boolean) => void;
|
|
93
|
-
onConfirmChange: (value: string) => void;
|
|
94
|
-
onPassphraseChange: (value: string) => void;
|
|
95
|
-
onUnlockPassphraseChange: (value: string) => void;
|
|
96
|
-
passphrase: string;
|
|
97
|
-
step: number;
|
|
98
|
-
unlockMatch: boolean;
|
|
99
|
-
unlockPassphrase: string;
|
|
100
|
-
}): ReactElement {
|
|
101
|
-
if (step === 1) {
|
|
102
|
-
return (
|
|
103
|
-
<div className="vault-modal-body">
|
|
104
|
-
<div className="notice warning-notice" role="alert"><FiAlertTriangle aria-hidden="true" /> Save this passphrase in a password manager now. aidevops cannot recover encrypted data if it is lost.</div>
|
|
105
|
-
<label className="vault-confirm-check"><input checked={accepted} onChange={(event) => onAcceptChange(event.currentTarget.checked)} type="checkbox" /> I have saved the Vault passphrase in a password manager and understand recovery is not possible without it.</label>
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (step === 2) {
|
|
111
|
-
return (
|
|
112
|
-
<div className="vault-modal-body">
|
|
113
|
-
<p>Final safety check: enter the newly saved passphrase once more before any confidential app data is encrypted.</p>
|
|
114
|
-
<label className="vault-field"><span>Use saved passphrase</span><input autoComplete="current-password" onChange={(event) => onUnlockPassphraseChange(event.currentTarget.value)} type="password" value={unlockPassphrase} /></label>
|
|
115
|
-
{unlockPassphrase.length > 0 ? <p className={unlockMatch ? "vault-valid" : "vault-invalid"}>{unlockMatch ? "Passphrase matches." : "Passphrase does not match the saved setup value."}</p> : null}
|
|
116
|
-
</div>
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return (
|
|
121
|
-
<div className="vault-modal-body">
|
|
122
|
-
<p>Create the encryption passphrase for this local Vault. Use a long unique value generated by your password manager.</p>
|
|
123
|
-
<label className="vault-field"><span>New passphrase</span><input autoComplete="new-password" autoFocus onChange={(event) => onPassphraseChange(event.currentTarget.value)} type="password" value={passphrase} /></label>
|
|
124
|
-
<label className="vault-field"><span>Repeat passphrase</span><input autoComplete="new-password" onChange={(event) => onConfirmChange(event.currentTarget.value)} type="password" value={confirm} /></label>
|
|
125
|
-
{confirm.length > 0 ? <p className={match ? "vault-valid" : "vault-invalid"}>{match ? "Passphrases match." : "Passphrases must match before continuing."}</p> : null}
|
|
126
|
-
</div>
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function UnlockStep({ intent, onUnlockPassphraseChange, unlockPassphrase, vault }: {
|
|
131
|
-
intent: VaultDialogIntent;
|
|
132
|
-
onUnlockPassphraseChange: (value: string) => void;
|
|
133
|
-
unlockPassphrase: string;
|
|
134
|
-
vault: GuiVaultStatusData;
|
|
135
|
-
}): ReactElement {
|
|
136
|
-
if (intent === "lock") {
|
|
137
|
-
return <div className="vault-modal-body"><p><FiLock aria-hidden="true" /> Locking forgets in-memory keys for this local session. Protected previews will be hidden again.</p></div>;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return (
|
|
141
|
-
<div className="vault-modal-body">
|
|
142
|
-
<p><FiShield aria-hidden="true" /> Enter your Vault passphrase to unlock protected previews and write actions for this local session.</p>
|
|
143
|
-
<label className="vault-field"><span>Vault passphrase</span><input autoComplete="current-password" autoFocus onChange={(event) => onUnlockPassphraseChange(event.currentTarget.value)} type="password" value={unlockPassphrase} /></label>
|
|
144
|
-
<div className="notice compact-notice" role="note"><FiCheckCircle aria-hidden="true" /> {vault.unlock_hint}</div>
|
|
145
|
-
</div>
|
|
146
|
-
);
|
|
147
|
-
}
|