Merge pull request #82 from amhsirak/develop
feat: more scheduler options
This commit is contained in:
@@ -30,6 +30,8 @@ export class RemoteBrowser {
|
||||
*/
|
||||
private browser: Browser | null = null;
|
||||
|
||||
private context: BrowserContext | null = null;
|
||||
|
||||
/**
|
||||
* The Playwright's [CDPSession](https://playwright.dev/docs/api/class-cdpsession) instance,
|
||||
* used to talk raw Chrome Devtools Protocol.
|
||||
@@ -90,13 +92,13 @@ export class RemoteBrowser {
|
||||
*/
|
||||
public initialize = async (options: RemoteBrowserOptions): Promise<void> => {
|
||||
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 },
|
||||
// recordVideo: { dir: 'videos/' }
|
||||
}
|
||||
);
|
||||
this.currentPage = await context.newPage();
|
||||
this.currentPage = await this.context.newPage();
|
||||
const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch);
|
||||
await blocker.enableBlockingInPage(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`)
|
||||
}
|
||||
});
|
||||
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) {
|
||||
await this.stopScreencast();
|
||||
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.socket.emit('urlChanged', this.currentPage.url());
|
||||
await this.makeAndEmitScreenshot();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Model, DataTypes, Optional } from 'sequelize';
|
||||
import sequelize from '../storage/db';
|
||||
import { WorkflowFile, Where, What, WhereWhatPair } from 'maxun-core';
|
||||
import User from './User'; // Import User model
|
||||
import Run from './Run';
|
||||
|
||||
interface RobotMeta {
|
||||
name: string;
|
||||
@@ -27,6 +25,19 @@ interface RobotAttributes {
|
||||
google_sheet_id?: string | null;
|
||||
google_access_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'> { }
|
||||
@@ -41,6 +52,7 @@ class Robot extends Model<RobotAttributes, RobotCreationAttributes> implements R
|
||||
public google_sheet_id?: string | null;
|
||||
public google_access_token!: string | null;
|
||||
public google_refresh_token!: string | null;
|
||||
public schedule!: ScheduleConfig | null;
|
||||
}
|
||||
|
||||
Robot.init(
|
||||
@@ -82,6 +94,10 @@ Robot.init(
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true,
|
||||
},
|
||||
schedule: {
|
||||
type: DataTypes.JSONB,
|
||||
allowNull: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
sequelize,
|
||||
|
||||
@@ -12,7 +12,7 @@ import { requireSignIn } from '../middlewares/auth';
|
||||
import Robot from '../models/Robot';
|
||||
import Run from '../models/Run';
|
||||
import { BinaryOutputService } from '../storage/mino';
|
||||
// import { workflowQueue } from '../worker';
|
||||
import { workflowQueue } from '../worker';
|
||||
|
||||
export const router = Router();
|
||||
|
||||
@@ -46,7 +46,7 @@ router.get('/recordings/:id', requireSignIn, async (req, res) => {
|
||||
where: { 'recording_meta.id': req.params.id },
|
||||
raw: true
|
||||
}
|
||||
);
|
||||
);
|
||||
return res.send(data);
|
||||
} catch (e) {
|
||||
logger.log('info', 'Error while reading recordings');
|
||||
@@ -208,8 +208,8 @@ router.post('/runs/run/:id', requireSignIn, async (req, res) => {
|
||||
if (browser && currentPage) {
|
||||
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
||||
recording.recording, currentPage, plainRun.interpreterSettings);
|
||||
const binaryOutputService = new BinaryOutputService('maxun-run-screenshots');
|
||||
const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput);
|
||||
const binaryOutputService = new BinaryOutputService('maxun-run-screenshots');
|
||||
const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput);
|
||||
await destroyRemoteBrowser(plainRun.browserId);
|
||||
await run.update({
|
||||
...run,
|
||||
@@ -247,18 +247,45 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const {
|
||||
// enabled = true,
|
||||
runEvery,
|
||||
runEveryUnit,
|
||||
startFrom,
|
||||
atTime,
|
||||
atTimeStart,
|
||||
atTimeEnd,
|
||||
timezone
|
||||
} = 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' });
|
||||
}
|
||||
|
||||
if (!['HOURS', 'DAYS', 'WEEKS', 'MONTHS'].includes(runEveryUnit)) {
|
||||
if (!['HOURS', 'DAYS', 'WEEKS', 'MONTHS', 'MINUTES'].includes(runEveryUnit)) {
|
||||
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' });
|
||||
}
|
||||
|
||||
const [hours, minutes] = atTime.split(':').map(Number);
|
||||
if (isNaN(hours) || isNaN(minutes) || hours < 0 || hours > 23 || minutes < 0 || minutes > 59) {
|
||||
const [startHours, startMinutes] = atTimeStart.split(':').map(Number);
|
||||
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' });
|
||||
}
|
||||
|
||||
@@ -278,18 +309,19 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
|
||||
|
||||
let cronExpression;
|
||||
switch (runEveryUnit) {
|
||||
case 'MINUTES':
|
||||
case 'HOURS':
|
||||
cronExpression = `${minutes} */${runEvery} * * *`;
|
||||
cronExpression = `${startMinutes}-${endMinutes} */${runEvery} * * *`;
|
||||
break;
|
||||
case 'DAYS':
|
||||
cronExpression = `${minutes} ${hours} */${runEvery} * *`;
|
||||
cronExpression = `${startMinutes} ${startHours} */${runEvery} * *`;
|
||||
break;
|
||||
case 'WEEKS':
|
||||
const dayIndex = days.indexOf(startFrom);
|
||||
cronExpression = `${minutes} ${hours} * * ${dayIndex}/${7 * runEvery}`;
|
||||
cronExpression = `${startMinutes} ${startHours} * * ${dayIndex}/${7 * runEvery}`;
|
||||
break;
|
||||
case 'MONTHS':
|
||||
cronExpression = `${minutes} ${hours} 1-7 */${runEvery} *`;
|
||||
cronExpression = `${startMinutes} ${startHours} 1-7 */${runEvery} *`;
|
||||
if (startFrom !== 'SUNDAY') {
|
||||
const dayIndex = days.indexOf(startFrom);
|
||||
cronExpression += ` ${dayIndex}`;
|
||||
@@ -304,22 +336,50 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
|
||||
const runId = uuid();
|
||||
const userId = req.user.id;
|
||||
|
||||
// await workflowQueue.add(
|
||||
// 'run workflow',
|
||||
// { id, runId, userId },
|
||||
// {
|
||||
// repeat: {
|
||||
// pattern: cronExpression,
|
||||
// tz: timezone
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
// Remove existing jobs for this robot just in case some were left
|
||||
// const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']);
|
||||
// for (const job of existingJobs) {
|
||||
// if (job.data.id === id) {
|
||||
// await job.remove();
|
||||
// }
|
||||
// }
|
||||
|
||||
// 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({
|
||||
message: 'success',
|
||||
runId,
|
||||
// cronExpression,
|
||||
// nextRunTime: getNextRunTime(cronExpression, timezone)
|
||||
robot: updatedRobot
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
@@ -328,12 +388,55 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// function getNextRunTime(cronExpression, timezone) {
|
||||
// const schedule = cron.schedule(cronExpression, () => {}, { timezone });
|
||||
// const nextDate = schedule.nextDate();
|
||||
// schedule.stop();
|
||||
// return nextDate.toDate();
|
||||
// }
|
||||
// Endpoint to get schedule details
|
||||
router.get('/schedule/:id', requireSignIn, async (req, res) => {
|
||||
try {
|
||||
const robot = await Robot.findOne({ where: { 'recording_meta.id': req.params.id }, raw: true });
|
||||
|
||||
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.
|
||||
|
||||
@@ -58,16 +58,19 @@ readdirSync(path.join(__dirname, 'api')).forEach((r) => {
|
||||
}
|
||||
});
|
||||
|
||||
const workerProcess = fork(path.resolve(__dirname, './worker.ts'));
|
||||
workerProcess.on('message', (message) => {
|
||||
console.log(`Message from worker: ${message}`);
|
||||
});
|
||||
const workerProcess = fork(path.resolve(__dirname, './worker.ts'), [], {
|
||||
execArgv: ['--inspect=5859'], // Specify a different debug port for the worker
|
||||
});
|
||||
|
||||
workerProcess.on('message', (message) => {
|
||||
console.log(`Message from worker: ${message}`);
|
||||
});
|
||||
workerProcess.on('error', (error) => {
|
||||
console.error(`Error in worker: ${error}`);
|
||||
});
|
||||
workerProcess.on('exit', (code) => {
|
||||
console.error(`Error in worker: ${error}`);
|
||||
});
|
||||
workerProcess.on('exit', (code) => {
|
||||
console.log(`Worker exited with code: ${code}`);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/', function (req, res) {
|
||||
return res.send('Maxun server started 🚀');
|
||||
@@ -81,6 +84,6 @@ server.listen(SERVER_PORT, async () => {
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('Main app shutting down...');
|
||||
//workerProcess.kill();
|
||||
workerProcess.kill();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
@@ -52,4 +52,8 @@ process.on('SIGINT', () => {
|
||||
process.exit();
|
||||
});
|
||||
|
||||
export { workflowQueue, worker };
|
||||
export { workflowQueue, worker };
|
||||
|
||||
export const temp = () => {
|
||||
console.log('temp');
|
||||
}
|
||||
@@ -12,7 +12,7 @@ export const getStoredRecordings = async (): Promise<string[] | null> => {
|
||||
} else {
|
||||
throw new Error('Couldn\'t retrieve stored recordings');
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return null;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ export const getStoredRuns = async (): Promise<string[] | null> => {
|
||||
} else {
|
||||
throw new Error('Couldn\'t retrieve stored recordings');
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return null;
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const getStoredRecording = async (id: string) => {
|
||||
} else {
|
||||
throw new Error(`Couldn't retrieve stored recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return null;
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export const deleteRecordingFromStorage = async (id: string): Promise<boolean> =
|
||||
} else {
|
||||
throw new Error(`Couldn't delete stored recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
@@ -68,7 +68,7 @@ export const deleteRunFromStorage = async (id: string): Promise<boolean> => {
|
||||
} else {
|
||||
throw new Error(`Couldn't delete stored recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ export const editRecordingFromStorage = async (browserId: string, id: string): P
|
||||
} else {
|
||||
throw new Error(`Couldn't edit stored recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return null;
|
||||
}
|
||||
@@ -92,15 +92,15 @@ export const createRunForStoredRecording = async (id: string, settings: RunSetti
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`http://localhost:8080/storage/runs/${id}`,
|
||||
{...settings});
|
||||
{ ...settings });
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(`Couldn't create a run for a recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return {browserId: '', runId: ''};
|
||||
return { browserId: '', runId: '' };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,13 +112,13 @@ export const interpretStoredRecording = async (id: string): Promise<boolean> =>
|
||||
} else {
|
||||
throw new Error(`Couldn't run a recording ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const notifyAboutAbort = async (id:string): Promise<boolean> => {
|
||||
export const notifyAboutAbort = async (id: string): Promise<boolean> => {
|
||||
try {
|
||||
const response = await axios.post(`http://localhost:8080/storage/runs/abort/${id}`);
|
||||
if (response.status === 200) {
|
||||
@@ -126,7 +126,7 @@ export const notifyAboutAbort = async (id:string): Promise<boolean> => {
|
||||
} else {
|
||||
throw new Error(`Couldn't abort a running recording with id ${id}`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
console.log(error);
|
||||
return false;
|
||||
}
|
||||
@@ -136,14 +136,42 @@ export const scheduleStoredRecording = async (id: string, settings: ScheduleSett
|
||||
try {
|
||||
const response = await axios.put(
|
||||
`http://localhost:8080/storage/schedule/${id}`,
|
||||
{...settings});
|
||||
{ ...settings });
|
||||
if (response.status === 200) {
|
||||
return response.data;
|
||||
} else {
|
||||
throw new Error(`Couldn't schedule recording ${id}. Please try again later.`);
|
||||
}
|
||||
} catch(error: any) {
|
||||
} catch (error: any) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,51 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 Button from "@mui/material/Button";
|
||||
import { modalStyle } from "./AddWhereCondModal";
|
||||
import { validMomentTimezones } from '../../constants/const';
|
||||
import { useGlobalInfoStore } from '../../context/globalInfo';
|
||||
import { getSchedule, deleteSchedule } from '../../api/storage';
|
||||
|
||||
interface ScheduleSettingsProps {
|
||||
isOpen: boolean;
|
||||
handleStart: (settings: ScheduleSettings) => void;
|
||||
handleClose: () => void;
|
||||
initialSettings?: ScheduleSettings | null;
|
||||
}
|
||||
|
||||
export interface ScheduleSettings {
|
||||
runEvery: number;
|
||||
runEveryUnit: string;
|
||||
startFrom: string;
|
||||
atTime: string;
|
||||
atTimeStart?: string;
|
||||
atTimeEnd?: 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>({
|
||||
runEvery: 1,
|
||||
runEveryUnit: 'HOURS',
|
||||
startFrom: 'MONDAY',
|
||||
atTime: '00:00',
|
||||
atTimeStart: '00:00',
|
||||
atTimeEnd: '01:00',
|
||||
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 }));
|
||||
};
|
||||
|
||||
console.log(`Settings:`, settings);
|
||||
|
||||
const textStyle = {
|
||||
width: '150px',
|
||||
height: '52px',
|
||||
@@ -49,11 +60,12 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
|
||||
};
|
||||
|
||||
const units = [
|
||||
'MINUTES',
|
||||
'HOURS',
|
||||
'DAYS',
|
||||
'WEEKS',
|
||||
'MONTHS'
|
||||
]
|
||||
];
|
||||
|
||||
const days = [
|
||||
'MONDAY',
|
||||
@@ -63,7 +75,48 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
|
||||
'FRIDAY',
|
||||
'SATURDAY',
|
||||
'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 (
|
||||
<GenericModal
|
||||
@@ -79,79 +132,117 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche
|
||||
'& > *': { marginBottom: '20px' },
|
||||
}}>
|
||||
<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%' }}>
|
||||
<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%' }}>
|
||||
<Typography sx={{ marginBottom: '5px', marginRight: '25px' }}>Start from / On</Typography>
|
||||
<Dropdown
|
||||
label=""
|
||||
id="startFrom"
|
||||
value={settings.startFrom}
|
||||
handleSelect={(e) => handleChange('startFrom', e.target.value)}
|
||||
sx={dropDownStyle}
|
||||
>
|
||||
{days.map((day) => (
|
||||
<MenuItem key={day} value={day}>{day}</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Typography sx={{ marginBottom: '5px', marginRight: '25px' }}>Start from / On</Typography>
|
||||
<Dropdown
|
||||
label=""
|
||||
id="startFrom"
|
||||
value={settings.startFrom}
|
||||
handleSelect={(e) => handleChange('startFrom', e.target.value)}
|
||||
sx={dropDownStyle}
|
||||
>
|
||||
{days.map((day) => (
|
||||
<MenuItem key={day} value={day}>{day}</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Box>
|
||||
{['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Box sx={{ marginRight: '20px' }}>
|
||||
<Typography sx={{ marginBottom: '5px' }}>In Between</Typography>
|
||||
<TextField
|
||||
type="time"
|
||||
value={settings.atTimeStart}
|
||||
onChange={(e) => handleChange('atTimeStart', e.target.value)}
|
||||
sx={textStyle}
|
||||
/>
|
||||
<TextField
|
||||
type="time"
|
||||
value={settings.atTimeEnd}
|
||||
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={{ marginRight: '20px' }}>
|
||||
<Typography sx={{ marginBottom: '5px' }}>At around</Typography>
|
||||
<TextField
|
||||
type="time"
|
||||
value={settings.atTime}
|
||||
onChange={(e) => handleChange('atTime', e.target.value)}
|
||||
sx={textStyle}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography sx={{ marginBottom: '5px' }}>Timezone</Typography>
|
||||
<Dropdown
|
||||
label=""
|
||||
id="timezone"
|
||||
value={settings.timezone}
|
||||
handleSelect={(e) => handleChange('timezone', e.target.value)}
|
||||
sx={dropDownStyle}
|
||||
>
|
||||
{validMomentTimezones.map((tz) => (
|
||||
<MenuItem key={tz} value={tz}>{tz}</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => handleStart(settings)}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
|
||||
<Typography sx={{ marginRight: '10px' }}>Timezone</Typography>
|
||||
<Dropdown
|
||||
label=""
|
||||
id="timezone"
|
||||
value={settings.timezone}
|
||||
handleSelect={(e) => handleChange('timezone', e.target.value)}
|
||||
sx={dropDownStyle}
|
||||
>
|
||||
{validMomentTimezones.map((tz) => (
|
||||
<MenuItem key={tz} value={tz}>{tz}</MenuItem>
|
||||
))}
|
||||
</Dropdown>
|
||||
</Box>
|
||||
<Box mt={2} display="flex" justifyContent="flex-end">
|
||||
<Button onClick={() => handleStart(settings)} variant="contained" color="primary">
|
||||
Save Schedule
|
||||
</Button>
|
||||
<Button onClick={handleClose} color="primary" variant="outlined" style={{ marginLeft: '10px' }}>
|
||||
Cancel
|
||||
</Button>
|
||||
</Box>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</>
|
||||
</Box>
|
||||
</GenericModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default ScheduleSettingsModal;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user