mirror of
https://github.com/aljazceru/goose.git
synced 2026-02-23 07:24:24 +01:00
ui: add timeout field to settings v2 modal (#1955)
This commit is contained in:
@@ -46,23 +46,29 @@ export default function Modal({
|
||||
}
|
||||
};
|
||||
|
||||
// Add event listener
|
||||
// Add event listener for Escape key
|
||||
document.addEventListener('keydown', handleEscKey);
|
||||
|
||||
// Add overflow-hidden to body to prevent scrolling background
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
// Clean up
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleEscKey);
|
||||
// Restore body scrolling when modal closes
|
||||
document.body.style.overflow = '';
|
||||
};
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards] flex items-center justify-center p-4"
|
||||
className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards] flex items-center justify-center p-4 z-[9999]"
|
||||
onClick={handleBackdropClick}
|
||||
style={{ isolation: 'isolate' }} /* Creates a new stacking context */
|
||||
>
|
||||
<Card
|
||||
ref={modalRef}
|
||||
className="relative w-[500px] max-w-full bg-bgApp rounded-xl my-10 max-h-[90vh] flex flex-col"
|
||||
className="relative w-[500px] max-w-full bg-bgApp rounded-xl my-10 max-h-[90vh] flex flex-col shadow-xl z-[10000]"
|
||||
>
|
||||
<div className="p-8 max-h-[calc(90vh-180px)] overflow-y-auto">{children}</div>
|
||||
{footer && (
|
||||
|
||||
@@ -6,6 +6,7 @@ import EnvVarsSection from './EnvVarsSection';
|
||||
import ExtensionConfigFields from './ExtensionConfigFields';
|
||||
import { PlusIcon, Edit, Trash2, AlertTriangle } from 'lucide-react';
|
||||
import ExtensionInfoFields from './ExtensionInfoFields';
|
||||
import ExtensionTimeoutField from './ExtensionTimeoutField';
|
||||
|
||||
interface ExtensionModalProps {
|
||||
title: string;
|
||||
@@ -84,9 +85,23 @@ export default function ExtensionModal({
|
||||
);
|
||||
};
|
||||
|
||||
const isTimeoutValid = () => {
|
||||
// Check if timeout is not undefined, null, or empty string
|
||||
if (formData.timeout === undefined || formData.timeout === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to number if it's a string
|
||||
const timeoutValue =
|
||||
typeof formData.timeout === 'string' ? Number(formData.timeout) : formData.timeout;
|
||||
|
||||
// Check if it's a valid number (not NaN) and is a positive number
|
||||
return !isNaN(timeoutValue) && timeoutValue > 0;
|
||||
};
|
||||
|
||||
// Form validation
|
||||
const isFormValid = () => {
|
||||
return isNameValid() && isConfigValid() && isEnvVarsValid();
|
||||
return isNameValid() && isConfigValid() && isEnvVarsValid() && isTimeoutValid();
|
||||
};
|
||||
|
||||
// Handle submit with validation
|
||||
@@ -94,9 +109,20 @@ export default function ExtensionModal({
|
||||
setSubmitAttempted(true);
|
||||
|
||||
if (isFormValid()) {
|
||||
onSubmit(formData);
|
||||
const dataToSubmit = { ...formData };
|
||||
|
||||
// Convert the timeout to a number if it's a string
|
||||
if (typeof dataToSubmit.timeout === 'string') {
|
||||
dataToSubmit.timeout = Number(dataToSubmit.timeout);
|
||||
}
|
||||
|
||||
// Submit the data with converted timeout
|
||||
onSubmit(dataToSubmit);
|
||||
onClose(); // Only close the modal if the form is valid
|
||||
} else {
|
||||
// Optional: Add some feedback that validation failed (like a toast notification)
|
||||
console.log('Form validation failed');
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
|
||||
// Create footer buttons based on current state
|
||||
@@ -186,7 +212,7 @@ export default function ExtensionModal({
|
||||
/>
|
||||
|
||||
{/* Divider */}
|
||||
<hr className="border-t border-borderSubtle mb-6" />
|
||||
<hr className="border-t border-borderSubtle mb-4" />
|
||||
|
||||
{/* Command */}
|
||||
<div className="mb-6">
|
||||
@@ -198,10 +224,16 @@ export default function ExtensionModal({
|
||||
submitAttempted={submitAttempted}
|
||||
isValid={isConfigValid()}
|
||||
/>
|
||||
<div className="mb-4" />
|
||||
<ExtensionTimeoutField
|
||||
timeout={formData.timeout}
|
||||
onChange={(key, value) => setFormData({ ...formData, [key]: value })}
|
||||
submitAttempted={submitAttempted}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Divider */}
|
||||
<hr className="border-t border-borderSubtle mb-6" />
|
||||
<hr className="border-t border-borderSubtle mb-4" />
|
||||
|
||||
{/* Environment Variables */}
|
||||
<div className="mb-6">
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Input } from '../../../ui/input';
|
||||
import Select from 'react-select';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
interface ExtensionTimeoutFieldProps {
|
||||
timeout: number;
|
||||
onChange: (key: string, value: any) => void;
|
||||
submitAttempted: boolean;
|
||||
}
|
||||
|
||||
export default function ExtensionTimeoutField({
|
||||
timeout,
|
||||
onChange,
|
||||
submitAttempted,
|
||||
}: ExtensionTimeoutFieldProps) {
|
||||
const isTimeoutValid = () => {
|
||||
// Check if timeout is not undefined, null, or empty string
|
||||
if (timeout === undefined || timeout === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert to number if it's a string
|
||||
const timeoutValue = typeof timeout === 'string' ? Number(timeout) : timeout;
|
||||
|
||||
// Check if it's a valid number (not NaN) and is a positive number
|
||||
return !isNaN(timeoutValue) && timeoutValue > 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
{/* Row with Timeout and timeout input side by side */}
|
||||
<div className="flex justify-between gap-4">
|
||||
<div className="flex-1">
|
||||
<label className="text-sm font-medium mb-2 block text-textStandard">Timeout</label>
|
||||
</div>
|
||||
|
||||
{/* Type Dropdown */}
|
||||
<div className="w-[200px]">
|
||||
<div className="relative">
|
||||
<Input
|
||||
value={timeout}
|
||||
onChange={(e) => onChange('timeout', e.target.value)}
|
||||
defaultValue={300}
|
||||
className={`${!submitAttempted || isTimeoutValid() ? 'border-borderSubtle' : 'border-red-500'} text-textStandard focus:border-borderStandard`}
|
||||
/>
|
||||
{submitAttempted && !isTimeoutValid() && (
|
||||
<div className="absolute text-xs text-red-500 mt-1">Timeout </div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
// Default extension timeout in seconds
|
||||
// TODO: keep in sync with rust better
|
||||
import * as module from 'node:module';
|
||||
|
||||
export const DEFAULT_EXTENSION_TIMEOUT = 300;
|
||||
|
||||
/**
|
||||
@@ -24,6 +26,7 @@ export interface ExtensionFormData {
|
||||
cmd?: string;
|
||||
endpoint?: string;
|
||||
enabled: boolean;
|
||||
timeout?: number;
|
||||
envVars: { key: string; value: string }[];
|
||||
}
|
||||
|
||||
@@ -35,6 +38,7 @@ export function getDefaultFormData(): ExtensionFormData {
|
||||
cmd: '',
|
||||
endpoint: '',
|
||||
enabled: true,
|
||||
timeout: 300,
|
||||
envVars: [],
|
||||
};
|
||||
}
|
||||
@@ -59,6 +63,7 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
|
||||
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args) : undefined,
|
||||
endpoint: extension.type === 'sse' ? extension.uri : undefined,
|
||||
enabled: extension.enabled,
|
||||
timeout: extension.timeout,
|
||||
envVars,
|
||||
};
|
||||
}
|
||||
@@ -84,6 +89,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
||||
description: formData.description,
|
||||
cmd: cmd,
|
||||
args: args,
|
||||
timeout: formData.timeout,
|
||||
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
||||
};
|
||||
} else if (formData.type === 'sse') {
|
||||
@@ -91,6 +97,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
||||
type: 'sse',
|
||||
name: formData.name,
|
||||
description: formData.description,
|
||||
timeout: formData.timeout,
|
||||
uri: formData.endpoint, // Assuming endpoint maps to uri for SSE type
|
||||
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
||||
};
|
||||
@@ -99,6 +106,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
||||
return {
|
||||
type: formData.type,
|
||||
name: formData.name,
|
||||
timeout: formData.timeout,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user