From b6c77431e720455357ce2aed05cfb83df8fe852b Mon Sep 17 00:00:00 2001 From: lily-de <119957291+lily-de@users.noreply.github.com> Date: Sun, 26 Jan 2025 18:11:23 -0500 Subject: [PATCH] fix:dont allow builtin deletion (#797) --- .../src/components/settings/Settings.tsx | 40 +++-- .../ConfigureBuiltInExtensionModal.tsx | 162 ++++++++++++++++++ .../settings/extensions/ExtensionItem.tsx | 11 +- ui/desktop/src/extensions.tsx | 2 +- 4 files changed, 199 insertions(+), 16 deletions(-) create mode 100644 ui/desktop/src/components/settings/extensions/ConfigureBuiltInExtensionModal.tsx diff --git a/ui/desktop/src/components/settings/Settings.tsx b/ui/desktop/src/components/settings/Settings.tsx index 2b81baa8..8a26859e 100644 --- a/ui/desktop/src/components/settings/Settings.tsx +++ b/ui/desktop/src/components/settings/Settings.tsx @@ -11,6 +11,7 @@ import { } from '../../extensions'; import { ConfigureExtensionModal } from './extensions/ConfigureExtensionModal'; import { ManualExtensionModal } from './extensions/ManualExtensionModal'; +import { ConfigureBuiltInExtensionModal } from './extensions/ConfigureBuiltInExtensionModal'; import BackButton from '../ui/BackButton'; import { RecentModelsRadio } from './models/RecentModels'; import { ExtensionItem } from './extensions/ExtensionItem'; @@ -167,6 +168,10 @@ export default function Settings() { navigate('/settings', { replace: true }); }; + const isBuiltIn = (extensionId: string) => { + return BUILT_IN_EXTENSIONS.some((builtIn) => builtIn.id === extensionId); + }; + return (
@@ -232,6 +237,7 @@ export default function Settings() { setExtensionBeingConfigured(extension)} /> @@ -244,17 +250,29 @@ export default function Settings() {
- { - setExtensionBeingConfigured(null); - // Clear URL parameters when closing manually - navigate('/settings', { replace: true }); - }} - extension={extensionBeingConfigured} - onSubmit={handleExtensionConfigSubmit} - onRemove={handleExtensionRemove} - /> + {extensionBeingConfigured && isBuiltIn(extensionBeingConfigured.id) ? ( + { + setExtensionBeingConfigured(null); + navigate('/settings', { replace: true }); + }} + extension={extensionBeingConfigured} + onSubmit={handleExtensionConfigSubmit} + /> + ) : ( + { + setExtensionBeingConfigured(null); + // Clear URL parameters when closing manually + navigate('/settings', { replace: true }); + }} + extension={extensionBeingConfigured} + onSubmit={handleExtensionConfigSubmit} + onRemove={handleExtensionRemove} + /> + )} void; + onSubmit: () => void; + extension: FullExtensionConfig | null; +} + +export function ConfigureBuiltInExtensionModal({ + isOpen, + onClose, + onSubmit, + extension, +}: ConfigureExtensionModalProps) { + const [envValues, setEnvValues] = React.useState>({}); + const [isSubmitting, setIsSubmitting] = React.useState(false); + + // Reset form when dialog closes or extension changes + React.useEffect(() => { + if (!isOpen || !extension) { + setEnvValues({}); + } + }, [isOpen, extension]); + + const handleExtensionConfigSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!extension) return; + + setIsSubmitting(true); + try { + // First store all environment variables + if (extension.env_keys?.length > 0) { + for (const envKey of extension.env_keys) { + const value = envValues[envKey]; + if (!value) continue; + + const storeResponse = await fetch(getApiUrl('/secrets/store'), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Secret-Key': getSecretKey(), + }, + body: JSON.stringify({ + key: envKey, + value: value.trim(), + }), + }); + + if (!storeResponse.ok) { + throw new Error(`Failed to store environment variable: ${envKey}`); + } + } + } + + const response = await addExtension(extension); + + if (!response.ok) { + throw new Error('Failed to add system configuration'); + } + + toast.success(`Successfully configured the ${extension.name} extension`); + onSubmit(); + onClose(); + } catch (error) { + console.error('Error configuring extension:', error); + toast.error('Failed to configure extension'); + } finally { + setIsSubmitting(false); + } + }; + + if (!extension || !isOpen) return null; + + return ( +
+ +
+ {/* Header */} +
+

+ Configure {extension.name} +

+
+ + {/* Form */} +
+
+ {extension.env_keys?.length > 0 ? ( + <> +

+ Please provide the required environment variables for this extension: +

+
+ {extension.env_keys?.map((envVarName) => ( +
+ + + setEnvValues((prev) => ({ + ...prev, + [envVarName]: e.target.value, + })) + } + className="w-full h-14 px-4 font-regular rounded-lg border shadow-none border-gray-300 bg-white text-lg placeholder:text-gray-400 font-regular text-gray-900 dark:bg-gray-800 dark:border-gray-600 dark:text-white dark:placeholder:text-gray-500" + required + /> +
+ ))} +
+ + ) : ( +

+ This extension doesn't require any environment variables. +

+ )} +
+ + {/* Actions */} +
+ + +
+
+
+
+
+ ); +} diff --git a/ui/desktop/src/components/settings/extensions/ExtensionItem.tsx b/ui/desktop/src/components/settings/extensions/ExtensionItem.tsx index a9f06c7d..36c184d4 100644 --- a/ui/desktop/src/components/settings/extensions/ExtensionItem.tsx +++ b/ui/desktop/src/components/settings/extensions/ExtensionItem.tsx @@ -5,10 +5,11 @@ import { Gear } from '../../icons'; type ExtensionItemProps = FullExtensionConfig & { onToggle: (id: string) => void; onConfigure: (extension: FullExtensionConfig) => void; + canConfigure?: boolean; // Added optional prop here }; export const ExtensionItem: React.FC = (props) => { - const { id, name, description, enabled, onToggle, onConfigure } = props; + const { id, name, description, enabled, onToggle, onConfigure, canConfigure } = props; return (
@@ -20,9 +21,11 @@ export const ExtensionItem: React.FC = (props) => {

{description}

- + {canConfigure && ( // Conditionally render the gear icon + + )}