Merge pull request #363 from RohitR311/cred-chng
feat: ability to update credentials for robots with authentication
This commit is contained in:
@@ -11,7 +11,7 @@ import TableRow from '@mui/material/TableRow';
|
||||
import { useEffect } from "react";
|
||||
import { WorkflowFile } from "maxun-core";
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText, CircularProgress } from "@mui/material";
|
||||
import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText, CircularProgress, RadioGroup, FormControlLabel, Radio } from "@mui/material";
|
||||
import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, MoreHoriz } from "@mui/icons-material";
|
||||
import { useGlobalInfoStore } from "../../context/globalInfo";
|
||||
import { checkRunsForRecording, deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
|
||||
@@ -85,7 +85,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
||||
},
|
||||
];
|
||||
|
||||
const { notify, setRecordings, browserId, setBrowserId, setInitialUrl, recordingUrl, setRecordingUrl, 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) => {
|
||||
@@ -321,6 +321,19 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
||||
onChange={setBrowserRecordingUrl}
|
||||
style={{ marginBottom: '20px', marginTop: '20px' }}
|
||||
/>
|
||||
|
||||
<Typography variant="h6" gutterBottom>{t('recordingtable.modal.login_title')}</Typography>
|
||||
<RadioGroup
|
||||
aria-labelledby="login-requirement-radio-group"
|
||||
name="login-requirement"
|
||||
value={isLogin ? 'yes' : 'no'}
|
||||
onChange={(e) => setIsLogin(e.target.value === 'yes')}
|
||||
style={{ marginBottom: '20px' }}
|
||||
>
|
||||
<FormControlLabel value="yes" control={<Radio />} label="Yes" />
|
||||
<FormControlLabel value="no" control={<Radio />} label="No" />
|
||||
</RadioGroup>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GenericModal } from "../ui/GenericModal";
|
||||
import { TextField, Typography, Box, Button } from "@mui/material";
|
||||
import { TextField, Typography, Box, Button, IconButton, InputAdornment } from "@mui/material";
|
||||
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { modalStyle } from "../recorder/AddWhereCondModal";
|
||||
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
import { getStoredRecording, updateRecording } from '../../api/storage';
|
||||
@@ -25,6 +26,14 @@ interface RobotEditOptions {
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
interface Credentials {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
interface CredentialVisibility {
|
||||
[key: string]: boolean;
|
||||
}
|
||||
|
||||
interface ScheduleConfig {
|
||||
runEvery: number;
|
||||
runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS';
|
||||
@@ -48,6 +57,7 @@ export interface RobotSettings {
|
||||
google_access_token?: string | null;
|
||||
google_refresh_token?: string | null;
|
||||
schedule?: ScheduleConfig | null;
|
||||
isLogin?: boolean;
|
||||
}
|
||||
|
||||
interface RobotSettingsProps {
|
||||
@@ -60,7 +70,17 @@ interface RobotSettingsProps {
|
||||
export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [robot, setRobot] = useState<RobotSettings | null>(null);
|
||||
const [credentials, setCredentials] = useState<Credentials>({});
|
||||
const { recordingId, notify } = useGlobalInfoStore();
|
||||
const [credentialSelectors, setCredentialSelectors] = useState<string[]>([]);
|
||||
const [showPasswords, setShowPasswords] = useState<CredentialVisibility>({});
|
||||
|
||||
const handleClickShowPassword = (selector: string) => {
|
||||
setShowPasswords(prev => ({
|
||||
...prev,
|
||||
[selector]: !prev[selector]
|
||||
}));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
@@ -68,6 +88,68 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (robot?.recording?.workflow) {
|
||||
const selectors = findCredentialSelectors(robot.recording.workflow);
|
||||
setCredentialSelectors(selectors);
|
||||
|
||||
const initialCredentials = extractInitialCredentials(robot.recording.workflow);
|
||||
setCredentials(initialCredentials);
|
||||
}
|
||||
}, [robot]);
|
||||
|
||||
const findCredentialSelectors = (workflow: WhereWhatPair[]): string[] => {
|
||||
const selectors = new Set<string>();
|
||||
|
||||
workflow?.forEach(step => {
|
||||
step.what?.forEach(action => {
|
||||
if (
|
||||
(action.action === 'type') &&
|
||||
action.args &&
|
||||
action.args[0] &&
|
||||
typeof action.args[0] === 'string'
|
||||
) {
|
||||
selectors.add(action.args[0]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(selectors);
|
||||
};
|
||||
|
||||
const extractInitialCredentials = (workflow: any[]): Record<string, string> => {
|
||||
const credentials: Record<string, string> = {};
|
||||
|
||||
const isPrintableCharacter = (char: string): boolean => {
|
||||
return char.length === 1 && !!char.match(/^[\x20-\x7E]$/);
|
||||
};
|
||||
|
||||
workflow.forEach(step => {
|
||||
if (!step.what) return;
|
||||
|
||||
step.what.forEach((action: any) => {
|
||||
if (
|
||||
(action.action === 'type' || action.action === 'press') &&
|
||||
action.args?.length >= 2 &&
|
||||
typeof action.args[1] === 'string'
|
||||
) {
|
||||
let currentSelector: string = action.args[0];
|
||||
let character: string = action.args[1];
|
||||
|
||||
if (!credentials.hasOwnProperty(currentSelector)) {
|
||||
credentials[currentSelector] = '';
|
||||
}
|
||||
|
||||
if (isPrintableCharacter(character)) {
|
||||
credentials[currentSelector] += character;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return credentials;
|
||||
};
|
||||
|
||||
const getRobot = async () => {
|
||||
if (recordingId) {
|
||||
const robot = await getStoredRecording(recordingId);
|
||||
@@ -83,6 +165,13 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin
|
||||
);
|
||||
};
|
||||
|
||||
const handleCredentialChange = (selector: string, value: string) => {
|
||||
setCredentials(prev => ({
|
||||
...prev,
|
||||
[selector]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleLimitChange = (newLimit: number) => {
|
||||
setRobot((prev) => {
|
||||
if (!prev) return prev;
|
||||
@@ -111,6 +200,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin
|
||||
const payload = {
|
||||
name: robot.recording_meta.name,
|
||||
limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit,
|
||||
credentials: credentials,
|
||||
};
|
||||
|
||||
const success = await updateRecording(robot.recording_meta.id, payload);
|
||||
@@ -170,6 +260,38 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin
|
||||
/>
|
||||
)}
|
||||
|
||||
{(robot.isLogin || credentialSelectors.length > 0) && (
|
||||
<>
|
||||
<Typography variant="h6" style={{ marginBottom: '20px' }}>
|
||||
{t('Login Credentials')}
|
||||
</Typography>
|
||||
|
||||
{credentialSelectors.map((selector) => (
|
||||
<TextField
|
||||
key={selector}
|
||||
type={showPasswords[selector] ? 'text' : 'password'}
|
||||
label={`Credential for ${selector}`}
|
||||
value={credentials[selector] || ''}
|
||||
onChange={(e) => handleCredentialChange(selector, e.target.value)}
|
||||
style={{ marginBottom: '20px' }}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => handleClickShowPassword(selector)}
|
||||
edge="end"
|
||||
>
|
||||
{showPasswords[selector] ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
|
||||
<Box mt={2} display="flex" justifyContent="flex-end">
|
||||
<Button variant="contained" color="primary" onClick={handleSave}>
|
||||
{t('robot_edit.save')}
|
||||
|
||||
Reference in New Issue
Block a user