From 794b0fb228f29bd3aeb037be17078d55a8d769ac Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 10 Dec 2025 21:15:55 -0800 Subject: [PATCH 1/6] remove secretstore view --- frontend/app/block/block.tsx | 2 - frontend/app/block/blockutil.tsx | 6 - .../app/view/secretstore/secretstore-model.ts | 251 ----------- frontend/app/view/secretstore/secretstore.tsx | 390 ------------------ 4 files changed, 649 deletions(-) delete mode 100644 frontend/app/view/secretstore/secretstore-model.ts delete mode 100644 frontend/app/view/secretstore/secretstore.tsx diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 7b307b9f33..ea2527bb2a 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -12,7 +12,6 @@ import { import { AiFileDiffViewModel } from "@/app/view/aifilediff/aifilediff"; import { LauncherViewModel } from "@/app/view/launcher/launcher"; import { PreviewModel } from "@/app/view/preview/preview-model"; -import { SecretStoreViewModel } from "@/app/view/secretstore/secretstore-model"; import { SysinfoViewModel } from "@/app/view/sysinfo/sysinfo"; import { TsunamiViewModel } from "@/app/view/tsunami/tsunami"; import { VDomModel } from "@/app/view/vdom/vdom-model"; @@ -54,7 +53,6 @@ BlockRegistry.set("help", HelpViewModel); BlockRegistry.set("launcher", LauncherViewModel); BlockRegistry.set("tsunami", TsunamiViewModel); BlockRegistry.set("aifilediff", AiFileDiffViewModel); -BlockRegistry.set("secretstore", SecretStoreViewModel); BlockRegistry.set("waveconfig", WaveConfigViewModel); function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel { diff --git a/frontend/app/block/blockutil.tsx b/frontend/app/block/blockutil.tsx index 3cfd2343c2..a31d9a799b 100644 --- a/frontend/app/block/blockutil.tsx +++ b/frontend/app/block/blockutil.tsx @@ -30,9 +30,6 @@ export function blockViewToIcon(view: string): string { if (view == "tips") { return "lightbulb"; } - if (view == "secretstore") { - return "key"; - } return "square"; } @@ -58,9 +55,6 @@ export function blockViewToName(view: string): string { if (view == "tips") { return "Tips"; } - if (view == "secretstore") { - return "Secret Store"; - } return view; } diff --git a/frontend/app/view/secretstore/secretstore-model.ts b/frontend/app/view/secretstore/secretstore-model.ts deleted file mode 100644 index 4e0e40e83a..0000000000 --- a/frontend/app/view/secretstore/secretstore-model.ts +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2025, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { BlockNodeModel } from "@/app/block/blocktypes"; -import { RpcApi } from "@/app/store/wshclientapi"; -import { TabRpcClient } from "@/app/store/wshrpcutil"; -import { WOS, globalStore } from "@/store/global"; -import * as jotai from "jotai"; -import { SecretStoreView } from "./secretstore"; - -const SECRET_NAME_REGEX = /^[A-Za-z][A-Za-z0-9_]*$/; - -export class SecretStoreViewModel implements ViewModel { - viewType: string; - blockId: string; - blockAtom: jotai.Atom; - nodeModel: BlockNodeModel; - - viewIcon = jotai.atom("key"); - viewName = jotai.atom("Secret Store"); - - secretNames: jotai.PrimitiveAtom; - selectedSecret: jotai.PrimitiveAtom; - secretValue: jotai.PrimitiveAtom; - isLoading: jotai.PrimitiveAtom; - errorMessage: jotai.PrimitiveAtom; - storageBackendError: jotai.PrimitiveAtom; - isEditing: jotai.PrimitiveAtom; - secretShown: jotai.PrimitiveAtom; - isAddingNew: jotai.PrimitiveAtom; - newSecretName: jotai.PrimitiveAtom; - newSecretValue: jotai.PrimitiveAtom; - - secretValueRef: HTMLTextAreaElement | null = null; - - endIconButtons!: jotai.Atom; - - constructor(blockId: string, nodeModel: BlockNodeModel) { - this.viewType = "secretstore"; - this.blockId = blockId; - this.nodeModel = nodeModel; - this.blockAtom = WOS.getWaveObjectAtom(`block:${blockId}`); - - this.secretNames = jotai.atom([]); - this.selectedSecret = jotai.atom(null) as jotai.PrimitiveAtom; - this.secretValue = jotai.atom(""); - this.isLoading = jotai.atom(false); - this.errorMessage = jotai.atom(null) as jotai.PrimitiveAtom; - this.storageBackendError = jotai.atom(null) as jotai.PrimitiveAtom; - this.isEditing = jotai.atom(false); - this.secretShown = jotai.atom(false); - this.isAddingNew = jotai.atom(false); - this.newSecretName = jotai.atom(""); - this.newSecretValue = jotai.atom(""); - - this.endIconButtons = jotai.atom((get) => { - const buttons: IconButtonDecl[] = []; - - buttons.push({ - elemtype: "iconbutton", - icon: "rotate-right", - title: "Refresh", - click: () => this.refreshSecrets(), - }); - - return buttons; - }); - - this.checkStorageBackend(); - this.refreshSecrets(); - } - - get viewComponent() { - return SecretStoreView; - } - - async checkStorageBackend() { - try { - const backend = await RpcApi.GetSecretsLinuxStorageBackendCommand(TabRpcClient); - if (backend === "basic_text" || backend === "unknown") { - globalStore.set( - this.storageBackendError, - "No appropriate secret manager found. Cannot manage secrets securely." - ); - } else { - globalStore.set(this.storageBackendError, null); - } - } catch (error) { - globalStore.set(this.storageBackendError, `Error checking storage backend: ${error.message}`); - } - } - - async refreshSecrets() { - globalStore.set(this.isLoading, true); - globalStore.set(this.errorMessage, null); - - try { - const names = await RpcApi.GetSecretsNamesCommand(TabRpcClient); - globalStore.set(this.secretNames, names || []); - } catch (error) { - globalStore.set(this.errorMessage, `Failed to load secrets: ${error.message}`); - } finally { - globalStore.set(this.isLoading, false); - } - } - - async viewSecret(name: string) { - globalStore.set(this.errorMessage, null); - globalStore.set(this.selectedSecret, name); - globalStore.set(this.isEditing, true); - globalStore.set(this.secretShown, false); - globalStore.set(this.secretValue, ""); - } - - closeSecretView() { - globalStore.set(this.selectedSecret, null); - globalStore.set(this.secretValue, ""); - globalStore.set(this.isEditing, false); - globalStore.set(this.errorMessage, null); - } - - async showSecret() { - const selectedSecret = globalStore.get(this.selectedSecret); - if (!selectedSecret) { - return; - } - - globalStore.set(this.isLoading, true); - globalStore.set(this.errorMessage, null); - - try { - const secrets = await RpcApi.GetSecretsCommand(TabRpcClient, [selectedSecret]); - const value = secrets[selectedSecret]; - if (value !== undefined) { - globalStore.set(this.secretValue, value); - globalStore.set(this.secretShown, true); - } else { - globalStore.set(this.errorMessage, `Secret not found: ${selectedSecret}`); - } - } catch (error) { - globalStore.set(this.errorMessage, `Failed to load secret: ${error.message}`); - } finally { - globalStore.set(this.isLoading, false); - } - } - - async saveSecret() { - const selectedSecret = globalStore.get(this.selectedSecret); - const secretValue = globalStore.get(this.secretValue); - - if (!selectedSecret) { - return; - } - - globalStore.set(this.isLoading, true); - globalStore.set(this.errorMessage, null); - - try { - await RpcApi.SetSecretsCommand(TabRpcClient, { [selectedSecret]: secretValue }); - this.closeSecretView(); - } catch (error) { - globalStore.set(this.errorMessage, `Failed to save secret: ${error.message}`); - } finally { - globalStore.set(this.isLoading, false); - } - } - - async deleteSecret() { - const selectedSecret = globalStore.get(this.selectedSecret); - - if (!selectedSecret) { - return; - } - - globalStore.set(this.isLoading, true); - globalStore.set(this.errorMessage, null); - - try { - await RpcApi.SetSecretsCommand(TabRpcClient, { [selectedSecret]: null }); - this.closeSecretView(); - await this.refreshSecrets(); - } catch (error) { - globalStore.set(this.errorMessage, `Failed to delete secret: ${error.message}`); - } finally { - globalStore.set(this.isLoading, false); - } - } - - startAddingSecret() { - globalStore.set(this.isAddingNew, true); - globalStore.set(this.newSecretName, ""); - globalStore.set(this.newSecretValue, ""); - globalStore.set(this.errorMessage, null); - } - - cancelAddingSecret() { - globalStore.set(this.isAddingNew, false); - globalStore.set(this.newSecretName, ""); - globalStore.set(this.newSecretValue, ""); - globalStore.set(this.errorMessage, null); - } - - async addNewSecret() { - const name = globalStore.get(this.newSecretName).trim(); - const value = globalStore.get(this.newSecretValue); - - if (!name) { - globalStore.set(this.errorMessage, "Secret name cannot be empty"); - return; - } - - if (!SECRET_NAME_REGEX.test(name)) { - globalStore.set( - this.errorMessage, - "Invalid secret name: must start with a letter and contain only letters, numbers, and underscores" - ); - return; - } - - const existingNames = globalStore.get(this.secretNames); - if (existingNames.includes(name)) { - globalStore.set(this.errorMessage, `Secret "${name}" already exists`); - return; - } - - globalStore.set(this.isLoading, true); - globalStore.set(this.errorMessage, null); - - try { - await RpcApi.SetSecretsCommand(TabRpcClient, { [name]: value }); - globalStore.set(this.isAddingNew, false); - globalStore.set(this.newSecretName, ""); - globalStore.set(this.newSecretValue, ""); - await this.refreshSecrets(); - } catch (error) { - globalStore.set(this.errorMessage, `Failed to add secret: ${error.message}`); - } finally { - globalStore.set(this.isLoading, false); - } - } - - giveFocus(): boolean { - if (this.secretValueRef) { - this.secretValueRef.focus(); - return true; - } - return true; - } - - dispose() {} -} diff --git a/frontend/app/view/secretstore/secretstore.tsx b/frontend/app/view/secretstore/secretstore.tsx deleted file mode 100644 index 4368f33d37..0000000000 --- a/frontend/app/view/secretstore/secretstore.tsx +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2025, Command Line Inc. -// SPDX-License-Identifier: Apache-2.0 - -import { cn } from "@/util/util"; -import { useAtomValue, useSetAtom } from "jotai"; -import { memo } from "react"; -import { SecretStoreViewModel } from "./secretstore-model"; - -interface ErrorDisplayProps { - message: string; - variant?: "error" | "warning"; -} - -const ErrorDisplay = memo(({ message, variant = "error" }: ErrorDisplayProps) => { - const icon = variant === "error" ? "fa-circle-exclamation" : "fa-triangle-exclamation"; - const baseClasses = "flex items-center gap-2 p-4 border rounded-lg"; - const variantClasses = - variant === "error" - ? "bg-red-500/10 border-red-500/20 text-red-400" - : "bg-yellow-500/10 border-yellow-500/20 text-yellow-400"; - - return ( -
- - {message} -
- ); -}); -ErrorDisplay.displayName = "ErrorDisplay"; - -const LoadingSpinner = memo(({ message }: { message: string }) => { - return ( -
- - {message} -
- ); -}); -LoadingSpinner.displayName = "LoadingSpinner"; - -const EmptyState = memo(({ onAddSecret }: { onAddSecret: () => void }) => { - return ( -
- -

No Secrets

-

Add a secret to get started

- -
- ); -}); -EmptyState.displayName = "EmptyState"; - -const CLIInfoBubble = memo(() => { - return ( -
-
- -
CLI Access
-
-
- wsh secret list -
- wsh secret get [name] -
- wsh secret set [name]=[value] -
-
- ); -}); -CLIInfoBubble.displayName = "CLIInfoBubble"; - -interface SecretListViewProps { - secretNames: string[]; - onSelectSecret: (name: string) => void; - onAddSecret: () => void; -} - -const SecretListView = memo(({ secretNames, onSelectSecret, onAddSecret }: SecretListViewProps) => { - return ( -
-
-

Secrets

- {secretNames.length} -
-
- {secretNames.map((name) => ( -
onSelectSecret(name)} - > - - {name} - -
- ))} -
- - Add New Secret -
-
- -
- ); -}); -SecretListView.displayName = "SecretListView"; - -interface AddSecretFormProps { - newSecretName: string; - newSecretValue: string; - isLoading: boolean; - onNameChange: (name: string) => void; - onValueChange: (value: string) => void; - onCancel: () => void; - onSubmit: () => void; -} - -const AddSecretForm = memo( - ({ - newSecretName, - newSecretValue, - isLoading, - onNameChange, - onValueChange, - onCancel, - onSubmit, - }: AddSecretFormProps) => { - const secretNameRegex = /^[A-Za-z][A-Za-z0-9_]*$/; - const isNameInvalid = newSecretName !== "" && !secretNameRegex.test(newSecretName); - - return ( -
-

Add New Secret

-
- - onNameChange(e.target.value)} - placeholder="MY_SECRET_NAME" - disabled={isLoading} - /> -
- Must start with a letter and contain only letters, numbers, and underscores -
-
-
- -