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);
|
document.addEventListener('keydown', handleEscKey);
|
||||||
|
|
||||||
|
// Add overflow-hidden to body to prevent scrolling background
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleEscKey);
|
document.removeEventListener('keydown', handleEscKey);
|
||||||
|
// Restore body scrolling when modal closes
|
||||||
|
document.body.style.overflow = '';
|
||||||
};
|
};
|
||||||
}, [onClose]);
|
}, [onClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<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}
|
onClick={handleBackdropClick}
|
||||||
|
style={{ isolation: 'isolate' }} /* Creates a new stacking context */
|
||||||
>
|
>
|
||||||
<Card
|
<Card
|
||||||
ref={modalRef}
|
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>
|
<div className="p-8 max-h-[calc(90vh-180px)] overflow-y-auto">{children}</div>
|
||||||
{footer && (
|
{footer && (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import EnvVarsSection from './EnvVarsSection';
|
|||||||
import ExtensionConfigFields from './ExtensionConfigFields';
|
import ExtensionConfigFields from './ExtensionConfigFields';
|
||||||
import { PlusIcon, Edit, Trash2, AlertTriangle } from 'lucide-react';
|
import { PlusIcon, Edit, Trash2, AlertTriangle } from 'lucide-react';
|
||||||
import ExtensionInfoFields from './ExtensionInfoFields';
|
import ExtensionInfoFields from './ExtensionInfoFields';
|
||||||
|
import ExtensionTimeoutField from './ExtensionTimeoutField';
|
||||||
|
|
||||||
interface ExtensionModalProps {
|
interface ExtensionModalProps {
|
||||||
title: string;
|
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
|
// Form validation
|
||||||
const isFormValid = () => {
|
const isFormValid = () => {
|
||||||
return isNameValid() && isConfigValid() && isEnvVarsValid();
|
return isNameValid() && isConfigValid() && isEnvVarsValid() && isTimeoutValid();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle submit with validation
|
// Handle submit with validation
|
||||||
@@ -94,9 +109,20 @@ export default function ExtensionModal({
|
|||||||
setSubmitAttempted(true);
|
setSubmitAttempted(true);
|
||||||
|
|
||||||
if (isFormValid()) {
|
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
|
// Create footer buttons based on current state
|
||||||
@@ -186,7 +212,7 @@ export default function ExtensionModal({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<hr className="border-t border-borderSubtle mb-6" />
|
<hr className="border-t border-borderSubtle mb-4" />
|
||||||
|
|
||||||
{/* Command */}
|
{/* Command */}
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
@@ -198,10 +224,16 @@ export default function ExtensionModal({
|
|||||||
submitAttempted={submitAttempted}
|
submitAttempted={submitAttempted}
|
||||||
isValid={isConfigValid()}
|
isValid={isConfigValid()}
|
||||||
/>
|
/>
|
||||||
|
<div className="mb-4" />
|
||||||
|
<ExtensionTimeoutField
|
||||||
|
timeout={formData.timeout}
|
||||||
|
onChange={(key, value) => setFormData({ ...formData, [key]: value })}
|
||||||
|
submitAttempted={submitAttempted}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* Divider */}
|
||||||
<hr className="border-t border-borderSubtle mb-6" />
|
<hr className="border-t border-borderSubtle mb-4" />
|
||||||
|
|
||||||
{/* Environment Variables */}
|
{/* Environment Variables */}
|
||||||
<div className="mb-6">
|
<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
|
// Default extension timeout in seconds
|
||||||
// TODO: keep in sync with rust better
|
// TODO: keep in sync with rust better
|
||||||
|
import * as module from 'node:module';
|
||||||
|
|
||||||
export const DEFAULT_EXTENSION_TIMEOUT = 300;
|
export const DEFAULT_EXTENSION_TIMEOUT = 300;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,6 +26,7 @@ export interface ExtensionFormData {
|
|||||||
cmd?: string;
|
cmd?: string;
|
||||||
endpoint?: string;
|
endpoint?: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
|
timeout?: number;
|
||||||
envVars: { key: string; value: string }[];
|
envVars: { key: string; value: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +38,7 @@ export function getDefaultFormData(): ExtensionFormData {
|
|||||||
cmd: '',
|
cmd: '',
|
||||||
endpoint: '',
|
endpoint: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
timeout: 300,
|
||||||
envVars: [],
|
envVars: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -59,6 +63,7 @@ export function extensionToFormData(extension: FixedExtensionEntry): ExtensionFo
|
|||||||
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args) : undefined,
|
cmd: extension.type === 'stdio' ? combineCmdAndArgs(extension.cmd, extension.args) : undefined,
|
||||||
endpoint: extension.type === 'sse' ? extension.uri : undefined,
|
endpoint: extension.type === 'sse' ? extension.uri : undefined,
|
||||||
enabled: extension.enabled,
|
enabled: extension.enabled,
|
||||||
|
timeout: extension.timeout,
|
||||||
envVars,
|
envVars,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -84,6 +89,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
|||||||
description: formData.description,
|
description: formData.description,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
args: args,
|
args: args,
|
||||||
|
timeout: formData.timeout,
|
||||||
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
||||||
};
|
};
|
||||||
} else if (formData.type === 'sse') {
|
} else if (formData.type === 'sse') {
|
||||||
@@ -91,6 +97,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
|||||||
type: 'sse',
|
type: 'sse',
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
description: formData.description,
|
description: formData.description,
|
||||||
|
timeout: formData.timeout,
|
||||||
uri: formData.endpoint, // Assuming endpoint maps to uri for SSE type
|
uri: formData.endpoint, // Assuming endpoint maps to uri for SSE type
|
||||||
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
...(Object.keys(envs).length > 0 ? { envs } : {}),
|
||||||
};
|
};
|
||||||
@@ -99,6 +106,7 @@ export function createExtensionConfig(formData: ExtensionFormData): ExtensionCon
|
|||||||
return {
|
return {
|
||||||
type: formData.type,
|
type: formData.type,
|
||||||
name: formData.name,
|
name: formData.name,
|
||||||
|
timeout: formData.timeout,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user