Merge pull request #68 from amhsirak/develop

feat: new ui
This commit is contained in:
Karishma Shukla
2024-10-12 15:07:00 +05:30
committed by GitHub
21 changed files with 350 additions and 245 deletions

View File

@@ -106,7 +106,7 @@ router.get("/robots/:id", requireAPIKey, async (req: Request, res: Response) =>
}
});
// TODO: Format runs to send more data formatted
// TODO: Format runs to send more data formatted
router.get("/robots/:id/runs", requireAPIKey, async (req: Request, res: Response) => {
try {
const runs = await Run.findAll({

View File

@@ -275,7 +275,7 @@ export class RemoteBrowser {
if (page) {
await this.stopScreencast();
this.currentPage = page;
await this.currentPage.setViewportSize({ height: 720, width: 1280 })
await this.currentPage.setViewportSize({ height: 500, width: 1280 })
this.client = await this.currentPage.context().newCDPSession(this.currentPage);
this.socket.emit('urlChanged', this.currentPage.url());
await this.makeAndEmitScreenshot();

View File

@@ -33,13 +33,13 @@ const defaultModalStyle = {
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: '50%',
width: 500,
bgcolor: 'background.paper',
boxShadow: 24,
p: 4,
height: '30%',
display: 'block',
overflow: 'hidden',
overflow: 'scroll',
padding: '5px 25px 10px 25px',
zIndex: 3147483647,
};
};

View File

@@ -12,6 +12,7 @@ import { UrlForm } from './UrlForm';
import { useCallback, useEffect, useState } from "react";
import { useSocketStore } from "../../context/socket";
import { getCurrentUrl } from "../../api/recording";
import { useGlobalInfoStore } from '../../context/globalInfo';
const StyledNavBar = styled.div<{ browserWidth: number }>`
display: flex;
@@ -31,8 +32,7 @@ const BrowserNavBar: FC<NavBarProps> = ({
}) => {
const { socket } = useSocketStore();
const [currentUrl, setCurrentUrl] = useState<string>('https://');
const { recordingUrl, setRecordingUrl } = useGlobalInfoStore();
const handleRefresh = useCallback((): void => {
socket?.emit('input:refresh');
@@ -44,14 +44,13 @@ const BrowserNavBar: FC<NavBarProps> = ({
const handleCurrentUrlChange = useCallback((url: string) => {
handleUrlChanged(url);
setCurrentUrl(url);
}, [handleUrlChanged, currentUrl]);
setRecordingUrl(url);
}, [handleUrlChanged, recordingUrl]);
useEffect(() => {
getCurrentUrl().then((response) => {
if (response) {
handleUrlChanged(response);
setCurrentUrl(response);
}
}).catch((error) => {
console.log("Fetching current url failed");
@@ -72,12 +71,13 @@ const BrowserNavBar: FC<NavBarProps> = ({
const addAddress = (address: string) => {
if (socket) {
handleUrlChanged(address);
setRecordingUrl(address);
handleGoTo(address);
}
};
return (
<StyledNavBar browserWidth={browserWidth}>
<StyledNavBar browserWidth={900}>
<NavBarButton
type="button"
onClick={() => {
@@ -111,7 +111,7 @@ const BrowserNavBar: FC<NavBarProps> = ({
</NavBarButton>
<UrlForm
currentAddress={currentUrl}
currentAddress={recordingUrl}
handleRefresh={handleRefresh}
setCurrentAddress={addAddress}
/>

View File

@@ -31,7 +31,7 @@ export const BrowserTabs = (
return (
<Box sx={{
width: `${width}px`,
width: 800,
display: 'flex',
overflow: 'auto',
alignItems: 'center',

View File

@@ -208,4 +208,4 @@ export const InterpretationLog: React.FC<InterpretationLogProps> = ({ isOpen, se
</SwipeableDrawer>
</div>
);
}
}

View File

@@ -6,10 +6,12 @@ import { useGlobalInfoStore } from "../../context/globalInfo";
import { Button, IconButton } from "@mui/material";
import { RecordingIcon } from "../atoms/RecorderIcon";
import { SaveRecording } from "./SaveRecording";
import { Circle } from "@mui/icons-material";
import { Circle, Add, Logout, Clear } from "@mui/icons-material";
import MeetingRoomIcon from '@mui/icons-material/MeetingRoom';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { AuthContext } from '../../context/auth';
import { GenericModal } from '../atoms/GenericModal';
import TextField from '@mui/material/TextField';
interface NavBarProps {
newRecording: () => void;
@@ -17,11 +19,15 @@ interface NavBarProps {
isRecording: boolean;
}
export const NavBar = ({ newRecording, recordingName, isRecording }: NavBarProps) => {
export const NavBar: React.FC<NavBarProps> = ({ newRecording, recordingName, isRecording }) => {
const { notify, browserId, setBrowserId, recordingLength } = useGlobalInfoStore();
const { notify, browserId, setBrowserId, recordingLength, recordingUrl, setRecordingUrl } = useGlobalInfoStore();
const { state, dispatch } = useContext(AuthContext);
const { user } = state;
const [isModalOpen, setModalOpen] = useState(false);
console.log(`Recording URL: ${recordingUrl}`)
const navigate = useNavigate();
const logout = async () => {
@@ -48,9 +54,14 @@ export const NavBar = ({ newRecording, recordingName, isRecording }: NavBarProps
setBrowserId(null);
await stopRecording(browserId);
}
setModalOpen(true);
};
const startRecording = () => {
setModalOpen(false);
newRecording();
notify('info', 'New Recording started');
}
notify('info', 'New Recording started for ' + recordingUrl);
};
return (
<NavBarWrapper>
@@ -68,61 +79,92 @@ export const NavBar = ({ newRecording, recordingName, isRecording }: NavBarProps
display: 'flex',
justifyContent: 'flex-end',
}}>
<IconButton
aria-label="new"
size={"small"}
onClick={handleNewRecording}
sx={{
width: isRecording ? '100px' : '130px',
{
!isRecording ? (
<>
<IconButton
aria-label="new"
size={"small"}
onClick={handleNewRecording}
sx={{
width: '140px',
borderRadius: '5px',
padding: '8px',
background: '#ff00c3',
color: 'white',
marginRight: '10px',
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
fontWeight: '500',
fontSize: '0.875rem',
lineHeight: '1.75',
letterSpacing: '0.02857em',
'&:hover': { color: 'white', backgroundColor: '#ff00c3' }
}
}
>
<Add sx={{ marginRight: '5px' }} /> Create Robot
</IconButton>
<IconButton sx={{
width: '140px',
borderRadius: '5px',
padding: '8px',
background: '#ff00c3',
color: 'white',
marginRight: '10px',
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
fontWeight: '500',
fontSize: '0.875rem',
lineHeight: '1.75',
letterSpacing: '0.02857em',
'&:hover': { color: 'white', backgroundColor: '#ff00c3' }
}} onClick={logout}>
<Logout sx={{ marginRight: '5px' }} />
Logout</IconButton>
</>
) : <IconButton sx={{
width: '140px',
borderRadius: '5px',
padding: '8px',
background: 'white',
color: 'rgba(255,0,0,0.7)',
background: 'red',
color: 'white',
marginRight: '10px',
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
fontWeight: '500',
fontSize: '0.875rem',
lineHeight: '1.75',
letterSpacing: '0.02857em',
'&:hover': { color: 'red', backgroundColor: 'white' }
}
}
>
<Circle sx={{ marginRight: '5px' }} /> {isRecording ? 'NEW' : 'RECORD'}
</IconButton>
'&:hover': { color: 'white', backgroundColor: 'red' }
}} onClick={goToMainMenu}>
<Clear sx={{ marginRight: '5px' }} />
Discard</IconButton>
}
{
recordingLength > 0
? <SaveRecording fileName={recordingName} />
: null
}
{isRecording ? <Button sx={{
width: '100px',
background: '#fff',
color: 'rgba(25, 118, 210, 0.7)',
padding: '9px',
marginRight: '19px',
'&:hover': {
background: 'white',
color: 'rgb(25, 118, 210)',
}
}} onClick={goToMainMenu}>
<MeetingRoomIcon sx={{ marginRight: '5px' }} />
exit</Button>
: null}
<Button sx={{
width: '100px',
background: '#fff',
color: 'rgba(25, 118, 210, 0.7)',
padding: '9px',
marginRight: '19px',
'&:hover': {
background: 'white',
color: 'rgb(25, 118, 210)',
}
}} onClick={logout}>
<MeetingRoomIcon sx={{ marginRight: '5px' }} />
logout</Button>
</div>
<GenericModal isOpen={isModalOpen} onClose={() => setModalOpen(false)}>
<div style={{ padding: '20px' }}>
<h2>Enter URL</h2>
<TextField
label="URL"
variant="outlined"
fullWidth
value={recordingUrl}
onChange={(e: any) => setRecordingUrl(e.target.value)}
style={{ marginBottom: '20px' }}
/>
<Button
variant="contained"
color="primary"
onClick={startRecording}
disabled={!recordingUrl}
>
Submit & Start Recording
</Button>
</div>
</GenericModal>
</>
) : ""
}
@@ -133,13 +175,13 @@ export const NavBar = ({ newRecording, recordingName, isRecording }: NavBarProps
const NavBarWrapper = styled.div`
grid-area: navbar;
background-color: #3f4853;
background-color: white;
padding:5px;
display: flex;
justify-content: space-between;
`;
const ProjectName = styled.b`
color: white;
color: #3f4853;
font-size: 1.3em;
`;

View File

@@ -14,9 +14,15 @@ import { Schedule, DeleteForever, Edit, PlayCircle } from "@mui/icons-material";
import LinkIcon from '@mui/icons-material/Link';
import { useGlobalInfoStore } from "../../context/globalInfo";
import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
import { Typography } from '@mui/material';
/** TODO:
* 1. allow editing existing robot after persisting browser steps
* 2. show robot settings: id, url, etc.
*/
interface Column {
id: 'id' | 'interpret' | 'name' | 'createdAt' | 'edit' | 'updatedAt' | 'delete' | 'schedule' | 'integrate';
id: 'interpret' | 'name' | 'delete' | 'schedule' | 'integrate';
label: string;
minWidth?: number;
align?: 'right';
@@ -24,20 +30,19 @@ interface Column {
}
const columns: readonly Column[] = [
{ id: 'id', label: 'ID', minWidth: 80 },
{ id: 'interpret', label: 'Run', minWidth: 80 },
{ id: 'name', label: 'Name', minWidth: 80 },
{
id: 'createdAt',
label: 'Created at',
minWidth: 80,
//format: (value: string) => value.toLocaleString('en-US'),
},
{
id: 'edit',
label: 'Edit',
minWidth: 80,
},
// {
// id: 'createdAt',
// label: 'Created at',
// minWidth: 80,
// //format: (value: string) => value.toLocaleString('en-US'),
// },
// {
// id: 'edit',
// label: 'Edit',
// minWidth: 80,
// },
{
id: 'schedule',
label: 'Schedule',
@@ -48,12 +53,12 @@ const columns: readonly Column[] = [
label: 'Integrate',
minWidth: 80,
},
{
id: 'updatedAt',
label: 'Updated at',
minWidth: 80,
//format: (value: string) => value.toLocaleString('en-US'),
},
// {
// id: 'updatedAt',
// label: 'Updated at',
// minWidth: 80,
// //format: (value: string) => value.toLocaleString('en-US'),
// },
{
id: 'delete',
label: 'Delete',
@@ -121,7 +126,10 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
return (
<React.Fragment>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
<Typography variant="h6" gutterBottom component="div">
My Robots
</Typography>
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden', marginTop: '15px' }}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
@@ -159,16 +167,16 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
<InterpretButton handleInterpret={() => handleRunRecording(row.id, row.name, row.params || [])} />
</TableCell>
);
case 'edit':
return (
<TableCell key={column.id} align={column.align}>
<IconButton aria-label="add" size="small" onClick={() => {
handleEditRecording(row.id, row.name);
}} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
<Edit />
</IconButton>
</TableCell>
);
// case 'edit':
// return (
// <TableCell key={column.id} align={column.align}>
// <IconButton aria-label="add" size="small" onClick={() => {
// handleEditRecording(row.id, row.name);
// }} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
// <Edit />
// </IconButton>
// </TableCell>
// );
case 'schedule':
return (
<TableCell key={column.id} align={column.align}>

View File

@@ -1,4 +1,4 @@
import { Box, Tabs, Typography, Tab } from "@mui/material";
import { Box, Tabs, Typography, Tab, Paper } from "@mui/material";
import Highlight from "react-highlight";
import Button from "@mui/material/Button";
import * as React from "react";
@@ -8,8 +8,14 @@ import SettingsIcon from '@mui/icons-material/Settings';
import ImageIcon from '@mui/icons-material/Image';
import ArticleIcon from '@mui/icons-material/Article';
import { Buffer } from 'buffer';
import { useEffect } from "react";
import { useEffect, useState } from "react";
import AssignmentIcon from '@mui/icons-material/Assignment';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
interface RunContentProps {
row: Data,
@@ -21,19 +27,34 @@ interface RunContentProps {
export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRef, abortRunHandler }: RunContentProps) => {
const [tab, setTab] = React.useState<string>('log');
const [tableData, setTableData] = useState<any[]>([]);
const [columns, setColumns] = useState<string[]>([]);
useEffect(() => {
setTab(tab);
}, [interpretationInProgress])
useEffect(() => {
if (row.serializableOutput && Object.keys(row.serializableOutput).length > 0) {
const firstKey = Object.keys(row.serializableOutput)[0];
const data = row.serializableOutput[firstKey];
if (Array.isArray(data)) {
setTableData(data);
if (data.length > 0) {
setColumns(Object.keys(data[0]));
}
}
}
}, [row.serializableOutput]);
return (
<Box sx={{ width: '100%' }}>
<TabContext value={tab}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs value={tab} onChange={(e, newTab) => setTab(newTab)} aria-label="run-content-tabs">
<Tab label="Log" value='log' />
{/* <Tab label="Log" value='log' /> */}
<Tab label="Input" value='input' />
<Tab label="Output" value='output' />
<Tab label="Output Data" value='output' />
</Tabs>
</Box>
<TabPanel value='log'>
@@ -107,29 +128,52 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe
<div>
<Typography variant='h6' sx={{ display: 'flex', alignItems: 'center' }}>
<ArticleIcon sx={{ marginRight: '15px' }} />
Serializable output</Typography>
Serializable output
</Typography>
{Object.keys(row.serializableOutput).map((key) => {
return (
<div key={`number-of-serializable-output-${key}`}>
<Typography>
{key}:
<a href={`data:application/json;utf8,${JSON.stringify(row.serializableOutput[key], null, 2)}`}
download={key} style={{ margin: '10px' }}>Download</a>
download={key} style={{ margin: '10px' }}>Download as JSON</a>
</Typography>
<Box sx={{
width: 'fit-content',
background: 'rgba(0,0,0,0.06)',
maxHeight: '300px',
overflow: 'scroll',
}}>
<pre key={`serializable-output-${key}`}>
{row.serializableOutput[key] ? JSON.stringify(row.serializableOutput[key], null, 2)
: 'The output is empty.'}
</pre>
</Box>
</div>
)
})}
{tableData.length > 0 ? (
<TableContainer component={Paper} sx={{ maxHeight: 440, marginTop: 2 }}>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
{columns.map((column) => (
<TableCell key={column}>{column}</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{tableData.map((row, index) => (
<TableRow key={index}>
{columns.map((column) => (
<TableCell key={column}>{row[column]}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
) : (
<Box sx={{
width: 'fit-content',
background: 'rgba(0,0,0,0.06)',
maxHeight: '300px',
overflow: 'scroll',
}}>
<pre>
{JSON.stringify(row.serializableOutput, null, 2)}
</pre>
</Box>
)}
</div>
}
{row.binaryOutput

View File

@@ -16,7 +16,7 @@ import { Accordion, AccordionSummary, AccordionDetails, Typography } from '@mui/
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
interface Column {
id: 'status' | 'name' | 'startedAt' | 'finishedAt' | 'duration' | 'task' | 'runId' | 'delete';
id: 'status' | 'name' | 'startedAt' | 'finishedAt' | 'runId' | 'delete';
label: string;
minWidth?: number;
align?: 'right';
@@ -25,12 +25,11 @@ interface Column {
export const columns: readonly Column[] = [
{ id: 'status', label: 'Status', minWidth: 80 },
{ id: 'name', label: 'Name', minWidth: 80 },
{ id: 'name', label: 'Robot Name', minWidth: 80 },
{ id: 'startedAt', label: 'Started at', minWidth: 80 },
{ id: 'finishedAt', label: 'Finished at', minWidth: 80 },
{ id: 'duration', label: 'Duration', minWidth: 80 },
{ id: 'runId', label: 'Run id', minWidth: 80 },
{ id: 'task', label: 'Task', minWidth: 80 },
{ id: 'runId', label: 'Run ID', minWidth: 80 },
// { id: 'task', label: 'Task', minWidth: 80 },
{ id: 'delete', label: 'Delete', minWidth: 80 },
];
@@ -40,8 +39,7 @@ export interface Data {
name: string;
startedAt: string;
finishedAt: string;
duration: string;
task: string;
// task: string;
log: string;
runId: string;
interpreterSettings: RunSettings;
@@ -78,7 +76,6 @@ export const RunsTable = (
if (runs) {
const parsedRows: Data[] = [];
runs.map((run: any, index) => {
// const run = JSON.parse(run);
parsedRows.push({
id: index,
...run,

View File

@@ -8,6 +8,7 @@ import { TextField, Typography } from "@mui/material";
import { WarningText } from "../atoms/texts";
import NotificationImportantIcon from "@mui/icons-material/NotificationImportant";
import FlagIcon from '@mui/icons-material/Flag';
import { DoneAll } from '@mui/icons-material'
import { useNavigate } from 'react-router-dom';
interface SaveRecordingProps {
@@ -68,26 +69,32 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
return (
<div>
<Button sx={{
width: '100px',
background: 'white',
color: 'rgba(0,128,0,0.7)',
'&:hover': { background: 'white', color: 'green' },
padding: '11px',
<IconButton sx={{
width: '140px',
background: 'green',
color: 'white',
'&:hover': { background: 'green', color: 'white' },
padding: '13px',
marginRight: '10px',
borderRadius: '5px',
fontFamily: '"Roboto","Helvetica","Arial",sans-serif',
fontWeight: '500',
fontSize: '0.875rem',
lineHeight: '1.75',
letterSpacing: '0.02857em',
}} onClick={() => setOpenModal(true)}>
<FlagIcon sx={{ marginRight: '3px' }} /> FINISH
</Button>
<DoneAll sx={{ marginRight: '5px' }} /> Finish
</IconButton>
<GenericModal isOpen={openModal} onClose={() => setOpenModal(false)} modalStyle={modalStyle}>
<form onSubmit={handleSaveRecording} style={{ paddingTop: '50px', display: 'flex', flexDirection: 'column' }} >
<Typography>Save the recording as:</Typography>
<form onSubmit={handleSaveRecording} style={{ paddingTop: '20px', display: 'flex', flexDirection: 'column' }} >
<Typography variant="h5">Save the robot as</Typography>
<TextField
required
sx={{ width: '250px', paddingBottom: '10px', margin: '15px 0px' }}
onChange={handleChangeOfTitle}
id="title"
label="Recording title"
label="Robot Name"
variant="outlined"
defaultValue={recordingName ? recordingName : null}
/>
@@ -97,7 +104,7 @@ export const SaveRecording = ({ fileName }: SaveRecordingProps) => {
<Button color="error" variant="contained" onClick={saveRecording}>Confirm</Button>
<WarningText>
<NotificationImportantIcon color="warning" />
Recording already exists, please confirm the recording's overwrite.
Robot with this name already exists, please confirm the Robot's overwrite.
</WarningText>
</React.Fragment>)
: <Button type="submit" variant="contained">Save</Button>

View File

@@ -1,12 +1,12 @@
import { useState, useCallback, useEffect, } from 'react';
import type { SyntheticEvent, } from 'react';
import React, { useState, useEffect, useCallback, useRef } from 'react';
import type { SyntheticEvent } from 'react';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import { NavBarForm, NavBarInput } from "../atoms/form";
import { UrlFormButton } from "../atoms/buttons/buttons";
import { useSocketStore } from '../../context/socket';
import { Socket } from "socket.io-client";
// TODO: Bring back REFRESHHHHHHH
type Props = {
currentAddress: string;
handleRefresh: (socket: Socket) => void;
@@ -18,45 +18,43 @@ export const UrlForm = ({
handleRefresh,
setCurrentAddress,
}: Props) => {
// states:
const [address, setAddress] = useState<string>(currentAddress);
// context:
const { socket } = useSocketStore();
const areSameAddresses = address === currentAddress;
const lastSubmittedRef = useRef<string>('');
const onChange = useCallback((event: SyntheticEvent): void => {
setAddress((event.target as HTMLInputElement).value);
}, [address]);
}, []);
const submitForm = useCallback((url: string): void => {
// Add protocol if missing
if (!/^(?:f|ht)tps?\:\/\//.test(url)) {
url = "https://" + url;
setAddress(url); // Update the input field to reflect protocol addition
}
try {
// Validate the URL
new URL(url);
setCurrentAddress(url);
lastSubmittedRef.current = url; // Update the last submitted URL
} catch (e) {
alert(`ERROR: ${url} is not a valid url!`);
}
}, [setCurrentAddress]);
const onSubmit = (event: SyntheticEvent): void => {
event.preventDefault();
let url = address;
// add protocol if missing
if (!/^(?:f|ht)tps?\:\/\//.test(address)) {
url = "https://" + address;
setAddress(url);
}
if (areSameAddresses) {
if (socket) {
handleRefresh(socket);
}
} else {
try {
// try the validity of url
new URL(url);
setCurrentAddress(url);
} catch (e) {
alert(`ERROR: ${url} is not a valid url!`);
}
}
submitForm(address);
};
// Sync internal state with currentAddress prop when it changes and auto-submit once
useEffect(() => {
setAddress(currentAddress)
}, [currentAddress]);
setAddress(currentAddress);
if (currentAddress !== '' && currentAddress !== lastSubmittedRef.current) {
submitForm(currentAddress);
}
}, [currentAddress, submitForm]);
return (
<NavBarForm onSubmit={onSubmit}>
@@ -70,4 +68,4 @@ export const UrlForm = ({
</UrlFormButton>
</NavBarForm>
);
};
};

View File

@@ -89,8 +89,9 @@ const ApiKeyManager = () => {
return (
<Container>
<Typography variant="h5" sx={{ marginBottom: '20px'}}>Manage Your API Key</Typography>
<Typography variant="h6" gutterBottom component="div" style={{ marginBottom: '20px', alignSelf: 'flex-start' }}>
Manage Your API Key
</Typography>
{apiKey ? (
<TableContainer component={Paper} sx={{ width: '100%', overflow: 'hidden' }}>
<Table>
@@ -106,18 +107,18 @@ const ApiKeyManager = () => {
<TableCell>{apiKeyName}</TableCell>
<TableCell>{showKey ? `${apiKey?.substring(0, 10)}...` : '***************'}</TableCell>
<TableCell>
<Tooltip title="Copy API Key">
<Tooltip title="Copy">
<IconButton onClick={copyToClipboard}>
<ContentCopy />
</IconButton>
</Tooltip>
<Tooltip title={showKey ? 'Hide API Key' : 'Show API Key'}>
<Tooltip title={showKey ? 'Hide' : 'Show'}>
<IconButton onClick={() => setShowKey(!showKey)}>
<Visibility />
</IconButton>
</Tooltip>
<Tooltip title="Delete API Key">
<IconButton onClick={deleteApiKey} color="error">
<Tooltip title="Delete">
<IconButton onClick={deleteApiKey}>
<Delete />
</IconButton>
</Tooltip>
@@ -129,7 +130,7 @@ const ApiKeyManager = () => {
) : (
<>
<Typography>You haven't generated an API key yet.</Typography>
<Button onClick={generateApiKey} variant="contained" color="primary" sx={{ marginTop: '15px'}}>
<Button onClick={generateApiKey} variant="contained" color="primary" sx={{ marginTop: '15px' }}>
Generate API Key
</Button>
</>

View File

@@ -6,7 +6,9 @@ import { useBrowserDimensionsStore } from "../../context/browserDimensions";
import { BrowserTabs } from "../molecules/BrowserTabs";
import { useSocketStore } from "../../context/socket";
import { getCurrentTabs, getCurrentUrl, interpretCurrentRecording } from "../../api/recording";
import { Box } from '@mui/material';
// TODO: Tab !show currentUrl after recordingUrl global state
export const BrowserContent = () => {
const { width } = useBrowserDimensionsStore();
const { socket } = useSocketStore();
@@ -113,7 +115,7 @@ export const BrowserContent = () => {
}, [])
return (
<BrowserContentWrapper>
<>
<BrowserTabs
tabs={tabs}
handleTabChange={handleTabChange}
@@ -124,14 +126,13 @@ export const BrowserContent = () => {
/>
<BrowserNavBar
// todo: use width from browser dimension once fixed
browserWidth={1270}
browserWidth={900}
handleUrlChanged={handleUrlChanged}
/>
<BrowserWindow/>
</BrowserContentWrapper>
</>
);
}
const BrowserContentWrapper = styled.div`
grid-area: browser;
`;

View File

@@ -63,7 +63,7 @@ export const LeftSidePanel = (
if (id) {
fetchWorkflow(id, workflowHandler);
}
}, (1000 * 60 * 15));
}, (900 * 60 * 15));
return () => clearInterval(interval)
}, [id]);

View File

@@ -19,10 +19,11 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
<Paper
sx={{
height: 'auto',
maxWidth: 'fit-content',
backgroundColor: 'lightgray',
width: '250px',
backgroundColor: 'white',
paddingTop: '2rem',
}}
variant="outlined"
>
<Box sx={{
width: '100%',
@@ -38,7 +39,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
<Tab sx={{
alignItems: 'baseline',
fontSize: 'medium',
}} value="recordings" label="Recordings" />
}} value="recordings" label="Robots" />
<Tab sx={{
alignItems: 'baseline',
fontSize: 'medium',

View File

@@ -8,8 +8,7 @@ const FormContainer = styled(Box)({
display: 'flex',
flexDirection: 'column',
gap: '16px',
padding: '20px',
borderRadius: '8px',
marginLeft: '30px'
});
const FormControl = styled(Box)({
@@ -85,10 +84,10 @@ const ProxyForm: React.FC = () => {
return (
<FormContainer>
<form onSubmit={handleSubmit}>
<Typography variant="subtitle1" gutterBottom style={{ marginBottom: '20px', marginTop: '20px' }}>
Proxy Configuration
</Typography>
<Typography variant="h6" gutterBottom component="div" style={{ marginTop: '20px' }}>
Proxy Configuration
</Typography>
<Box component="form" onSubmit={handleSubmit} sx={{ maxWidth: 400, width: '100%' }}>
<FormControl>
<TextField
label="Proxy Server URL"
@@ -98,9 +97,9 @@ const ProxyForm: React.FC = () => {
fullWidth
required
error={!!errors.server_url}
helperText={errors.server_url || `Proxy to be used for all requests.
HTTP and SOCKS proxies are supported, for example http://myproxy.com:3128 or
socks5://myproxy.com:3128. Short form myproxy.com:3128 is considered an HTTP proxy.`}
helperText={errors.server_url || `Proxy to be used for all robots. HTTP and SOCKS proxies are supported.
Example http://myproxy.com:3128 or socks5://myproxy.com:3128.
Short form myproxy.com:3128 is considered an HTTP proxy.`}
/>
</FormControl>
<FormControl>
@@ -147,7 +146,7 @@ const ProxyForm: React.FC = () => {
>
Add Proxy
</Button>
</form>
</Box>
</FormContainer>
);
};

View File

@@ -20,6 +20,8 @@ interface GlobalInfo {
setRecordingId: (newId: string | null) => void;
recordingName: string;
setRecordingName: (recordingName: string) => void;
recordingUrl: string;
setRecordingUrl: (recordingUrl: string) => void;
};
class GlobalInfoStore implements Partial<GlobalInfo> {
@@ -35,6 +37,7 @@ class GlobalInfoStore implements Partial<GlobalInfo> {
recordings: string[] = [];
rerenderRuns = false;
recordingName = '';
recordingUrl = 'https://';
};
const globalInfoStore = new GlobalInfoStore();
@@ -51,6 +54,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
const [recordingLength, setRecordingLength] = useState<number>(globalInfoStore.recordingLength);
const [recordingId, setRecordingId] = useState<string | null>(globalInfoStore.recordingId);
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
const [recordingUrl, setRecordingUrl] = useState<string>(globalInfoStore.recordingUrl);
const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => {
setNotification({ severity, message, isOpen: true });
@@ -86,7 +90,9 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
recordingId,
setRecordingId,
recordingName,
setRecordingName
setRecordingName,
recordingUrl,
setRecordingUrl,
}}
>
{children}

View File

@@ -104,7 +104,6 @@ const Login = () => {
)}
</Button>
{/* Redirect to Register */}
<Typography variant="body2" align="center">
Dont have an account?{' '}
<Link to="/register" style={{ textDecoration: 'none', color: '#1976d2' }}>

View File

@@ -158,7 +158,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
}
return (
<Stack direction='row' spacing={0} sx={{ minHeight: '800px' }}>
<Stack direction='row' spacing={0} sx={{ minHeight: '900px' }}>
<MainMenu value={content} handleChangeContent={setContent} />
{DisplayContent()}
</Stack>

View File

@@ -50,64 +50,66 @@ const Register = () => {
};
return (
<Container maxWidth="sm" sx={{ mt: 8 }}>
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
>
<Typography component="h1" variant="h5">
Create an account
<Box
sx={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
mt: 5,
}}
>
<Typography variant="h4" gutterBottom>
Create an account
</Typography>
<Box component="form" onSubmit={submitForm} sx={{ maxWidth: 400, width: '100%' }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
value={email}
onChange={handleChange}
autoComplete="email"
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={password}
onChange={handleChange}
autoComplete="current-password"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 3, mb: 2 }}
disabled={loading || !email || !password}
>
{loading ? (
<>
<CircularProgress size={20} sx={{ mr: 2 }} />
Loading
</>
) : (
'Register'
)}
</Button>
<Typography variant="body2" align="center">
Already have an account?{' '}
<Link to="/login" style={{ textDecoration: 'none', color: '#1976d2' }}>
Login
</Link>
</Typography>
<Box component="form" onSubmit={submitForm} sx={{ mt: 1 }}>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Email Address"
name="email"
value={email}
onChange={handleChange}
autoComplete="email"
/>
<TextField
margin="normal"
required
fullWidth
name="password"
label="Password"
type="password"
id="password"
value={password}
onChange={handleChange}
autoComplete="current-password"
/>
<Button
type="submit"
fullWidth
variant="contained"
color="primary"
sx={{ mt: 3, mb: 2 }}
disabled={loading || !email || !password}
>
{loading ? (
<CircularProgress size={24} sx={{ color: '#fff' }} />
) : (
'Register'
)}
</Button>
<Typography variant="body2" align="center">
Already have an account?{' '}
<Link to="/login" style={{ textDecoration: 'none', color: '#1976d2' }}>
Login
</Link>
</Typography>
</Box>
</Box>
</Container>
</Box>
);
};