diff --git a/src/components/robot/RecordingsTable.tsx b/src/components/robot/RecordingsTable.tsx index 6297c7aa..a7594b08 100644 --- a/src/components/robot/RecordingsTable.tsx +++ b/src/components/robot/RecordingsTable.tsx @@ -76,7 +76,14 @@ interface RecordingsTableProps { handleDuplicateRobot: (id: string, name: string, params: string[]) => void; } -export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { +export const RecordingsTable = ({ + handleEditRecording, + handleRunRecording, + handleScheduleRecording, + handleIntegrateRecording, + handleSettingsRecording, + handleEditRobot, + handleDuplicateRobot }: RecordingsTableProps) => { const { t } = useTranslation(); const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); @@ -109,7 +116,20 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl }, ]; - const { notify, setRecordings, browserId, setBrowserId, setInitialUrl, recordingUrl, setRecordingUrl, isLogin, setIsLogin, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore(); + const { + notify, + setRecordings, + browserId, + setBrowserId, + setInitialUrl, + recordingUrl, + setRecordingUrl, + isLogin, + setIsLogin, + recordingName, + setRecordingName, + recordingId, + setRecordingId } = useGlobalInfoStore(); const navigate = useNavigate(); const handleChangePage = (event: unknown, newPage: number) => { diff --git a/src/components/robot/RobotEdit.tsx b/src/components/robot/RobotEdit.tsx index e96fc858..07cc5b38 100644 --- a/src/components/robot/RobotEdit.tsx +++ b/src/components/robot/RobotEdit.tsx @@ -8,7 +8,6 @@ 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; @@ -55,7 +54,6 @@ interface RobotSettingsProps { initialSettings?: RobotSettings | null; } -// Enhanced interfaces for credential handling interface CredentialInfo { value: string; type: string; @@ -81,44 +79,37 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const [robot, setRobot] = useState(null); const [credentials, setCredentials] = useState({}); const { recordingId, notify } = useGlobalInfoStore(); - const [credentialGroups, setCredentialGroups] = useState({ - passwords: [], + const [credentialGroups, setCredentialGroups] = useState({ + passwords: [], emails: [], usernames: [], - others: [] + others: [] }); const [showPasswords, setShowPasswords] = useState({}); 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'); + 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') { + if (info.type === 'password' || selector.toLowerCase().includes('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(); @@ -135,32 +126,37 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const extractInitialCredentials = (workflow: any[]): Credentials => { const credentials: Credentials = {}; - + // Helper function to check if a character is printable const isPrintableCharacter = (char: string): boolean => { return char.length === 1 && !!char.match(/^[\x20-\x7E]$/); }; - + // Process each step in the workflow workflow.forEach(step => { if (!step.what) return; - + // Keep track of the current input field being processed let currentSelector = ''; let currentValue = ''; let currentType = ''; - + // Process actions in sequence to maintain correct text state step.what.forEach((action: any) => { if ( - (action.action === 'type' || action.action === 'press') && - action.args?.length >= 2 && + (action.action === 'type' || action.action === 'press') && + action.args?.length >= 2 && typeof action.args[1] === 'string' ) { const selector: string = action.args[0]; const character: string = action.args[1]; const inputType: string = action.args[2] || ''; - + + // Detect `input[type="password"]` + if (!currentType && inputType.toLowerCase() === 'password') { + currentType = 'password'; + } + // If we're dealing with a new selector, store the previous one if (currentSelector && selector !== currentSelector) { if (!credentials[currentSelector]) { @@ -172,14 +168,14 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin credentials[currentSelector].value = currentValue; } } - + // Update current tracking variables if (selector !== currentSelector) { currentSelector = selector; currentValue = credentials[selector]?.value || ''; currentType = inputType || credentials[selector]?.type || ''; } - + // Handle different types of key actions if (character === 'Backspace') { // Remove the last character when backspace is pressed @@ -191,7 +187,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin // Note: We ignore other special keys like 'Shift', 'Enter', etc. } }); - + // Store the final state of the last processed selector if (currentSelector) { credentials[currentSelector] = { @@ -200,14 +196,14 @@ 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); @@ -221,7 +217,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin default: acc.others.push(selector); } - + return acc; }, { passwords: [], emails: [], usernames: [], others: [] }); }; @@ -281,32 +277,28 @@ 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 + credentialGroups.usernames, + t('Username'), + 'text' )} - - {/* Render email credentials */} + {renderCredentialFields( - credentialGroups.emails, - t('Email Credentials'), - 'text' // Always show emails as text + credentialGroups.emails, + t('Email'), + 'text' )} - - {/* Render password credentials */} + {renderCredentialFields( - credentialGroups.passwords, - t('Password Credentials'), - 'password' // Use password masking + credentialGroups.passwords, + t('Password'), + 'password' )} - - {/* Render other credentials */} + {renderCredentialFields( - credentialGroups.others, - t('Other Credentials'), - 'text' // Show other credentials as text + credentialGroups.others, + t('Other'), + 'text' )} ); @@ -314,33 +306,30 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const renderCredentialFields = (selectors: string[], headerText: string, defaultType: 'text' | 'password' = 'text') => { if (selectors.length === 0) return null; - + return ( <> - + {/* {headerText} - - {selectors.map((selector) => { + */} + {selectors.map((selector, index) => { const isVisible = showPasswords[selector]; - + return ( handleCredentialChange(selector, e.target.value)} style={{ marginBottom: '20px' }} InputProps={{ - // Now showing visibility toggle for all fields endAdornment: ( handleClickShowPassword(selector)} edge="end" - // Optional: disable if field is empty disabled={!credentials[selector]?.value} > {isVisible ? : } @@ -415,7 +404,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin onChange={(e) => handleRobotNameChange(e.target.value)} style={{ marginBottom: '20px' }} /> - + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( 0) && ( <> + + {t('Input Texts')} + {renderAllCredentialFields()} )}