aidevops 3.31.81 → 3.32.0
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 +1 -1
- package/VERSION +1 -1
- package/aidevops.sh +1 -1
- package/package.json +1 -1
- package/packages/gui-shared/src/fixtures.ts +45 -1
- package/packages/gui-web/src/App.tsx +12 -0
- package/packages/gui-web/src/AppNavigation.tsx +18 -8
- package/packages/gui-web/src/AppWorkspace.tsx +19 -11
- package/packages/gui-web/src/StatusSurfaces.tsx +11 -10
- package/packages/gui-web/src/VaultBadges.tsx +36 -2
- package/packages/gui-web/src/VaultPassphraseModal.tsx +132 -0
- package/packages/gui-web/src/styles.css +142 -0
- package/setup.sh +1 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ The result: an AI operations platform that manages projects across every busines
|
|
|
60
60
|
[](https://github.com/marcusquinn)
|
|
61
61
|
|
|
62
62
|
<!-- Release & Version Info -->
|
|
63
|
-
[](https://github.com/marcusquinn/aidevops/releases)
|
|
64
64
|
[](https://www.npmjs.com/package/aidevops)
|
|
65
65
|
[](https://github.com/marcusquinn/homebrew-tap)
|
|
66
66
|
[](https://github.com/marcusquinn/aidevops)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.32.0
|
package/aidevops.sh
CHANGED
package/package.json
CHANGED
|
@@ -389,12 +389,56 @@ export const statusFixture: GuiStatusData = {
|
|
|
389
389
|
label: "Fleet, sync, and secure messages",
|
|
390
390
|
data_class: "mail/messages",
|
|
391
391
|
labels: ["client-confidential"],
|
|
392
|
-
surface_ids: ["devices", "messagingAccounts", "emailAccounts"
|
|
392
|
+
surface_ids: ["devices", "messagingAccounts", "emailAccounts"],
|
|
393
393
|
encrypted: true,
|
|
394
394
|
state: "planned",
|
|
395
395
|
preview_policy: "hidden_while_locked",
|
|
396
396
|
actions_policy: "disabled_while_locked",
|
|
397
397
|
},
|
|
398
|
+
{
|
|
399
|
+
id: "ai-session-history",
|
|
400
|
+
label: "AI sessions, worker threads, and prompts",
|
|
401
|
+
data_class: "session/history",
|
|
402
|
+
labels: ["confidential", "local-ai", "provider-policy"],
|
|
403
|
+
surface_ids: ["aiSessions", "workers"],
|
|
404
|
+
encrypted: true,
|
|
405
|
+
state: "planned",
|
|
406
|
+
preview_policy: "metadata_only",
|
|
407
|
+
actions_policy: "disabled_while_locked",
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
id: "repo-project-metadata",
|
|
411
|
+
label: "Repos, deployments, routines, and project metadata",
|
|
412
|
+
data_class: "repo/project metadata",
|
|
413
|
+
labels: ["internal", "client-confidential"],
|
|
414
|
+
surface_ids: ["git", "repos", "projects", "deployments", "routines"],
|
|
415
|
+
encrypted: true,
|
|
416
|
+
state: "planned",
|
|
417
|
+
preview_policy: "metadata_only",
|
|
418
|
+
actions_policy: "disabled_while_locked",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
id: "infrastructure-inventory",
|
|
422
|
+
label: "Infrastructure, accounts, domains, and servers",
|
|
423
|
+
data_class: "infrastructure inventory",
|
|
424
|
+
labels: ["confidential", "credential-adjacent"],
|
|
425
|
+
surface_ids: ["vpnsProxies", "apps", "installation", "registrars", "hosts", "servers", "domains"],
|
|
426
|
+
encrypted: true,
|
|
427
|
+
state: "planned",
|
|
428
|
+
preview_policy: "metadata_only",
|
|
429
|
+
actions_policy: "disabled_while_locked",
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: "creator-business-records",
|
|
433
|
+
label: "Creator, business, and client records",
|
|
434
|
+
data_class: "business records",
|
|
435
|
+
labels: ["client-confidential", "personal-data"],
|
|
436
|
+
surface_ids: ["brands", "personas", "websites", "forums", "socialMedia", "marketplaces", "tasks", "contacts", "events", "notes", "bookmarks", "inbox", "campaigns", "cases", "projectConfig", "feedback", "knowledge", "maintenance", "performance", "reports"],
|
|
437
|
+
encrypted: true,
|
|
438
|
+
state: "planned",
|
|
439
|
+
preview_policy: "metadata_only",
|
|
440
|
+
actions_policy: "disabled_while_locked",
|
|
441
|
+
},
|
|
398
442
|
],
|
|
399
443
|
devices: [
|
|
400
444
|
{
|
|
@@ -8,6 +8,8 @@ import { DesktopStatusBar } from "./DesktopStatusBar";
|
|
|
8
8
|
import { type NavigationHistory, nextHistoryIndex, useNavigationHistoryKeyboard } from "./navigation-history";
|
|
9
9
|
import { ScreenshotCaptureNotificationHost } from "./ScreenshotCaptureNotification";
|
|
10
10
|
import { fetchStatus, mockedStatus } from "./status-client";
|
|
11
|
+
import type { VaultDialogIntent } from "./VaultBadges";
|
|
12
|
+
import { VaultPassphraseModal } from "./VaultPassphraseModal";
|
|
11
13
|
|
|
12
14
|
interface WebKitBridgeWindow extends Window {
|
|
13
15
|
webkit?: {
|
|
@@ -105,6 +107,7 @@ export function App(): ReactElement {
|
|
|
105
107
|
const [conversationMode, setConversationMode] = useState<ConversationMode>("ai");
|
|
106
108
|
const [selectedLocalRepoIndex, setSelectedLocalRepoIndex] = useState(0);
|
|
107
109
|
const [selectedSessionId, setSelectedSessionId] = useState<string | undefined>();
|
|
110
|
+
const [vaultDialogIntent, setVaultDialogIntent] = useState<VaultDialogIntent | null>(null);
|
|
108
111
|
const [systemTheme, setSystemTheme] = useState<"light" | "dark">("light");
|
|
109
112
|
const resolvedTheme = themePreference === "system" ? systemTheme : themePreference;
|
|
110
113
|
const activeSurface: SurfaceId = navigation.entries[navigation.index] ?? "overview";
|
|
@@ -209,6 +212,12 @@ export function App(): ReactElement {
|
|
|
209
212
|
setSelectedLocalRepoIndex((current) => Math.min(current, Math.max(0, status.data.local_repos.repos.length - 1)));
|
|
210
213
|
}, [status.data.local_repos.repos.length]);
|
|
211
214
|
|
|
215
|
+
useEffect(() => {
|
|
216
|
+
if (!statusLoading && status.data.vault.readiness.setup_required) {
|
|
217
|
+
setVaultDialogIntent("setup");
|
|
218
|
+
}
|
|
219
|
+
}, [status.data.vault.readiness.setup_required, statusLoading]);
|
|
220
|
+
|
|
212
221
|
return (
|
|
213
222
|
<main className={machineRailVisible ? "app-shell" : "app-shell machine-rail-collapsed"} aria-busy={statusLoading}>
|
|
214
223
|
<div className="desktop-titlebar-tagline" aria-hidden="true">Your data protected. Your systems managed. Your creations published.</div>
|
|
@@ -221,6 +230,7 @@ export function App(): ReactElement {
|
|
|
221
230
|
conversationMode={conversationMode}
|
|
222
231
|
fontSizePreference={fontSizePreference}
|
|
223
232
|
fontPreference={fontPreference}
|
|
233
|
+
onVaultRequest={(intent) => setVaultDialogIntent(intent)}
|
|
224
234
|
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
225
235
|
selectedSessionId={selectedSessionId}
|
|
226
236
|
setAccentHue={setAccentHue}
|
|
@@ -252,6 +262,7 @@ export function App(): ReactElement {
|
|
|
252
262
|
fileRoot={fileRoot}
|
|
253
263
|
goBack={goBack}
|
|
254
264
|
goForward={goForward}
|
|
265
|
+
onVaultRequest={(intent) => setVaultDialogIntent(intent)}
|
|
255
266
|
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
256
267
|
selectedSessionId={selectedSessionId}
|
|
257
268
|
setActiveSurface={setActiveSurface}
|
|
@@ -263,6 +274,7 @@ export function App(): ReactElement {
|
|
|
263
274
|
status={status.data}
|
|
264
275
|
/>
|
|
265
276
|
<DesktopStatusBar status={status.data} />
|
|
277
|
+
{vaultDialogIntent ? <VaultPassphraseModal intent={vaultDialogIntent} onClose={() => setVaultDialogIntent(null)} vault={status.data.vault} /> : null}
|
|
266
278
|
</main>
|
|
267
279
|
);
|
|
268
280
|
}
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
import type { ContrastPreference, ConversationMode, FontPreference, FontSizePreference, ShellMode, SidebarMode, SurfaceIconName, SurfaceId, SurfaceNavGroup, SurfaceNavItem, ThemePreference } from "./app-model";
|
|
38
38
|
import { dashboardNavItem, navGroups, sidebarModeForSurface, surfaceRecordCounts, text } from "./app-model";
|
|
39
39
|
import { SidebarFooter, wrappedOptionIndex } from "./AppearanceControls";
|
|
40
|
-
import { VaultPadlock, vaultCollectionForSurface, vaultCollectionTooltip } from "./VaultBadges";
|
|
40
|
+
import { type VaultDialogIntent, VaultPadlock, vaultCollectionForSurface, vaultCollectionTooltip, vaultDialogIntentForStatus } from "./VaultBadges";
|
|
41
41
|
|
|
42
42
|
function AidevopsPromptIcon() {
|
|
43
43
|
return (
|
|
@@ -109,13 +109,14 @@ export function MachineRail({ machine }: { machine?: GuiMachineSummary }) {
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
export function Sidebar({ activeSurface, accentHue, contrastPreference, conversationMode, fontPreference, fontSizePreference, selectedLocalRepoIndex, selectedSessionId, setAccentHue, setActiveSurface, setContrastPreference, setConversationMode, setFontPreference, setFontSizePreference, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, setShowBorders, setShowNavCounts, setThemePreference, shellMode, showBorders, showNavCounts, status, themePreference }: {
|
|
112
|
+
export function Sidebar({ activeSurface, accentHue, contrastPreference, conversationMode, fontPreference, fontSizePreference, onVaultRequest, selectedLocalRepoIndex, selectedSessionId, setAccentHue, setActiveSurface, setContrastPreference, setConversationMode, setFontPreference, setFontSizePreference, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, setShowBorders, setShowNavCounts, setThemePreference, shellMode, showBorders, showNavCounts, status, themePreference }: {
|
|
113
113
|
activeSurface: SurfaceId;
|
|
114
114
|
accentHue: number;
|
|
115
115
|
contrastPreference: ContrastPreference;
|
|
116
116
|
conversationMode: ConversationMode;
|
|
117
117
|
fontPreference: FontPreference;
|
|
118
118
|
fontSizePreference: FontSizePreference;
|
|
119
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
119
120
|
selectedLocalRepoIndex: number;
|
|
120
121
|
selectedSessionId: string | undefined;
|
|
121
122
|
setAccentHue: (hue: number) => void;
|
|
@@ -151,9 +152,9 @@ export function Sidebar({ activeSurface, accentHue, contrastPreference, conversa
|
|
|
151
152
|
{shellMode === "devices" ? <>
|
|
152
153
|
<SidebarModeTabs mode={sidebarMode} setMode={(mode) => setSidebarMode(mode as SidebarMode)} />
|
|
153
154
|
<ul className="sidebar-top-link">
|
|
154
|
-
<SidebarItem activeSurface={activeSurface} item={dashboardNavItem} setActiveSurface={setActiveSurface} showCount={false} status={status} />
|
|
155
|
+
<SidebarItem activeSurface={activeSurface} item={dashboardNavItem} onVaultRequest={onVaultRequest} setActiveSurface={setActiveSurface} showCount={false} status={status} />
|
|
155
156
|
</ul>
|
|
156
|
-
{visibleGroups.map((group) => <SidebarGroup activeSurface={activeSurface} group={group} key={group.label} recordCounts={recordCounts} setActiveSurface={setActiveSurface} showNavCounts={showNavCounts} status={status} />)}
|
|
157
|
+
{visibleGroups.map((group) => <SidebarGroup activeSurface={activeSurface} group={group} key={group.label} onVaultRequest={onVaultRequest} recordCounts={recordCounts} setActiveSurface={setActiveSurface} showNavCounts={showNavCounts} status={status} />)}
|
|
157
158
|
</> : <ConversationSidebar
|
|
158
159
|
conversationMode={conversationMode}
|
|
159
160
|
selectedLocalRepoIndex={selectedLocalRepoIndex}
|
|
@@ -310,9 +311,10 @@ function SidebarModeTabs<TMode extends SidebarMode | ConversationMode>({ mode, s
|
|
|
310
311
|
);
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
function SidebarGroup({ activeSurface, group, recordCounts, setActiveSurface, showNavCounts, status }: {
|
|
314
|
+
function SidebarGroup({ activeSurface, group, onVaultRequest, recordCounts, setActiveSurface, showNavCounts, status }: {
|
|
314
315
|
activeSurface: SurfaceId;
|
|
315
316
|
group: SurfaceNavGroup;
|
|
317
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
316
318
|
recordCounts: Partial<Record<SurfaceId, number>>;
|
|
317
319
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
318
320
|
showNavCounts: boolean;
|
|
@@ -322,15 +324,16 @@ function SidebarGroup({ activeSurface, group, recordCounts, setActiveSurface, sh
|
|
|
322
324
|
<section className="sidebar-group">
|
|
323
325
|
<h2>{group.label}</h2>
|
|
324
326
|
<ul>
|
|
325
|
-
{group.items.map((item) => <SidebarItem activeSurface={activeSurface} item={item} key={item.id} recordCount={recordCounts[item.id]} setActiveSurface={setActiveSurface} showCount={showNavCounts} status={status} />)}
|
|
327
|
+
{group.items.map((item) => <SidebarItem activeSurface={activeSurface} item={item} key={item.id} onVaultRequest={onVaultRequest} recordCount={recordCounts[item.id]} setActiveSurface={setActiveSurface} showCount={showNavCounts} status={status} />)}
|
|
326
328
|
</ul>
|
|
327
329
|
</section>
|
|
328
330
|
);
|
|
329
331
|
}
|
|
330
332
|
|
|
331
|
-
function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showCount, status }: {
|
|
333
|
+
function SidebarItem({ activeSurface, item, onVaultRequest, recordCount, setActiveSurface, showCount, status }: {
|
|
332
334
|
activeSurface: SurfaceId;
|
|
333
335
|
item: SurfaceNavItem;
|
|
336
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
334
337
|
recordCount?: number;
|
|
335
338
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
336
339
|
showCount: boolean;
|
|
@@ -348,7 +351,14 @@ function SidebarItem({ activeSurface, item, recordCount, setActiveSurface, showC
|
|
|
348
351
|
aria-label={tooltip}
|
|
349
352
|
aria-current={isActive ? "page" : undefined}
|
|
350
353
|
className={isActive ? "surface-link active" : "surface-link"}
|
|
351
|
-
onClick={() =>
|
|
354
|
+
onClick={(event) => {
|
|
355
|
+
if (vaultCollection && event.target instanceof HTMLElement && event.target.closest(".vault-padlock")) {
|
|
356
|
+
onVaultRequest(vaultDialogIntentForStatus(status.vault));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
setActiveSurface(item.id);
|
|
361
|
+
}}
|
|
352
362
|
title={tooltip}
|
|
353
363
|
type="button"
|
|
354
364
|
>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* jshint esversion: 11 */
|
|
2
2
|
import { type ReactElement, type ReactNode, useEffect, useState } from "react";
|
|
3
|
-
import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash, FiInfo, FiLogOut, FiMap, FiMessageSquare, FiPaperclip, FiSearch, FiSettings, FiShield, FiTerminal, FiTool, FiUser } from "react-icons/fi";
|
|
3
|
+
import { FiAlertTriangle, FiBell, FiCheckCircle, FiChevronLeft, FiChevronRight, FiCommand, FiFileText, FiGlobe, FiHash, FiInfo, FiLock, FiLogOut, FiMap, FiMessageSquare, FiPaperclip, FiSearch, FiSettings, FiShield, FiTerminal, FiTool, FiUnlock, FiUser } from "react-icons/fi";
|
|
4
4
|
import type { GuiFileRootId, GuiNotificationSummary, GuiStatusData } from "../../gui-shared/src";
|
|
5
5
|
import { SurfaceGlyph } from "./AppNavigation";
|
|
6
6
|
import type { ConversationMode, ShellMode, SurfaceId, SurfaceNavItem } from "./app-model";
|
|
@@ -12,7 +12,7 @@ import { TamboConversationPart } from "./GenUiCards";
|
|
|
12
12
|
import { AppsSurface, EditableInventorySurface, InstallationSurface } from "./InventorySurfaces";
|
|
13
13
|
import { PulseWorkersSurface } from "./PulseWorkersSurface";
|
|
14
14
|
import { AiProvidersSurface, LocalReposSurface, LockedVaultGate, OverviewSurface, PlannedSurface, ProjectsSurface, SecuritySurface, VaultSurface } from "./StatusSurfaces";
|
|
15
|
-
import { isVaultSurfaceLocked, vaultCollectionForSurface } from "./VaultBadges";
|
|
15
|
+
import { type VaultDialogIntent, isVaultSurfaceLocked, vaultCollectionForSurface, vaultDialogIntentForStatus } from "./VaultBadges";
|
|
16
16
|
import { applyCommandPaletteSelection, useHeaderMenuState } from "./workspace-header-state";
|
|
17
17
|
import { useWorkspaceTour, WorkspaceTourProvider } from "./WorkspaceTour";
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ const communityLinks = {
|
|
|
21
21
|
x: "https://x.com/marcuswquinn",
|
|
22
22
|
} as const;
|
|
23
23
|
|
|
24
|
-
export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, conversationMode, fileRoot, goBack, goForward, selectedLocalRepoIndex, selectedSessionId, setActiveSurface, setConversationMode, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, shellMode, status }: {
|
|
24
|
+
export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, conversationMode, fileRoot, goBack, goForward, onVaultRequest, selectedLocalRepoIndex, selectedSessionId, setActiveSurface, setConversationMode, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, shellMode, status }: {
|
|
25
25
|
activeItem: SurfaceNavItem;
|
|
26
26
|
activeSectionLabel: string;
|
|
27
27
|
activeSurface: SurfaceId;
|
|
@@ -31,6 +31,7 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
31
31
|
fileRoot: GuiFileRootId | undefined;
|
|
32
32
|
goBack: () => void;
|
|
33
33
|
goForward: () => void;
|
|
34
|
+
onVaultRequest?: (intent: VaultDialogIntent) => void;
|
|
34
35
|
selectedLocalRepoIndex: number;
|
|
35
36
|
selectedSessionId: string | undefined;
|
|
36
37
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
@@ -41,21 +42,23 @@ export function Workspace({ activeItem, activeSectionLabel, activeSurface, canGo
|
|
|
41
42
|
shellMode: ShellMode;
|
|
42
43
|
status: GuiStatusData;
|
|
43
44
|
}) {
|
|
45
|
+
const requestVault = onVaultRequest ?? (() => undefined);
|
|
46
|
+
|
|
44
47
|
return (
|
|
45
48
|
<section className="app-inset" aria-label={text.workspaceLabel}>
|
|
46
49
|
<WorkspaceTourProvider activeSurface={activeSurface}>
|
|
47
|
-
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setSelectedLocalRepoIndex={setSelectedLocalRepoIndex} setSelectedSessionId={setSelectedSessionId} setShellMode={setShellMode} status={status} />
|
|
50
|
+
<WorkspaceHeader activeItem={activeItem} activeSectionLabel={activeSectionLabel} activeSurface={activeSurface} canGoBack={canGoBack} canGoForward={canGoForward} goBack={goBack} goForward={goForward} onVaultRequest={requestVault} setActiveSurface={setActiveSurface} setConversationMode={setConversationMode} setSelectedLocalRepoIndex={setSelectedLocalRepoIndex} setSelectedSessionId={setSelectedSessionId} setShellMode={setShellMode} status={status} />
|
|
48
51
|
<div className="workspace-scroll">
|
|
49
52
|
{shellMode === "sessions"
|
|
50
53
|
? <ConversationWorkspace conversationMode={conversationMode} selectedLocalRepoIndex={selectedLocalRepoIndex} selectedSessionId={selectedSessionId} status={status} />
|
|
51
|
-
: <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} openSurface={setActiveSurface} status={status} />}
|
|
54
|
+
: <SurfaceContent activeItem={activeItem} activeSurface={activeSurface} fileRoot={fileRoot} onVaultRequest={requestVault} openSurface={setActiveSurface} status={status} />}
|
|
52
55
|
</div>
|
|
53
56
|
</WorkspaceTourProvider>
|
|
54
57
|
</section>
|
|
55
58
|
);
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, goBack, goForward, setActiveSurface, setConversationMode, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, status }: {
|
|
61
|
+
function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoBack, canGoForward, goBack, goForward, onVaultRequest, setActiveSurface, setConversationMode, setSelectedLocalRepoIndex, setSelectedSessionId, setShellMode, status }: {
|
|
59
62
|
activeItem: SurfaceNavItem;
|
|
60
63
|
activeSectionLabel: string;
|
|
61
64
|
activeSurface: SurfaceId;
|
|
@@ -63,6 +66,7 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
63
66
|
canGoForward: boolean;
|
|
64
67
|
goBack: () => void;
|
|
65
68
|
goForward: () => void;
|
|
69
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
66
70
|
setActiveSurface: (surface: SurfaceId) => void;
|
|
67
71
|
setConversationMode: (mode: ConversationMode) => void;
|
|
68
72
|
setSelectedLocalRepoIndex: (index: number) => void;
|
|
@@ -146,7 +150,7 @@ function WorkspaceHeader({ activeItem, activeSectionLabel, activeSurface, canGoB
|
|
|
146
150
|
<button aria-expanded={headerMenus.profileOpen} aria-label={`Open profile menu for ${userName}`} className="profile-avatar-button" onClick={headerMenus.toggleProfile} title={userName} type="button">
|
|
147
151
|
<span>{userInitials}</span>
|
|
148
152
|
</button>
|
|
149
|
-
{headerMenus.profileOpen ? <ProfileMenu openSurface={(surface) => openItem({ surface })} userName={userName} /> : null}
|
|
153
|
+
{headerMenus.profileOpen ? <ProfileMenu onVaultRequest={onVaultRequest} openSurface={(surface) => openItem({ surface })} status={status} userName={userName} /> : null}
|
|
150
154
|
</div>
|
|
151
155
|
</div>
|
|
152
156
|
{headerMenus.assistantOpen ? <AssistantPanel activeSurface={activeSurface} userName={userName} /> : null}
|
|
@@ -364,7 +368,9 @@ function ChatBubble({ body, speaker, title }: { body: string; speaker: "assistan
|
|
|
364
368
|
);
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
function ProfileMenu({ openSurface, userName }: { openSurface: (surface: SurfaceId) => void; userName: string }): ReactElement {
|
|
371
|
+
function ProfileMenu({ onVaultRequest, openSurface, status, userName }: { onVaultRequest: (intent: VaultDialogIntent) => void; openSurface: (surface: SurfaceId) => void; status: GuiStatusData; userName: string }): ReactElement {
|
|
372
|
+
const vaultIntent = vaultDialogIntentForStatus(status.vault);
|
|
373
|
+
const VaultIcon = vaultIntent === "lock" ? FiLock : FiUnlock;
|
|
368
374
|
return (
|
|
369
375
|
<div className="popover-menu profile-menu" role="menu">
|
|
370
376
|
<div className="profile-menu-heading">
|
|
@@ -374,6 +380,7 @@ function ProfileMenu({ openSurface, userName }: { openSurface: (surface: Surface
|
|
|
374
380
|
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiSettings aria-hidden="true" /> Settings</button>
|
|
375
381
|
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiCommand aria-hidden="true" /> Theme</button>
|
|
376
382
|
<button onClick={() => openSurface("settings")} role="menuitem" type="button"><FiGlobe aria-hidden="true" /> Language</button>
|
|
383
|
+
<button onClick={() => onVaultRequest(vaultIntent)} role="menuitem" type="button"><VaultIcon aria-hidden="true" /> {vaultIntent === "lock" ? "Lock Vault" : "Unlock Vault"}</button>
|
|
377
384
|
<div className="menu-separator" />
|
|
378
385
|
<strong>Community</strong>
|
|
379
386
|
<a href={communityLinks.github} rel="noreferrer" role="menuitem" target="_blank"><FiMessageSquare aria-hidden="true" /> GitHub</a>
|
|
@@ -397,10 +404,11 @@ function AssistantPanel({ activeSurface, userName }: { activeSurface: SurfaceId;
|
|
|
397
404
|
);
|
|
398
405
|
}
|
|
399
406
|
|
|
400
|
-
function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, status }: {
|
|
407
|
+
function SurfaceContent({ activeItem, activeSurface, fileRoot, onVaultRequest, openSurface, status }: {
|
|
401
408
|
activeItem: SurfaceNavItem;
|
|
402
409
|
activeSurface: SurfaceId;
|
|
403
410
|
fileRoot: GuiFileRootId | undefined;
|
|
411
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
404
412
|
openSurface: (surface: SurfaceId) => void;
|
|
405
413
|
status: GuiStatusData;
|
|
406
414
|
}) {
|
|
@@ -412,7 +420,7 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
|
|
|
412
420
|
settings: <SettingsSurface status={status} />,
|
|
413
421
|
notifications: <NotificationsSurface openSurface={openSurface} status={status} />,
|
|
414
422
|
admin: <AdminSurface />,
|
|
415
|
-
vault: <VaultSurface status={status} />,
|
|
423
|
+
vault: <VaultSurface onVaultRequest={onVaultRequest} status={status} />,
|
|
416
424
|
aiSessions: <AiSessionsSurface selectedRepoIndex={0} status={status} />,
|
|
417
425
|
channels: <CommsConversationSurface mode="channels" />,
|
|
418
426
|
directMessages: <CommsConversationSurface mode="directMessages" />,
|
|
@@ -450,7 +458,7 @@ function SurfaceContent({ activeItem, activeSurface, fileRoot, openSurface, stat
|
|
|
450
458
|
};
|
|
451
459
|
|
|
452
460
|
if (isVaultSurfaceLocked(status.vault, activeSurface) && vaultCollection) {
|
|
453
|
-
return <LockedVaultGate collection={vaultCollection} label={activeItem.label} vault={status.vault} />;
|
|
461
|
+
return <LockedVaultGate collection={vaultCollection} label={activeItem.label} onVaultRequest={onVaultRequest} vault={status.vault} />;
|
|
454
462
|
}
|
|
455
463
|
|
|
456
464
|
if (activeSurface === "git") {
|
|
@@ -4,7 +4,7 @@ import type { GuiAiAppSummary, GuiLocalRepoSetupSummary, GuiSetupTargetSummary,
|
|
|
4
4
|
import { plannedHomes, text } from "./app-model";
|
|
5
5
|
import { FileExplorerSurface } from "./FileExplorerSurface";
|
|
6
6
|
import { PathActions } from "./PathActions";
|
|
7
|
-
import { VaultPadlock } from "./VaultBadges";
|
|
7
|
+
import { type VaultDialogIntent, VaultPadlock, vaultDialogIntentForStatus } from "./VaultBadges";
|
|
8
8
|
|
|
9
9
|
export { AiProvidersSurface } from "./AiProvidersSurface";
|
|
10
10
|
|
|
@@ -121,7 +121,7 @@ export function SecuritySurface({ status }: { status: GuiStatusData }) {
|
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
124
|
+
export function VaultSurface({ onVaultRequest, status }: { onVaultRequest: (intent: VaultDialogIntent) => void; status: GuiStatusData }) {
|
|
125
125
|
const vault = status.vault;
|
|
126
126
|
const vaultCollection = vault.collections.find((collection) => collection.surface_ids.includes("vault")) ?? vault.collections[0];
|
|
127
127
|
const readiness = [
|
|
@@ -150,7 +150,7 @@ export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
|
150
150
|
<h2>{text.vault}</h2>
|
|
151
151
|
<p>{text.vaultIntro}</p>
|
|
152
152
|
</div>
|
|
153
|
-
{vaultCollection ? <VaultPadlock collection={vaultCollection} vault={vault} /> : null}
|
|
153
|
+
{vaultCollection ? <VaultPadlock collection={vaultCollection} onActivate={onVaultRequest} vault={vault} /> : null}
|
|
154
154
|
</div>
|
|
155
155
|
<ul aria-label="Vault readiness" className="vault-readiness-strip">
|
|
156
156
|
{readiness.map((item) => <li key={item.label}><strong>{item.label}</strong>{item.value}</li>)}
|
|
@@ -175,7 +175,7 @@ export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
|
175
175
|
<h2>{vault.readiness.setup_required ? "Setup required" : "Setup metadata"}</h2>
|
|
176
176
|
<p>{vault.setup_hint}</p>
|
|
177
177
|
</div>
|
|
178
|
-
<button className="secondary-action vault-cta"
|
|
178
|
+
<button className="secondary-action vault-cta" onClick={() => onVaultRequest(vaultDialogIntentForStatus(vault))} title={vault.unlock_hint} type="button">{vault.unlocked ? "Lock Vault" : text.vaultUnlockCta}</button>
|
|
179
179
|
</div>
|
|
180
180
|
<ol className="vault-step-list">
|
|
181
181
|
<li>Initialize locally with the hidden-prompt helper.</li>
|
|
@@ -191,7 +191,7 @@ export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
|
191
191
|
<p>{text.vaultCollectionIntro}</p>
|
|
192
192
|
</div>
|
|
193
193
|
<ul className="object-list vault-collection-list">
|
|
194
|
-
{vault.collections.map((collection) => <VaultCollectionRow collection={collection} key={collection.id} vault={vault} />)}
|
|
194
|
+
{vault.collections.map((collection) => <VaultCollectionRow collection={collection} key={collection.id} onVaultRequest={onVaultRequest} vault={vault} />)}
|
|
195
195
|
</ul>
|
|
196
196
|
</section>
|
|
197
197
|
<section className="panel" aria-label="Vault devices and audit">
|
|
@@ -219,9 +219,10 @@ export function VaultSurface({ status }: { status: GuiStatusData }) {
|
|
|
219
219
|
);
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
export function LockedVaultGate({ collection, label, vault }: {
|
|
222
|
+
export function LockedVaultGate({ collection, label, onVaultRequest, vault }: {
|
|
223
223
|
collection: GuiVaultCollectionSummary;
|
|
224
224
|
label: string;
|
|
225
|
+
onVaultRequest: (intent: VaultDialogIntent) => void;
|
|
225
226
|
vault: GuiVaultStatusData;
|
|
226
227
|
}) {
|
|
227
228
|
return (
|
|
@@ -232,12 +233,12 @@ export function LockedVaultGate({ collection, label, vault }: {
|
|
|
232
233
|
<h2>{label} is locked</h2>
|
|
233
234
|
<p>{text.vaultLockedPreview}</p>
|
|
234
235
|
</div>
|
|
235
|
-
<VaultPadlock collection={collection} vault={vault} />
|
|
236
|
+
<VaultPadlock collection={collection} onActivate={onVaultRequest} vault={vault} />
|
|
236
237
|
</div>
|
|
237
238
|
<div className="notice compact-notice" role="note">
|
|
238
239
|
{text.vaultTooltip} {vault.unlock_hint}
|
|
239
240
|
</div>
|
|
240
|
-
<button className="secondary-action vault-cta"
|
|
241
|
+
<button className="secondary-action vault-cta" onClick={() => onVaultRequest(vaultDialogIntentForStatus(vault))} title={vault.unlock_hint} type="button">{text.vaultUnlockCta}</button>
|
|
241
242
|
</section>
|
|
242
243
|
);
|
|
243
244
|
}
|
|
@@ -252,11 +253,11 @@ function VaultFeatureCard({ detail, label, value }: { detail: string; label: str
|
|
|
252
253
|
);
|
|
253
254
|
}
|
|
254
255
|
|
|
255
|
-
function VaultCollectionRow({ collection, vault }: { collection: GuiVaultCollectionSummary; vault: GuiVaultStatusData }) {
|
|
256
|
+
function VaultCollectionRow({ collection, onVaultRequest, vault }: { collection: GuiVaultCollectionSummary; onVaultRequest: (intent: VaultDialogIntent) => void; vault: GuiVaultStatusData }) {
|
|
256
257
|
return (
|
|
257
258
|
<li>
|
|
258
259
|
<strong>{collection.label}</strong>
|
|
259
|
-
<VaultPadlock collection={collection} compact vault={vault} />
|
|
260
|
+
<VaultPadlock collection={collection} compact onActivate={onVaultRequest} vault={vault} />
|
|
260
261
|
<span>{collection.preview_policy}</span>
|
|
261
262
|
<small>{collection.labels.join(", ")}</small>
|
|
262
263
|
<small>{collection.surface_ids.join(", ")}</small>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GuiVaultCollectionSummary, GuiVaultStatusData } from "@aidevops/gui-shared";
|
|
2
|
+
import type { KeyboardEvent, MouseEvent } from "react";
|
|
2
3
|
import { FiLock, FiUnlock } from "react-icons/fi";
|
|
3
4
|
import type { SurfaceId } from "./app-model";
|
|
4
5
|
import { text } from "./app-model";
|
|
@@ -23,20 +24,53 @@ export function vaultCollectionTooltip(collection: GuiVaultCollectionSummary): s
|
|
|
23
24
|
return text.vaultTooltip;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
export
|
|
27
|
+
export type VaultDialogIntent = "setup" | "unlock" | "lock";
|
|
28
|
+
|
|
29
|
+
export function vaultDialogIntentForStatus(vault: GuiVaultStatusData): VaultDialogIntent {
|
|
30
|
+
if (vault.unlocked) {
|
|
31
|
+
return "lock";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return vault.readiness.setup_required || !vault.initialized ? "setup" : "unlock";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function VaultPadlock({ collection, compact = false, onActivate, vault }: {
|
|
27
38
|
collection: GuiVaultCollectionSummary;
|
|
28
39
|
compact?: boolean;
|
|
40
|
+
onActivate?: (intent: VaultDialogIntent) => void;
|
|
29
41
|
vault: GuiVaultStatusData;
|
|
30
42
|
}) {
|
|
31
43
|
const locked = collection.state !== "unlocked" || !vault.unlocked;
|
|
32
44
|
const stateLabel = locked ? "Locked" : "Unlocked";
|
|
33
45
|
const Icon = locked ? FiLock : FiUnlock;
|
|
34
46
|
const tooltip = `${stateLabel}: ${vaultCollectionTooltip(collection)}`;
|
|
47
|
+
const className = compact ? "vault-padlock compact" : "vault-padlock";
|
|
48
|
+
|
|
49
|
+
const activate = (event: MouseEvent<HTMLSpanElement> | KeyboardEvent<HTMLSpanElement>) => {
|
|
50
|
+
if (onActivate === undefined) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
event.stopPropagation();
|
|
56
|
+
onActivate(vaultDialogIntentForStatus(vault));
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const handleKeyDown = (event: KeyboardEvent<HTMLSpanElement>) => {
|
|
60
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
61
|
+
activate(event);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
35
64
|
|
|
36
65
|
return (
|
|
37
66
|
<span
|
|
38
|
-
|
|
67
|
+
aria-label={tooltip}
|
|
68
|
+
className={onActivate ? `${className} interactive` : className}
|
|
39
69
|
data-vault-state={locked ? "locked" : "unlocked"}
|
|
70
|
+
onClick={activate}
|
|
71
|
+
onKeyDown={handleKeyDown}
|
|
72
|
+
role={onActivate ? "button" : undefined}
|
|
73
|
+
tabIndex={onActivate ? 0 : undefined}
|
|
40
74
|
title={tooltip}
|
|
41
75
|
>
|
|
42
76
|
<Icon aria-hidden="true" focusable="false" />
|
|
@@ -0,0 +1,132 @@
|
|
|
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 aria-label={title} aria-modal="true" className="vault-modal" role="dialog">
|
|
55
|
+
<header className="vault-modal-header">
|
|
56
|
+
<span className="vault-modal-icon" aria-hidden="true">{intent === "lock" ? <FiLock /> : <FiUnlock />}</span>
|
|
57
|
+
<div>
|
|
58
|
+
<p className="eyebrow">aidevops Vault</p>
|
|
59
|
+
<h2>{title}</h2>
|
|
60
|
+
</div>
|
|
61
|
+
</header>
|
|
62
|
+
{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} />}
|
|
63
|
+
<footer className="vault-modal-actions">
|
|
64
|
+
<button className="secondary-action" onClick={onClose} type="button">Cancel</button>
|
|
65
|
+
<button className="primary-action" disabled={!canContinue} onClick={continueFlow} type="button">{setupMode && step < 2 ? "Continue" : actionLabel}</button>
|
|
66
|
+
</footer>
|
|
67
|
+
<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>
|
|
68
|
+
</section>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function SetupStep({ accepted, confirm, match, onAcceptChange, onConfirmChange, onPassphraseChange, onUnlockPassphraseChange, passphrase, step, unlockMatch, unlockPassphrase }: {
|
|
74
|
+
accepted: boolean;
|
|
75
|
+
confirm: string;
|
|
76
|
+
match: boolean;
|
|
77
|
+
onAcceptChange: (accepted: boolean) => void;
|
|
78
|
+
onConfirmChange: (value: string) => void;
|
|
79
|
+
onPassphraseChange: (value: string) => void;
|
|
80
|
+
onUnlockPassphraseChange: (value: string) => void;
|
|
81
|
+
passphrase: string;
|
|
82
|
+
step: number;
|
|
83
|
+
unlockMatch: boolean;
|
|
84
|
+
unlockPassphrase: string;
|
|
85
|
+
}): ReactElement {
|
|
86
|
+
if (step === 1) {
|
|
87
|
+
return (
|
|
88
|
+
<div className="vault-modal-body">
|
|
89
|
+
<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>
|
|
90
|
+
<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>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (step === 2) {
|
|
96
|
+
return (
|
|
97
|
+
<div className="vault-modal-body">
|
|
98
|
+
<p>Final safety check: enter the newly saved passphrase once more before any confidential app data is encrypted.</p>
|
|
99
|
+
<label className="vault-field"><span>Use saved passphrase</span><input autoComplete="current-password" onChange={(event) => onUnlockPassphraseChange(event.currentTarget.value)} type="password" value={unlockPassphrase} /></label>
|
|
100
|
+
{unlockPassphrase.length > 0 ? <p className={unlockMatch ? "vault-valid" : "vault-invalid"}>{unlockMatch ? "Passphrase matches." : "Passphrase does not match the saved setup value."}</p> : null}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="vault-modal-body">
|
|
107
|
+
<p>Create the encryption passphrase for this local Vault. Use a long unique value generated by your password manager.</p>
|
|
108
|
+
<label className="vault-field"><span>New passphrase</span><input autoComplete="new-password" autoFocus onChange={(event) => onPassphraseChange(event.currentTarget.value)} type="password" value={passphrase} /></label>
|
|
109
|
+
<label className="vault-field"><span>Repeat passphrase</span><input autoComplete="new-password" onChange={(event) => onConfirmChange(event.currentTarget.value)} type="password" value={confirm} /></label>
|
|
110
|
+
{confirm.length > 0 ? <p className={match ? "vault-valid" : "vault-invalid"}>{match ? "Passphrases match." : "Passphrases must match before continuing."}</p> : null}
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function UnlockStep({ intent, onUnlockPassphraseChange, unlockPassphrase, vault }: {
|
|
116
|
+
intent: VaultDialogIntent;
|
|
117
|
+
onUnlockPassphraseChange: (value: string) => void;
|
|
118
|
+
unlockPassphrase: string;
|
|
119
|
+
vault: GuiVaultStatusData;
|
|
120
|
+
}): ReactElement {
|
|
121
|
+
if (intent === "lock") {
|
|
122
|
+
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>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="vault-modal-body">
|
|
127
|
+
<p><FiShield aria-hidden="true" /> Enter your Vault passphrase to unlock protected previews and write actions for this local session.</p>
|
|
128
|
+
<label className="vault-field"><span>Vault passphrase</span><input autoComplete="current-password" autoFocus onChange={(event) => onUnlockPassphraseChange(event.currentTarget.value)} type="password" value={unlockPassphrase} /></label>
|
|
129
|
+
<div className="notice compact-notice" role="note"><FiCheckCircle aria-hidden="true" /> {vault.unlock_hint}</div>
|
|
130
|
+
</div>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
@@ -953,6 +953,15 @@ code {
|
|
|
953
953
|
color: var(--accent);
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
+
.vault-padlock.interactive {
|
|
957
|
+
cursor: pointer;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.vault-padlock.interactive:focus-visible {
|
|
961
|
+
outline: 2px solid var(--accent);
|
|
962
|
+
outline-offset: 2px;
|
|
963
|
+
}
|
|
964
|
+
|
|
956
965
|
.sidebar-group h2 {
|
|
957
966
|
font-size: max(12.5px, 0.84em);
|
|
958
967
|
}
|
|
@@ -1605,6 +1614,130 @@ code {
|
|
|
1605
1614
|
width: 100%;
|
|
1606
1615
|
}
|
|
1607
1616
|
|
|
1617
|
+
.vault-modal-backdrop {
|
|
1618
|
+
align-items: center;
|
|
1619
|
+
backdrop-filter: blur(10px) saturate(130%);
|
|
1620
|
+
background: rgb(0 0 0 / 46%);
|
|
1621
|
+
display: flex;
|
|
1622
|
+
inset: 0;
|
|
1623
|
+
justify-content: center;
|
|
1624
|
+
padding: 24px;
|
|
1625
|
+
position: fixed;
|
|
1626
|
+
z-index: 30;
|
|
1627
|
+
-webkit-backdrop-filter: blur(10px) saturate(130%);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
.vault-modal {
|
|
1631
|
+
background: var(--surface-elevated);
|
|
1632
|
+
border: 1px solid var(--border-accent);
|
|
1633
|
+
border-radius: 24px;
|
|
1634
|
+
box-shadow: 0 30px 90px rgb(0 0 0 / 42%);
|
|
1635
|
+
display: grid;
|
|
1636
|
+
gap: 18px;
|
|
1637
|
+
max-width: min(620px, calc(100vw - 40px));
|
|
1638
|
+
padding: 22px;
|
|
1639
|
+
width: 100%;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
.vault-modal-header,
|
|
1643
|
+
.vault-modal-actions,
|
|
1644
|
+
.vault-modal-body p,
|
|
1645
|
+
.vault-field,
|
|
1646
|
+
.vault-confirm-check,
|
|
1647
|
+
.vault-valid,
|
|
1648
|
+
.vault-invalid {
|
|
1649
|
+
margin: 0;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
.vault-modal-header {
|
|
1653
|
+
align-items: center;
|
|
1654
|
+
display: flex;
|
|
1655
|
+
gap: 14px;
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
.vault-modal-icon {
|
|
1659
|
+
align-items: center;
|
|
1660
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
1661
|
+
border: 1px solid var(--border-accent);
|
|
1662
|
+
border-radius: 16px;
|
|
1663
|
+
color: var(--accent);
|
|
1664
|
+
display: inline-flex;
|
|
1665
|
+
height: 46px;
|
|
1666
|
+
justify-content: center;
|
|
1667
|
+
width: 46px;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
.vault-modal-body {
|
|
1671
|
+
display: grid;
|
|
1672
|
+
gap: 14px;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.vault-field {
|
|
1676
|
+
display: grid;
|
|
1677
|
+
gap: 7px;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
.vault-field span,
|
|
1681
|
+
.vault-confirm-check {
|
|
1682
|
+
color: var(--text-secondary);
|
|
1683
|
+
font-size: 0.9rem;
|
|
1684
|
+
font-weight: 800;
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
.vault-field input {
|
|
1688
|
+
background: var(--surface-muted);
|
|
1689
|
+
border: 1px solid var(--border);
|
|
1690
|
+
border-radius: 12px;
|
|
1691
|
+
color: var(--text-primary);
|
|
1692
|
+
padding: 12px;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
.vault-confirm-check {
|
|
1696
|
+
align-items: flex-start;
|
|
1697
|
+
display: flex;
|
|
1698
|
+
gap: 10px;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
.warning-notice {
|
|
1702
|
+
border-color: var(--border-warning);
|
|
1703
|
+
color: #b45309;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
.vault-valid {
|
|
1707
|
+
color: var(--accent);
|
|
1708
|
+
font-weight: 800;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
.vault-invalid {
|
|
1712
|
+
color: #b45309;
|
|
1713
|
+
font-weight: 800;
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
.vault-modal-actions {
|
|
1717
|
+
display: flex;
|
|
1718
|
+
gap: 10px;
|
|
1719
|
+
justify-content: flex-end;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
.primary-action {
|
|
1723
|
+
background: var(--accent);
|
|
1724
|
+
border: 1px solid var(--border-accent);
|
|
1725
|
+
border-radius: 10px;
|
|
1726
|
+
color: var(--surface-base);
|
|
1727
|
+
font-weight: 900;
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
.primary-action:disabled {
|
|
1731
|
+
cursor: not-allowed;
|
|
1732
|
+
opacity: 0.5;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
.vault-modal-footnote {
|
|
1736
|
+
color: var(--text-muted);
|
|
1737
|
+
font-size: 0.82rem;
|
|
1738
|
+
margin: 0;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1608
1741
|
.command-input-row {
|
|
1609
1742
|
align-items: center;
|
|
1610
1743
|
background: var(--surface-muted);
|
|
@@ -1659,6 +1792,8 @@ code {
|
|
|
1659
1792
|
}
|
|
1660
1793
|
|
|
1661
1794
|
.workspace-scroll {
|
|
1795
|
+
align-content: start;
|
|
1796
|
+
align-items: start;
|
|
1662
1797
|
display: grid;
|
|
1663
1798
|
gap: 16px;
|
|
1664
1799
|
overflow-x: hidden;
|
|
@@ -1667,6 +1802,7 @@ code {
|
|
|
1667
1802
|
}
|
|
1668
1803
|
|
|
1669
1804
|
.surface-page {
|
|
1805
|
+
align-content: start;
|
|
1670
1806
|
display: grid;
|
|
1671
1807
|
gap: 16px;
|
|
1672
1808
|
}
|
|
@@ -1973,17 +2109,21 @@ code {
|
|
|
1973
2109
|
|
|
1974
2110
|
.settings-surface,
|
|
1975
2111
|
.settings-form {
|
|
2112
|
+
align-content: start;
|
|
1976
2113
|
display: grid;
|
|
1977
2114
|
gap: 16px;
|
|
1978
2115
|
}
|
|
1979
2116
|
|
|
1980
2117
|
.settings-form {
|
|
2118
|
+
align-items: start;
|
|
1981
2119
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
1982
2120
|
}
|
|
1983
2121
|
|
|
1984
2122
|
.settings-form label {
|
|
2123
|
+
align-content: start;
|
|
1985
2124
|
display: grid;
|
|
1986
2125
|
gap: 8px;
|
|
2126
|
+
min-height: 0;
|
|
1987
2127
|
}
|
|
1988
2128
|
|
|
1989
2129
|
.settings-form span {
|
|
@@ -1997,7 +2137,9 @@ code {
|
|
|
1997
2137
|
border: 1px solid var(--border);
|
|
1998
2138
|
border-radius: 14px;
|
|
1999
2139
|
color: var(--text-secondary);
|
|
2140
|
+
min-height: 44px;
|
|
2000
2141
|
padding: 11px 12px;
|
|
2142
|
+
width: 100%;
|
|
2001
2143
|
}
|
|
2002
2144
|
|
|
2003
2145
|
.notification-list li {
|
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.
|
|
15
|
+
# Version: 3.32.0
|
|
16
16
|
#
|
|
17
17
|
# Quick Install:
|
|
18
18
|
# npm install -g aidevops && aidevops update (recommended)
|