diff --git a/tools/server/public/index.html.gz b/tools/server/public/index.html.gz old mode 100644 new mode 100755 index 53b71079c1e2a..b2c2842788609 Binary files a/tools/server/public/index.html.gz and b/tools/server/public/index.html.gz differ diff --git a/tools/server/webui/src/components/SettingDialog.tsx b/tools/server/webui/src/components/SettingDialog.tsx old mode 100644 new mode 100755 index 45a8d73b00592..c3aca94dc589e --- a/tools/server/webui/src/components/SettingDialog.tsx +++ b/tools/server/webui/src/components/SettingDialog.tsx @@ -6,14 +6,17 @@ import StorageUtils from '../utils/storage'; import { classNames, isBoolean, isNumeric, isString } from '../utils/misc'; import { BeakerIcon, + BookmarkIcon, ChatBubbleOvalLeftEllipsisIcon, Cog6ToothIcon, FunnelIcon, HandRaisedIcon, SquaresPlusIcon, + TrashIcon, } from '@heroicons/react/24/outline'; import { OpenInNewTab } from '../utils/common'; import { useModals } from './ModalProvider'; +import { SettingsPreset } from '../utils/types'; type SettKey = keyof typeof CONFIG_DEFAULT; @@ -74,7 +77,155 @@ interface SettingSection { const ICON_CLASSNAME = 'w-4 h-4 mr-1 inline'; -const SETTING_SECTIONS: SettingSection[] = [ +// Presets Component +function PresetsManager({ + currentConfig, + onLoadPreset, +}: { + currentConfig: typeof CONFIG_DEFAULT; + onLoadPreset: (config: typeof CONFIG_DEFAULT) => void; +}) { + const [presets, setPresets] = useState(() => + StorageUtils.getPresets() + ); + const [presetName, setPresetName] = useState(''); + const [selectedPresetId, setSelectedPresetId] = useState(null); + const { showConfirm, showAlert } = useModals(); + + const handleSavePreset = async () => { + if (!presetName.trim()) { + await showAlert('Please enter a preset name'); + return; + } + + // Check if preset name already exists + const existingPreset = presets.find((p) => p.name === presetName.trim()); + if (existingPreset) { + if ( + await showConfirm( + `Preset "${presetName}" already exists. Do you want to overwrite it?` + ) + ) { + StorageUtils.updatePreset(existingPreset.id, currentConfig); + setPresets(StorageUtils.getPresets()); + setPresetName(''); + await showAlert('Preset updated successfully'); + } + } else { + const newPreset = StorageUtils.savePreset( + presetName.trim(), + currentConfig + ); + setPresets([...presets, newPreset]); + setPresetName(''); + await showAlert('Preset saved successfully'); + } + }; + + const handleLoadPreset = async (preset: SettingsPreset) => { + if ( + await showConfirm( + `Load preset "${preset.name}"? Current settings will be replaced.` + ) + ) { + onLoadPreset(preset.config as typeof CONFIG_DEFAULT); + setSelectedPresetId(preset.id); + } + }; + + const handleDeletePreset = async (preset: SettingsPreset) => { + if (await showConfirm(`Delete preset "${preset.name}"?`)) { + StorageUtils.deletePreset(preset.id); + setPresets(presets.filter((p) => p.id !== preset.id)); + if (selectedPresetId === preset.id) { + setSelectedPresetId(null); + } + } + }; + + return ( +
+ {/* Save current settings as preset */} +
+ +
+ setPresetName(e.target.value)} + onKeyPress={(e) => { + if (e.key === 'Enter') { + handleSavePreset(); + } + }} + /> + +
+
+ + {/* List of saved presets */} +
+ + {presets.length === 0 ? ( +
+ No presets saved yet +
+ ) : ( +
+ {presets.map((preset) => ( +
+
+
+

{preset.name}

+

+ Created: {new Date(preset.createdAt).toLocaleString()} +

+
+
+ + +
+
+
+ ))} +
+ )} +
+
+ ); +} + +const SETTING_SECTIONS = ( + localConfig: typeof CONFIG_DEFAULT, + setLocalConfig: (config: typeof CONFIG_DEFAULT) => void +): SettingSection[] => [ { title: ( <> @@ -267,6 +418,26 @@ const SETTING_SECTIONS: SettingSection[] = [ }, ], }, + { + title: ( + <> + + Presets + + ), + fields: [ + { + type: SettingInputType.CUSTOM, + key: 'custom', // dummy key for presets + component: () => ( + + ), + }, + ], + }, ]; export default function SettingDialog({ @@ -285,6 +456,12 @@ export default function SettingDialog({ ); const { showConfirm, showAlert } = useModals(); + // Generate sections with access to local state + const SETTING_SECTIONS_GENERATED = SETTING_SECTIONS( + localConfig, + setLocalConfig + ); + const resetConfig = async () => { if (await showConfirm('Are you sure you want to reset all settings?')) { setLocalConfig(CONFIG_DEFAULT); @@ -351,7 +528,7 @@ export default function SettingDialog({ aria-description="Settings sections" tabIndex={0} > - {SETTING_SECTIONS.map((section, idx) => ( + {SETTING_SECTIONS_GENERATED.map((section, idx) => (