From 8419875eb6fada679dcd3f03500c33d9c7948c0a Mon Sep 17 00:00:00 2001 From: Rohit Date: Thu, 23 Jan 2025 01:16:42 +0530 Subject: [PATCH] feat: add support for different inputs --- src/components/robot/RobotEdit.tsx | 364 ++++++++++++++++++----------- 1 file changed, 234 insertions(+), 130 deletions(-) diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index c9f38fa9..c4e75286 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -8,6 +8,7 @@ import { useGlobalInfoStore } from '../../context/globalInfo'; import { getStoredRecording, updateRecording } from '../../api/storage'; import { WhereWhatPair } from 'maxun-core'; +// Base interfaces for robot data structure interface RobotMeta { name: string; id: string; @@ -21,19 +22,6 @@ interface RobotWorkflow { workflow: WhereWhatPair[]; } -interface RobotEditOptions { - name: string; - limit?: number; -} - -interface Credentials { - [key: string]: string; -} - -interface CredentialVisibility { - [key: string]: boolean; -} - interface ScheduleConfig { runEvery: number; runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; @@ -67,21 +55,70 @@ interface RobotSettingsProps { initialSettings?: RobotSettings | null; } +// Enhanced interfaces for credential handling +interface CredentialInfo { + value: string; + type: string; +} + +interface Credentials { + [key: string]: CredentialInfo; +} + +interface CredentialVisibility { + [key: string]: boolean; +} + +interface GroupedCredentials { + passwords: string[]; + emails: string[]; + usernames: string[]; + others: string[]; +} + export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { const { t } = useTranslation(); const [robot, setRobot] = useState(null); const [credentials, setCredentials] = useState({}); const { recordingId, notify } = useGlobalInfoStore(); - const [credentialSelectors, setCredentialSelectors] = useState([]); + const [credentialGroups, setCredentialGroups] = useState({ + passwords: [], + emails: [], + usernames: [], + others: [] + }); const [showPasswords, setShowPasswords] = useState({}); - const handleClickShowPassword = (selector: string) => { - setShowPasswords(prev => ({ - ...prev, - [selector]: !prev[selector] - })); + const isEmailPattern = (value: string): boolean => { + return value.includes('@'); + }; + + const isUsernameSelector = (selector: string): boolean => { + return selector.toLowerCase().includes('username') || + selector.toLowerCase().includes('user') || + selector.toLowerCase().includes('email'); }; + const determineCredentialType = (selector: string, info: CredentialInfo): 'password' | 'email' | 'username' | 'other' => { + // Check for password type first + if (info.type === 'password') { + return 'password'; + } + + // Check for email patterns in the value or selector + if (isEmailPattern(info.value) || selector.toLowerCase().includes('email')) { + return 'email'; + } + + // Check for username patterns in the selector + if (isUsernameSelector(selector)) { + return 'username'; + } + + // If no specific pattern is matched, classify as other + return 'other'; + }; + useEffect(() => { if (isOpen) { getRobot(); @@ -90,35 +127,14 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin useEffect(() => { if (robot?.recording?.workflow) { - const selectors = findCredentialSelectors(robot.recording.workflow); - setCredentialSelectors(selectors); - - const initialCredentials = extractInitialCredentials(robot.recording.workflow); - setCredentials(initialCredentials); + const extractedCredentials = extractInitialCredentials(robot.recording.workflow); + setCredentials(extractedCredentials); + setCredentialGroups(groupCredentialsByType(extractedCredentials)); } }, [robot]); - const findCredentialSelectors = (workflow: WhereWhatPair[]): string[] => { - const selectors = new Set(); - - workflow?.forEach(step => { - step.what?.forEach(action => { - if ( - (action.action === 'type' || action.action === 'press') && - action.args && - action.args[0] && - typeof action.args[0] === 'string' - ) { - selectors.add(action.args[0]); - } - }); - }); - - return Array.from(selectors); - }; - - const extractInitialCredentials = (workflow: any[]): Record => { - const credentials: Record = {}; + const extractInitialCredentials = (workflow: any[]): Credentials => { + const credentials: Credentials = {}; const isPrintableCharacter = (char: string): boolean => { return char.length === 1 && !!char.match(/^[\x20-\x7E]$/); @@ -133,15 +149,19 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin action.args?.length >= 2 && typeof action.args[1] === 'string' ) { - let currentSelector: string = action.args[0]; - let character: string = action.args[1]; + const currentSelector: string = action.args[0]; + const character: string = action.args[1]; + const inputType: string = action.args[2] || ''; if (!credentials.hasOwnProperty(currentSelector)) { - credentials[currentSelector] = ''; + credentials[currentSelector] = { + value: '', + type: inputType + }; } if (isPrintableCharacter(character)) { - credentials[currentSelector] += character; + credentials[currentSelector].value += character; } } }); @@ -150,6 +170,28 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin return credentials; }; + const groupCredentialsByType = (credentials: Credentials): GroupedCredentials => { + return Object.entries(credentials).reduce((acc: GroupedCredentials, [selector, info]) => { + const credentialType = determineCredentialType(selector, info); + + switch (credentialType) { + case 'password': + acc.passwords.push(selector); + break; + case 'email': + acc.emails.push(selector); + break; + case 'username': + acc.usernames.push(selector); + break; + default: + acc.others.push(selector); + } + + return acc; + }, { passwords: [], emails: [], usernames: [], others: [] }); + }; + const getRobot = async () => { if (recordingId) { const robot = await getStoredRecording(recordingId); @@ -157,7 +199,14 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin } else { notify('error', t('robot_edit.notifications.update_failed')); } - } + }; + + const handleClickShowPassword = (selector: string) => { + setShowPasswords(prev => ({ + ...prev, + [selector]: !prev[selector] + })); + }; const handleRobotNameChange = (newName: string) => { setRobot((prev) => @@ -167,8 +216,11 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const handleCredentialChange = (selector: string, value: string) => { setCredentials(prev => ({ - ...prev, - [selector]: value + ...prev, + [selector]: { + ...prev[selector], + value + } })); }; @@ -177,7 +229,6 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin if (!prev) return prev; const updatedWorkflow = [...prev.recording.workflow]; - if ( updatedWorkflow.length > 0 && updatedWorkflow[0]?.what && @@ -193,21 +244,101 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin }); }; + const renderAllCredentialFields = () => { + return ( + <> + {/* Render username credentials */} + {renderCredentialFields( + credentialGroups.usernames, + t('Username Credentials'), + 'text' // Always show usernames as text + )} + + {/* Render email credentials */} + {renderCredentialFields( + credentialGroups.emails, + t('Email Credentials'), + 'text' // Always show emails as text + )} + + {/* Render password credentials */} + {renderCredentialFields( + credentialGroups.passwords, + t('Password Credentials'), + 'password' // Use password masking + )} + + {/* Render other credentials */} + {renderCredentialFields( + credentialGroups.others, + t('Other Credentials'), + 'text' // Show other credentials as text + )} + + ); + }; + + const renderCredentialFields = (selectors: string[], headerText: string, defaultType: 'text' | 'password' = 'text') => { + if (selectors.length === 0) return null; + + return ( + <> + + {headerText} + + {selectors.map((selector) => ( + handleCredentialChange(selector, e.target.value)} + style={{ marginBottom: '20px' }} + InputProps={{ + // Only show visibility toggle for password fields + endAdornment: defaultType === 'password' ? ( + + handleClickShowPassword(selector)} + edge="end" + > + {showPasswords[selector] ? : } + + + ) : undefined, + }} + /> + ))} + + ); + }; + const handleSave = async () => { if (!robot) return; try { + const credentialsForPayload = Object.entries(credentials).reduce((acc, [selector, info]) => { + const enforceType = info.type === 'password' ? 'password' : 'text'; + + acc[selector] = { + value: info.value, + type: enforceType + }; + return acc; + }, {} as Record); + const payload = { name: robot.recording_meta.name, limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit, - credentials: credentials, + credentials: credentialsForPayload, }; const success = await updateRecording(robot.recording_meta.id, payload); if (success) { notify('success', t('robot_edit.notifications.update_success')); - handleStart(robot); // Inform parent about the updated robot + handleStart(robot); handleClose(); setTimeout(() => { @@ -233,87 +364,60 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin {t('robot_edit.title')} - { - robot && ( - <> + {robot && ( + <> + handleRobotNameChange(e.target.value)} + style={{ marginBottom: '20px' }} + /> + + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( handleRobotNameChange(e.target.value)} + label={t('robot_edit.robot_limit')} + type="number" + value={robot.recording.workflow[0].what[0].args[0].limit || ''} + onChange={(e) => { + const value = parseInt(e.target.value, 10); + if (value >= 1) { + handleLimitChange(value); + } + }} + inputProps={{ min: 1 }} style={{ marginBottom: '20px' }} /> - {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( - { - const value = parseInt(e.target.value, 10); - if (value >= 1) { - handleLimitChange(value); - } - }} - inputProps={{ min: 1 }} - style={{ marginBottom: '20px' }} - /> - )} + )} - {(robot.isLogin || credentialSelectors.length > 0) && ( - <> - - {t('Login Credentials')} - - - {credentialSelectors.map((selector) => ( - handleCredentialChange(selector, e.target.value)} - style={{ marginBottom: '20px' }} - InputProps={{ - endAdornment: ( - - handleClickShowPassword(selector)} - edge="end" - > - {showPasswords[selector] ? : } - - - ), - }} - /> - ))} - - )} + {(robot.isLogin || Object.keys(credentials).length > 0) && ( + <> + {renderAllCredentialFields()} + + )} - - - - - - ) - } + + + + + + )} ); -}; +}; \ No newline at end of file