Merge pull request #82 from amhsirak/develop

feat: more scheduler options
This commit is contained in:
Karishma Shukla
2024-10-23 04:19:15 +05:30
committed by GitHub
7 changed files with 398 additions and 141 deletions

View File

@@ -30,6 +30,8 @@ export class RemoteBrowser {
*/ */
private browser: Browser | null = null; private browser: Browser | null = null;
private context: BrowserContext | null = null;
/** /**
* The Playwright's [CDPSession](https://playwright.dev/docs/api/class-cdpsession) instance, * The Playwright's [CDPSession](https://playwright.dev/docs/api/class-cdpsession) instance,
* used to talk raw Chrome Devtools Protocol. * used to talk raw Chrome Devtools Protocol.
@@ -90,13 +92,13 @@ export class RemoteBrowser {
*/ */
public initialize = async (options: RemoteBrowserOptions): Promise<void> => { public initialize = async (options: RemoteBrowserOptions): Promise<void> => {
this.browser = <Browser>(await options.browser.launch(options.launchOptions)); this.browser = <Browser>(await options.browser.launch(options.launchOptions));
const context = await this.browser.newContext( this.context = await this.browser.newContext(
{ {
viewport: { height: 400, width: 900 }, viewport: { height: 400, width: 900 },
// recordVideo: { dir: 'videos/' } // recordVideo: { dir: 'videos/' }
} }
); );
this.currentPage = await context.newPage(); this.currentPage = await this.context.newPage();
const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch); const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch);
await blocker.enableBlockingInPage(this.currentPage); await blocker.enableBlockingInPage(this.currentPage);
this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage);
@@ -138,6 +140,16 @@ export class RemoteBrowser {
logger.log('error', `${tabInfo.index} index out of range of pages`) logger.log('error', `${tabInfo.index} index out of range of pages`)
} }
}); });
this.socket.on('setViewportSize', async (data: { width: number, height: number }) => {
const { width, height } = data;
logger.log('debug', `Received viewport size: width=${width}, height=${height}`);
// Update the browser context's viewport dynamically
if (this.context && this.browser) {
this.context = await this.browser.newContext({ viewport: { width, height } });
logger.log('debug', `Viewport size updated to width=${width}, height=${height} for the entire browser context`);
}
});
} }
/** /**
@@ -280,7 +292,7 @@ export class RemoteBrowser {
if (page) { if (page) {
await this.stopScreencast(); await this.stopScreencast();
this.currentPage = page; this.currentPage = page;
await this.currentPage.setViewportSize({ height: 400, width: 900 }) //await this.currentPage.setViewportSize({ height: 400, width: 900 })
this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.client = await this.currentPage.context().newCDPSession(this.currentPage);
this.socket.emit('urlChanged', this.currentPage.url()); this.socket.emit('urlChanged', this.currentPage.url());
await this.makeAndEmitScreenshot(); await this.makeAndEmitScreenshot();

View File

@@ -1,8 +1,6 @@
import { Model, DataTypes, Optional } from 'sequelize'; import { Model, DataTypes, Optional } from 'sequelize';
import sequelize from '../storage/db'; import sequelize from '../storage/db';
import { WorkflowFile, Where, What, WhereWhatPair } from 'maxun-core'; import { WorkflowFile, Where, What, WhereWhatPair } from 'maxun-core';
import User from './User'; // Import User model
import Run from './Run';
interface RobotMeta { interface RobotMeta {
name: string; name: string;
@@ -27,6 +25,19 @@ interface RobotAttributes {
google_sheet_id?: string | null; google_sheet_id?: string | null;
google_access_token?: string | null; google_access_token?: string | null;
google_refresh_token?: string | null; google_refresh_token?: string | null;
schedule?: ScheduleConfig | null;
}
interface ScheduleConfig {
runEvery: number;
runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS';
startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY';
atTimeStart?: string;
atTimeEnd?: string;
timezone: string;
lastRunAt?: Date;
nextRunAt?: Date;
cronExpression?: string;
} }
interface RobotCreationAttributes extends Optional<RobotAttributes, 'id'> { } interface RobotCreationAttributes extends Optional<RobotAttributes, 'id'> { }
@@ -41,6 +52,7 @@ class Robot extends Model<RobotAttributes, RobotCreationAttributes> implements R
public google_sheet_id?: string | null; public google_sheet_id?: string | null;
public google_access_token!: string | null; public google_access_token!: string | null;
public google_refresh_token!: string | null; public google_refresh_token!: string | null;
public schedule!: ScheduleConfig | null;
} }
Robot.init( Robot.init(
@@ -82,6 +94,10 @@ Robot.init(
type: DataTypes.STRING, type: DataTypes.STRING,
allowNull: true, allowNull: true,
}, },
schedule: {
type: DataTypes.JSONB,
allowNull: true,
},
}, },
{ {
sequelize, sequelize,

View File

@@ -12,7 +12,7 @@ import { requireSignIn } from '../middlewares/auth';
import Robot from '../models/Robot'; import Robot from '../models/Robot';
import Run from '../models/Run'; import Run from '../models/Run';
import { BinaryOutputService } from '../storage/mino'; import { BinaryOutputService } from '../storage/mino';
// import { workflowQueue } from '../worker'; import { workflowQueue } from '../worker';
export const router = Router(); export const router = Router();
@@ -46,7 +46,7 @@ router.get('/recordings/:id', requireSignIn, async (req, res) => {
where: { 'recording_meta.id': req.params.id }, where: { 'recording_meta.id': req.params.id },
raw: true raw: true
} }
); );
return res.send(data); return res.send(data);
} catch (e) { } catch (e) {
logger.log('info', 'Error while reading recordings'); logger.log('info', 'Error while reading recordings');
@@ -208,8 +208,8 @@ router.post('/runs/run/:id', requireSignIn, async (req, res) => {
if (browser && currentPage) { if (browser && currentPage) {
const interpretationInfo = await browser.interpreter.InterpretRecording( const interpretationInfo = await browser.interpreter.InterpretRecording(
recording.recording, currentPage, plainRun.interpreterSettings); recording.recording, currentPage, plainRun.interpreterSettings);
const binaryOutputService = new BinaryOutputService('maxun-run-screenshots'); const binaryOutputService = new BinaryOutputService('maxun-run-screenshots');
const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput); const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput);
await destroyRemoteBrowser(plainRun.browserId); await destroyRemoteBrowser(plainRun.browserId);
await run.update({ await run.update({
...run, ...run,
@@ -247,18 +247,45 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const { const {
// enabled = true,
runEvery, runEvery,
runEveryUnit, runEveryUnit,
startFrom, startFrom,
atTime, atTimeStart,
atTimeEnd,
timezone timezone
} = req.body; } = req.body;
if (!id || !runEvery || !runEveryUnit || !startFrom || !atTime || !timezone) { const robot = await Robot.findOne({ where: { 'recording_meta.id': id } });
if (!robot) {
return res.status(404).json({ error: 'Robot not found' });
}
// If disabled, remove scheduling
// if (!enabled) {
// // Remove existing job from queue if it exists
// const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']);
// for (const job of existingJobs) {
// if (job.data.id === id) {
// await job.remove();
// }
// }
// // Update robot to disable scheduling
// await robot.update({
// schedule: null
// });
// return res.status(200).json({
// message: 'Schedule disabled successfully'
// });
// }
if (!id || !runEvery || !runEveryUnit || !startFrom || !timezone || (runEveryUnit === 'HOURS' || runEveryUnit === 'MINUTES') && (!atTimeStart || !atTimeEnd)) {
return res.status(400).json({ error: 'Missing required parameters' }); return res.status(400).json({ error: 'Missing required parameters' });
} }
if (!['HOURS', 'DAYS', 'WEEKS', 'MONTHS'].includes(runEveryUnit)) { if (!['HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'MINUTES'].includes(runEveryUnit)) {
return res.status(400).json({ error: 'Invalid runEvery unit' }); return res.status(400).json({ error: 'Invalid runEvery unit' });
} }
@@ -266,8 +293,12 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
return res.status(400).json({ error: 'Invalid timezone' }); return res.status(400).json({ error: 'Invalid timezone' });
} }
const [hours, minutes] = atTime.split(':').map(Number); const [startHours, startMinutes] = atTimeStart.split(':').map(Number);
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) { const [endHours, endMinutes] = atTimeEnd.split(':').map(Number);
if (isNaN(startHours) || isNaN(startMinutes) || isNaN(endHours) || isNaN(endMinutes) ||
startHours < 0 || startHours > 23 || startMinutes < 0 || startMinutes > 59 ||
endHours < 0 || endHours > 23 || endMinutes < 0 || endMinutes > 59) {
return res.status(400).json({ error: 'Invalid time format' }); return res.status(400).json({ error: 'Invalid time format' });
} }
@@ -278,18 +309,19 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
let cronExpression; let cronExpression;
switch (runEveryUnit) { switch (runEveryUnit) {
case 'MINUTES':
case 'HOURS': case 'HOURS':
cronExpression = `${minutes} */${runEvery} * * *`; cronExpression = `${startMinutes}-${endMinutes} */${runEvery} * * *`;
break; break;
case 'DAYS': case 'DAYS':
cronExpression = `${minutes} ${hours} */${runEvery} * *`; cronExpression = `${startMinutes} ${startHours} */${runEvery} * *`;
break; break;
case 'WEEKS': case 'WEEKS':
const dayIndex = days.indexOf(startFrom); const dayIndex = days.indexOf(startFrom);
cronExpression = `${minutes} ${hours} * * ${dayIndex}/${7 * runEvery}`; cronExpression = `${startMinutes} ${startHours} * * ${dayIndex}/${7 * runEvery}`;
break; break;
case 'MONTHS': case 'MONTHS':
cronExpression = `${minutes} ${hours} 1-7 */${runEvery} *`; cronExpression = `${startMinutes} ${startHours} 1-7 */${runEvery} *`;
if (startFrom !== 'SUNDAY') { if (startFrom !== 'SUNDAY') {
const dayIndex = days.indexOf(startFrom); const dayIndex = days.indexOf(startFrom);
cronExpression += ` ${dayIndex}`; cronExpression += ` ${dayIndex}`;
@@ -304,22 +336,50 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
const runId = uuid(); const runId = uuid();
const userId = req.user.id; const userId = req.user.id;
// await workflowQueue.add( // Remove existing jobs for this robot just in case some were left
// 'run workflow', // const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']);
// { id, runId, userId }, // for (const job of existingJobs) {
// { // if (job.data.id === id) {
// repeat: { // await job.remove();
// pattern: cronExpression, // }
// tz: timezone // }
// }
// } // Add new job
// ); const job = await workflowQueue.add(
'run workflow',
{ id, runId, userId },
{
repeat: {
pattern: cronExpression,
tz: timezone
}
}
);
const nextRun = job.timestamp;
// Update robot with schedule details
await robot.update({
schedule: {
runEvery,
runEveryUnit,
startFrom,
atTimeStart,
atTimeEnd,
timezone,
cronExpression,
lastRunAt: undefined,
nextRunAt: new Date(nextRun)
}
});
// Fetch updated schedule details after setting it
const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } });
res.status(200).json({ res.status(200).json({
message: 'success', message: 'success',
runId, runId,
// cronExpression, robot: updatedRobot
// nextRunTime: getNextRunTime(cronExpression, timezone)
}); });
} catch (error) { } catch (error) {
@@ -328,12 +388,55 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
} }
}); });
// function getNextRunTime(cronExpression, timezone) { // Endpoint to get schedule details
// const schedule = cron.schedule(cronExpression, () => {}, { timezone }); router.get('/schedule/:id', requireSignIn, async (req, res) => {
// const nextDate = schedule.nextDate(); try {
// schedule.stop(); const robot = await Robot.findOne({ where: { 'recording_meta.id': req.params.id }, raw: true });
// return nextDate.toDate();
// } if (!robot) {
return res.status(404).json({ error: 'Robot not found' });
}
return res.status(200).json({
schedule: robot.schedule
});
} catch (error) {
console.error('Error getting schedule:', error);
res.status(500).json({ error: 'Failed to get schedule' });
}
});
// Endpoint to delete schedule
router.delete('/schedule/:id', requireSignIn, async (req, res) => {
try {
const { id } = req.params;
const robot = await Robot.findOne({ where: { 'recording_meta.id': id } });
if (!robot) {
return res.status(404).json({ error: 'Robot not found' });
}
// Remove existing job from queue if it exists
const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']);
for (const job of existingJobs) {
if (job.data.id === id) {
await job.remove();
}
}
// Delete the schedule from the robot
await robot.update({
schedule: null
});
res.status(200).json({ message: 'Schedule deleted successfully' });
} catch (error) {
console.error('Error deleting schedule:', error);
res.status(500).json({ error: 'Failed to delete schedule' });
}
});
/** /**
* POST endpoint for aborting a current interpretation of the run. * POST endpoint for aborting a current interpretation of the run.

View File

@@ -58,16 +58,19 @@ readdirSync(path.join(__dirname, 'api')).forEach((r) => {
} }
}); });
const workerProcess = fork(path.resolve(__dirname, './worker.ts')); const workerProcess = fork(path.resolve(__dirname, './worker.ts'), [], {
workerProcess.on('message', (message) => { execArgv: ['--inspect=5859'], // Specify a different debug port for the worker
console.log(`Message from worker: ${message}`); });
});
workerProcess.on('message', (message) => {
console.log(`Message from worker: ${message}`);
});
workerProcess.on('error', (error) => { workerProcess.on('error', (error) => {
console.error(`Error in worker: ${error}`); console.error(`Error in worker: ${error}`);
}); });
workerProcess.on('exit', (code) => { workerProcess.on('exit', (code) => {
console.log(`Worker exited with code: ${code}`); console.log(`Worker exited with code: ${code}`);
}); });
app.get('/', function (req, res) { app.get('/', function (req, res) {
return res.send('Maxun server started 🚀'); return res.send('Maxun server started 🚀');
@@ -81,6 +84,6 @@ server.listen(SERVER_PORT, async () => {
process.on('SIGINT', () => { process.on('SIGINT', () => {
console.log('Main app shutting down...'); console.log('Main app shutting down...');
//workerProcess.kill(); workerProcess.kill();
process.exit(); process.exit();
}); });

View File

@@ -52,4 +52,8 @@ process.on('SIGINT', () => {
process.exit(); process.exit();
}); });
export { workflowQueue, worker }; export { workflowQueue, worker };
export const temp = () => {
console.log('temp');
}

View File

@@ -12,7 +12,7 @@ export const getStoredRecordings = async (): Promise<string[] | null> => {
} else { } else {
throw new Error('Couldn\'t retrieve stored recordings'); throw new Error('Couldn\'t retrieve stored recordings');
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return null; return null;
} }
@@ -26,7 +26,7 @@ export const getStoredRuns = async (): Promise<string[] | null> => {
} else { } else {
throw new Error('Couldn\'t retrieve stored recordings'); throw new Error('Couldn\'t retrieve stored recordings');
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return null; return null;
} }
@@ -40,7 +40,7 @@ export const getStoredRecording = async (id: string) => {
} else { } else {
throw new Error(`Couldn't retrieve stored recording ${id}`); throw new Error(`Couldn't retrieve stored recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return null; return null;
} }
@@ -54,7 +54,7 @@ export const deleteRecordingFromStorage = async (id: string): Promise<boolean> =
} else { } else {
throw new Error(`Couldn't delete stored recording ${id}`); throw new Error(`Couldn't delete stored recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return false; return false;
} }
@@ -68,7 +68,7 @@ export const deleteRunFromStorage = async (id: string): Promise<boolean> => {
} else { } else {
throw new Error(`Couldn't delete stored recording ${id}`); throw new Error(`Couldn't delete stored recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return false; return false;
} }
@@ -82,7 +82,7 @@ export const editRecordingFromStorage = async (browserId: string, id: string): P
} else { } else {
throw new Error(`Couldn't edit stored recording ${id}`); throw new Error(`Couldn't edit stored recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return null; return null;
} }
@@ -92,15 +92,15 @@ export const createRunForStoredRecording = async (id: string, settings: RunSetti
try { try {
const response = await axios.put( const response = await axios.put(
`http://localhost:8080/storage/runs/${id}`, `http://localhost:8080/storage/runs/${id}`,
{...settings}); { ...settings });
if (response.status === 200) { if (response.status === 200) {
return response.data; return response.data;
} else { } else {
throw new Error(`Couldn't create a run for a recording ${id}`); throw new Error(`Couldn't create a run for a recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return {browserId: '', runId: ''}; return { browserId: '', runId: '' };
} }
} }
@@ -112,13 +112,13 @@ export const interpretStoredRecording = async (id: string): Promise<boolean> =>
} else { } else {
throw new Error(`Couldn't run a recording ${id}`); throw new Error(`Couldn't run a recording ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return false; return false;
} }
} }
export const notifyAboutAbort = async (id:string): Promise<boolean> => { export const notifyAboutAbort = async (id: string): Promise<boolean> => {
try { try {
const response = await axios.post(`http://localhost:8080/storage/runs/abort/${id}`); const response = await axios.post(`http://localhost:8080/storage/runs/abort/${id}`);
if (response.status === 200) { if (response.status === 200) {
@@ -126,7 +126,7 @@ export const notifyAboutAbort = async (id:string): Promise<boolean> => {
} else { } else {
throw new Error(`Couldn't abort a running recording with id ${id}`); throw new Error(`Couldn't abort a running recording with id ${id}`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return false; return false;
} }
@@ -136,14 +136,42 @@ export const scheduleStoredRecording = async (id: string, settings: ScheduleSett
try { try {
const response = await axios.put( const response = await axios.put(
`http://localhost:8080/storage/schedule/${id}`, `http://localhost:8080/storage/schedule/${id}`,
{...settings}); { ...settings });
if (response.status === 200) { if (response.status === 200) {
return response.data; return response.data;
} else { } else {
throw new Error(`Couldn't schedule recording ${id}. Please try again later.`); throw new Error(`Couldn't schedule recording ${id}. Please try again later.`);
} }
} catch(error: any) { } catch (error: any) {
console.log(error); console.log(error);
return {message: '', runId: ''}; return { message: '', runId: '' };
} }
} }
export const getSchedule = async (id: string) => {
try {
const response = await axios.get(`http://localhost:8080/storage/schedule/${id}`);
if (response.status === 200) {
return response.data.schedule;
} else {
throw new Error(`Couldn't retrieve schedule for recording ${id}`);
}
} catch (error: any) {
console.log(error);
return null;
}
}
export const deleteSchedule = async (id: string): Promise<boolean> => {
try {
const response = await axios.delete(`http://localhost:8080/storage/schedule/${id}`);
if (response.status === 200) {
return response.data;
} else {
throw new Error(`Couldn't delete schedule for recording ${id}`);
}
} catch (error: any) {
console.log(error);
return false;
}
}

View File

@@ -1,40 +1,51 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { GenericModal } from "../atoms/GenericModal"; import { GenericModal } from "../atoms/GenericModal";
import { MenuItem, TextField, Typography, Box } from "@mui/material"; import { MenuItem, TextField, Typography, Box, Switch, FormControlLabel } from "@mui/material";
import { Dropdown } from "../atoms/DropdownMui"; import { Dropdown } from "../atoms/DropdownMui";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import { modalStyle } from "./AddWhereCondModal"; import { modalStyle } from "./AddWhereCondModal";
import { validMomentTimezones } from '../../constants/const'; import { validMomentTimezones } from '../../constants/const';
import { useGlobalInfoStore } from '../../context/globalInfo';
import { getSchedule, deleteSchedule } from '../../api/storage';
interface ScheduleSettingsProps { interface ScheduleSettingsProps {
isOpen: boolean; isOpen: boolean;
handleStart: (settings: ScheduleSettings) => void; handleStart: (settings: ScheduleSettings) => void;
handleClose: () => void; handleClose: () => void;
initialSettings?: ScheduleSettings | null;
} }
export interface ScheduleSettings { export interface ScheduleSettings {
runEvery: number; runEvery: number;
runEveryUnit: string; runEveryUnit: string;
startFrom: string; startFrom: string;
atTime: string; atTimeStart?: string;
atTimeEnd?: string;
timezone: string; timezone: string;
} }
export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: ScheduleSettingsProps) => { export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: ScheduleSettingsProps) => {
const [schedule, setSchedule] = useState<ScheduleSettings | null>(null);
const [settings, setSettings] = useState<ScheduleSettings>({ const [settings, setSettings] = useState<ScheduleSettings>({
runEvery: 1, runEvery: 1,
runEveryUnit: 'HOURS', runEveryUnit: 'HOURS',
startFrom: 'MONDAY', startFrom: 'MONDAY',
atTime: '00:00', atTimeStart: '00:00',
atTimeEnd: '01:00',
timezone: 'UTC' timezone: 'UTC'
}); });
const handleChange = (field: keyof ScheduleSettings, value: string | number) => { // Load initial settings if provided
useEffect(() => {
if (initialSettings) {
setSettings(initialSettings);
}
}, [initialSettings]);
const handleChange = (field: keyof ScheduleSettings, value: string | number | boolean) => {
setSettings(prev => ({ ...prev, [field]: value })); setSettings(prev => ({ ...prev, [field]: value }));
}; };
console.log(`Settings:`, settings);
const textStyle = { const textStyle = {
width: '150px', width: '150px',
height: '52px', height: '52px',
@@ -49,11 +60,12 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
}; };
const units = [ const units = [
'MINUTES',
'HOURS', 'HOURS',
'DAYS', 'DAYS',
'WEEKS', 'WEEKS',
'MONTHS' 'MONTHS'
] ];
const days = [ const days = [
'MONDAY', 'MONDAY',
@@ -63,7 +75,48 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
'FRIDAY', 'FRIDAY',
'SATURDAY', 'SATURDAY',
'SUNDAY' 'SUNDAY'
] ];
const { recordingId } = useGlobalInfoStore();
console.log(`Recoridng ID Shculde: ${recordingId}`);
const deleteRobotSchedule = () => {
if (recordingId) {
deleteSchedule(recordingId);
setSchedule(null);
} else {
console.error('No recording id provided');
}
setSettings({
runEvery: 1,
runEveryUnit: 'HOURS',
startFrom: 'MONDAY',
atTimeStart: '00:00',
atTimeEnd: '01:00',
timezone: 'UTC'
});
};
const getRobotSchedule = async () => {
if (recordingId) {
const scheduleData = await getSchedule(recordingId);
console.log(`Robot found schedule: ${JSON.stringify(scheduleData, null, 2)}`);
setSchedule(scheduleData);
} else {
console.error('No recording id provided');
}
}
useEffect(() => {
if (isOpen) {
const fetchSchedule = async () => {
await getRobotSchedule();
};
fetchSchedule();
}
}, [isOpen]);
return ( return (
<GenericModal <GenericModal
@@ -79,79 +132,117 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
'& > *': { marginBottom: '20px' }, '& > *': { marginBottom: '20px' },
}}> }}>
<Typography variant="h6" sx={{ marginBottom: '20px' }}>Schedule Settings</Typography> <Typography variant="h6" sx={{ marginBottom: '20px' }}>Schedule Settings</Typography>
<>
{
(schedule !== null) ? (
<>
<Typography>Robot is scheduled to run every {schedule.runEvery} {schedule.runEveryUnit} starting from {schedule.startFrom} at {schedule.atTimeStart} to {schedule.atTimeEnd} in {schedule.timezone} timezone.</Typography>
<Box mt={2} display="flex" justifyContent="space-between">
<Button
onClick={deleteRobotSchedule}
variant="contained"
color="primary"
>
Delete Schedule
</Button>
</Box>
</>
) : (
<>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Typography sx={{ marginRight: '10px' }}>Run once every</Typography>
<TextField
type="number"
value={settings.runEvery}
onChange={(e) => handleChange('runEvery', parseInt(e.target.value))}
sx={textStyle}
inputProps={{ min: 1 }}
/>
<Dropdown
label=""
id="runEveryUnit"
value={settings.runEveryUnit}
handleSelect={(e) => handleChange('runEveryUnit', e.target.value)}
sx={dropDownStyle}
>
{units.map((unit) => (
<MenuItem key={unit} value={unit}>{unit}</MenuItem>
))}
</Dropdown>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}> <Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Typography sx={{ marginRight: '10px' }}>Run once every</Typography> <Typography sx={{ marginBottom: '5px', marginRight: '25px' }}>Start from / On</Typography>
<TextField <Dropdown
type="number" label=""
value={settings.runEvery} id="startFrom"
onChange={(e) => handleChange('runEvery', parseInt(e.target.value))} value={settings.startFrom}
sx={textStyle} handleSelect={(e) => handleChange('startFrom', e.target.value)}
inputProps={{ min: 1 }} sx={dropDownStyle}
/> >
<Dropdown {days.map((day) => (
label="" <MenuItem key={day} value={day}>{day}</MenuItem>
id="runEveryUnit" ))}
value={settings.runEveryUnit} </Dropdown>
handleSelect={(e) => handleChange('runEveryUnit', e.target.value)} </Box>
sx={dropDownStyle}
>
{units.map((unit) => (
<MenuItem key={unit} value={unit}>{unit}</MenuItem>
))}
</Dropdown>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}> {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? (
<Typography sx={{ marginBottom: '5px', marginRight: '25px' }}>Start from / On</Typography> <Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Dropdown <Box sx={{ marginRight: '20px' }}>
label="" <Typography sx={{ marginBottom: '5px' }}>In Between</Typography>
id="startFrom" <TextField
value={settings.startFrom} type="time"
handleSelect={(e) => handleChange('startFrom', e.target.value)} value={settings.atTimeStart}
sx={dropDownStyle} onChange={(e) => handleChange('atTimeStart', e.target.value)}
> sx={textStyle}
{days.map((day) => ( />
<MenuItem key={day} value={day}>{day}</MenuItem> <TextField
))} type="time"
</Dropdown> value={settings.atTimeEnd}
</Box> onChange={(e) => handleChange('atTimeEnd', e.target.value)}
sx={textStyle}
/>
</Box>
</Box>
) : (
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Typography sx={{ marginBottom: '5px', marginRight: '10px' }}>At Around</Typography>
<TextField
type="time"
value={settings.atTimeStart}
onChange={(e) => handleChange('atTimeStart', e.target.value)}
sx={textStyle}
/>
</Box>
)}
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}> <Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Box sx={{ marginRight: '20px' }}> <Typography sx={{ marginRight: '10px' }}>Timezone</Typography>
<Typography sx={{ marginBottom: '5px' }}>At around</Typography> <Dropdown
<TextField label=""
type="time" id="timezone"
value={settings.atTime} value={settings.timezone}
onChange={(e) => handleChange('atTime', e.target.value)} handleSelect={(e) => handleChange('timezone', e.target.value)}
sx={textStyle} sx={dropDownStyle}
/> >
</Box> {validMomentTimezones.map((tz) => (
<Box> <MenuItem key={tz} value={tz}>{tz}</MenuItem>
<Typography sx={{ marginBottom: '5px' }}>Timezone</Typography> ))}
<Dropdown </Dropdown>
label="" </Box>
id="timezone" <Box mt={2} display="flex" justifyContent="flex-end">
value={settings.timezone} <Button onClick={() => handleStart(settings)} variant="contained" color="primary">
handleSelect={(e) => handleChange('timezone', e.target.value)} Save Schedule
sx={dropDownStyle} </Button>
> <Button onClick={handleClose} color="primary" variant="outlined" style={{ marginLeft: '10px' }}>
{validMomentTimezones.map((tz) => ( Cancel
<MenuItem key={tz} value={tz}>{tz}</MenuItem> </Button>
))} </Box>
</Dropdown> </>
</Box> )
</Box> }
</>
<Button
variant="contained"
onClick={() => handleStart(settings)}
>
Save
</Button>
</Box> </Box>
</GenericModal> </GenericModal>
); );
} };
export default ScheduleSettingsModal;