From d759bbf8919cfe6caa3bbb5e52445817bb545776 Mon Sep 17 00:00:00 2001 From: eshurakov <54751+eshurakov@users.noreply.github.com> Date: Tue, 14 Jul 2026 20:36:43 +0000 Subject: [PATCH 1/2] feat(cloud-agent-next): add command editing and reordering in profile dialog Implement functionality to edit existing commands and reorder them within the ProfileEditPanel. This includes adding state management for editing modes, handlers for saving updated commands, and handlers for moving commands up or down in the sequence. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../cloud-agent-next/ProfilesListDialog.tsx | 182 ++++++++++++++++-- 1 file changed, 168 insertions(+), 14 deletions(-) diff --git a/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx b/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx index a396f3afee..2985e64313 100644 --- a/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx +++ b/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx @@ -11,6 +11,7 @@ import { AlertCircle, ChevronDown, ChevronRight, + ChevronUp, Eye, EyeOff, Lock, @@ -536,6 +537,10 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) const [isAddingCommand, setIsAddingCommand] = useState(false); const [newCommand, setNewCommand] = useState(''); + // State for editing existing command + const [editingCommandIndex, setEditingCommandIndex] = useState(null); + const [editingCommandValue, setEditingCommandValue] = useState(''); + // Loading states const [savingVarKey, setSavingVarKey] = useState(null); const [deletingVarKey, setDeletingVarKey] = useState(null); @@ -711,6 +716,74 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) } }; + const resetEditCommandForm = () => { + setEditingCommandIndex(null); + setEditingCommandValue(''); + }; + + const handleStartEditCommand = (index: number) => { + const cmd = profile?.commands[index]; + if (!cmd) return; + setEditingCommandIndex(index); + setEditingCommandValue(cmd.command); + }; + + const handleSaveCommand = async (index: number) => { + if (!editingCommandValue.trim()) { + toast.error('Command is required'); + return; + } + + const currentCommands = profile?.commands.map(c => c.command) || []; + const updatedCommands = currentCommands.map((c, i) => + i === index ? editingCommandValue.trim() : c + ); + + setSavingCommands(true); + try { + await setCommands.mutateAsync({ + profileId, + commands: updatedCommands, + organizationId, + }); + toast.success('Command updated'); + resetEditCommandForm(); + } catch (err) { + console.error('Failed to update command:', err); + toast.error('Failed to update command'); + } finally { + setSavingCommands(false); + } + }; + + const handleMoveCommand = async (index: number, direction: 'up' | 'down') => { + const currentCommands = profile?.commands.map(c => c.command) || []; + const targetIndex = direction === 'up' ? index - 1 : index + 1; + if (targetIndex < 0 || targetIndex >= currentCommands.length) return; + + const updatedCommands = [...currentCommands]; + [updatedCommands[index], updatedCommands[targetIndex]] = [ + updatedCommands[targetIndex], + updatedCommands[index], + ]; + + setSavingCommands(true); + try { + await setCommands.mutateAsync({ + profileId, + commands: updatedCommands, + organizationId, + }); + } catch (err) { + console.error('Failed to reorder command:', err); + toast.error('Failed to reorder command'); + } finally { + setSavingCommands(false); + } + }; + + const commandActionsDisabled = savingCommands || editingCommandIndex !== null; + if (isLoading) { return (
@@ -1006,21 +1079,102 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) {profile?.commands.map((cmd, index) => (
- {index + 1}. - - {cmd.command} - - + {editingCommandIndex === index ? ( + // Edit mode +
+ setEditingCommandValue(e.target.value)} + placeholder="npm install" + className="font-mono" + autoFocus + onKeyDown={e => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSaveCommand(index); + } else if (e.key === 'Escape') { + resetEditCommandForm(); + } + }} + /> +
+ + +
+
+ ) : ( + // View mode +
+ + {index + 1}. + + + {cmd.command} + +
+ + + + +
+
+ )}
))} From 0a25f16c1c6f52d5f5d8b9122ec034f988f9eb3d Mon Sep 17 00:00:00 2001 From: eshurakov <54751+eshurakov@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:42:08 +0000 Subject: [PATCH 2/2] fix(cloud-agent-next): gate command edit keyboard actions during save Disable the edit-mode command input and early-return its keydown handler while a save is in flight, so Enter/Escape can't launch overlapping full-replacement setCommands mutations or cancel mid-save. Also void the async save call to satisfy no-floating-promises, and apply oxfmt. Co-authored-by: kiloconnect[bot] <240665456+kiloconnect[bot]@users.noreply.github.com> --- .../cloud-agent-next/ProfilesListDialog.tsx | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx b/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx index 2985e64313..b6cc431b78 100644 --- a/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx +++ b/apps/web/src/components/cloud-agent-next/ProfilesListDialog.tsx @@ -1090,10 +1090,12 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) placeholder="npm install" className="font-mono" autoFocus + disabled={savingCommands} onKeyDown={e => { + if (savingCommands) return; if (e.key === 'Enter') { e.preventDefault(); - handleSaveCommand(index); + void handleSaveCommand(index); } else if (e.key === 'Escape') { resetEditCommandForm(); } @@ -1113,20 +1115,14 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) onClick={() => handleSaveCommand(index)} disabled={savingCommands} > - {savingCommands ? ( - - ) : ( - 'Save' - )} + {savingCommands ? : 'Save'}
) : ( // View mode
- - {index + 1}. - + {index + 1}. {cmd.command} @@ -1147,8 +1143,7 @@ function ProfileEditPanel({ profileId, organizationId }: ProfileEditPanelProps) className="h-7 w-7" onClick={() => handleMoveCommand(index, 'down')} disabled={ - index === (profile?.commands.length ?? 0) - 1 || - commandActionsDisabled + index === (profile?.commands.length ?? 0) - 1 || commandActionsDisabled } aria-label="Move down" >