From f35b8a7d609f94666a32bccf2dfed332e618978b Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:43:28 +0530 Subject: [PATCH 01/48] feat:dynamic viewport for browser context --- .../classes/RemoteBrowser.ts | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 03ed5e41..287d1a0a 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -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 => { this.browser = (await options.browser.launch(options.launchOptions)); - const context = await this.browser.newContext( - { - viewport: { height: 400, width: 900 }, - // recordVideo: { dir: 'videos/' } - } + 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`); + } + }); } /** From a016ec6322b86a4fd1ada4bdc61f29f97fe9c287 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:44:09 +0530 Subject: [PATCH 02/48] feat: context docs --- server/src/browser-management/classes/RemoteBrowser.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 287d1a0a..555c7d4e 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -30,6 +30,10 @@ export class RemoteBrowser { */ private browser: Browser | null = null; + /** + * Playwright's [browser context](https://playwright.dev/docs/api/class-browsercontext) instance. + * @private + */ private context: BrowserContext | null = null; /** From a6a0342c1a89feda9e96c8171ca02e16e0f90625 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:44:57 +0530 Subject: [PATCH 03/48] feat: viewport size property --- server/src/browser-management/classes/RemoteBrowser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 555c7d4e..61b543ac 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -36,6 +36,8 @@ export class RemoteBrowser { */ private context: BrowserContext | null = null; + private viewportSize: { width: number, height: number } = { width: 900, height: 400 }; + /** * The Playwright's [CDPSession](https://playwright.dev/docs/api/class-cdpsession) instance, * used to talk raw Chrome Devtools Protocol. From 39501bb8aabe926d6f84dd96d3d6553552b5e155 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:45:34 +0530 Subject: [PATCH 04/48] feat: viewport docs docs --- server/src/browser-management/classes/RemoteBrowser.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 61b543ac..305f4a62 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -36,6 +36,10 @@ export class RemoteBrowser { */ private context: BrowserContext | null = null; + /** + * The viewport size of the browser. + * @private + */ private viewportSize: { width: number, height: number } = { width: 900, height: 400 }; /** From b8946e29138777320b84f8993f485820a6566633 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:46:35 +0530 Subject: [PATCH 05/48] feat: update viewport property --- server/src/browser-management/classes/RemoteBrowser.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 305f4a62..60c43199 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -153,6 +153,8 @@ export class RemoteBrowser { this.socket.on('setViewportSize', async (data: { width: number, height: number }) => { const { width, height } = data; logger.log('debug', `Received viewport size: width=${width}, height=${height}`); + + this.viewportSize = { width, height }; // Update the browser context's viewport dynamically if (this.context && this.browser) { From 2e3da36f51d7e6849d8208c4e5651546418817b5 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 02:47:30 +0530 Subject: [PATCH 06/48] feat: use private viewportSize property --- server/src/browser-management/classes/RemoteBrowser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index 60c43199..cd0f736e 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -304,7 +304,7 @@ export class RemoteBrowser { if (page) { await this.stopScreencast(); this.currentPage = page; - await this.currentPage.setViewportSize({ height: 400, width: 900 }) + await this.currentPage.setViewportSize(this.viewportSize); this.client = await this.currentPage.context().newCDPSession(this.currentPage); this.socket.emit('urlChanged', this.currentPage.url()); await this.makeAndEmitScreenshot(); From 43eaf01137fb3be91abf241682cbe9db77c58d56 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 15:23:57 +0530 Subject: [PATCH 07/48] chore: revert changes --- .../classes/RemoteBrowser.ts | 22 +++++-------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index cd0f736e..e4a305fd 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -30,18 +30,8 @@ export class RemoteBrowser { */ private browser: Browser | null = null; - /** - * Playwright's [browser context](https://playwright.dev/docs/api/class-browsercontext) instance. - * @private - */ private context: BrowserContext | null = null; - /** - * The viewport size of the browser. - * @private - */ - private viewportSize: { width: number, height: number } = { width: 900, height: 400 }; - /** * The Playwright's [CDPSession](https://playwright.dev/docs/api/class-cdpsession) instance, * used to talk raw Chrome Devtools Protocol. @@ -103,10 +93,10 @@ export class RemoteBrowser { public initialize = async (options: RemoteBrowserOptions): Promise => { this.browser = (await options.browser.launch(options.launchOptions)); this.context = await this.browser.newContext( - // { - // viewport: { height: 400, width: 900 }, - // // recordVideo: { dir: 'videos/' } - // } + { + viewport: { height: 400, width: 900 }, + // recordVideo: { dir: 'videos/' } + } ); this.currentPage = await this.context.newPage(); const blocker = await PlaywrightBlocker.fromPrebuiltAdsAndTracking(fetch); @@ -153,8 +143,6 @@ export class RemoteBrowser { this.socket.on('setViewportSize', async (data: { width: number, height: number }) => { const { width, height } = data; logger.log('debug', `Received viewport size: width=${width}, height=${height}`); - - this.viewportSize = { width, height }; // Update the browser context's viewport dynamically if (this.context && this.browser) { @@ -304,7 +292,7 @@ export class RemoteBrowser { if (page) { await this.stopScreencast(); this.currentPage = page; - await this.currentPage.setViewportSize(this.viewportSize); + //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(); From 0c08d27a607bc0bcf3b9cd16d8bea059755f484e Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 16:57:19 +0530 Subject: [PATCH 08/48] fix: uncomment workflowQueue code --- server/src/routes/storage.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index c684d6ea..e5135fa3 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -304,16 +304,16 @@ 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 - // } - // } - // ); + await workflowQueue.add( + 'run workflow', + { id, runId, userId }, + { + repeat: { + pattern: cronExpression, + tz: timezone + } + } + ); res.status(200).json({ message: 'success', From 02f9e949284d27af2972822be4015e01dafc498c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 16:57:33 +0530 Subject: [PATCH 09/48] fix: uncomment workflowQueue code --- server/src/routes/storage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index e5135fa3..e98fb99a 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -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(); From 0af80adf93b99600bfc8967fed5c0c1c969e3694 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 16:58:48 +0530 Subject: [PATCH 10/48] fix: uncomment workflowQueue code --- server/src/worker.ts | 76 +++++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/server/src/worker.ts b/server/src/worker.ts index baef2387..12473fc2 100644 --- a/server/src/worker.ts +++ b/server/src/worker.ts @@ -3,53 +3,57 @@ import IORedis from 'ioredis'; import logger from './logger'; import { handleRunRecording } from "./workflow-management/scheduler"; -const connection = new IORedis({ - host: 'localhost', - port: 6379, - maxRetriesPerRequest: null, + const connection = new IORedis({ + host: 'localhost', + port: 6379, + maxRetriesPerRequest: null, + }); + + connection.on('connect', () => { + console.log('Connected to Redis!'); }); -connection.on('connect', () => { - console.log('Connected to Redis!'); -}); - -connection.on('error', (err) => { - console.error('Redis connection error:', err); -}); + connection.on('error', (err) => { + console.error('Redis connection error:', err); + }); const workflowQueue = new Queue('workflow', { connection }); -const worker = new Worker('workflow', async job => { - const { runId, userId } = job.data; - try { - const result = await handleRunRecording(runId, userId); + const worker = new Worker('workflow', async job => { + const { runId, userId } = job.data; + try { + const result = await handleRunRecording(runId, userId); return result; } catch (error) { - logger.error('Error running workflow:', error); - throw error; - } -}, { connection }); + logger.error('Error running workflow:', error); + throw error; + } + }, { connection }); -worker.on('completed', async (job: any) => { - logger.log(`info`, `Job ${job.id} completed for ${job.data.runId}`); -}); + worker.on('completed', async (job: any) => { + logger.log(`info`, `Job ${job.id} completed for ${job.data.runId}`); + }); -worker.on('failed', async (job: any, err) => { - logger.log(`error`, `Job ${job.id} failed for ${job.data.runId}:`, err); -}); + worker.on('failed', async (job: any, err) => { + logger.log(`error`, `Job ${job.id} failed for ${job.data.runId}:`, err); + }); -console.log('Worker is running...'); + console.log('Worker is running...'); -async function jobCounts() { - const jobCounts = await workflowQueue.getJobCounts(); - console.log('Jobs:', jobCounts); -} + async function jobCounts() { + const jobCounts = await workflowQueue.getJobCounts(); + console.log('Jobs:', jobCounts); + } -jobCounts(); + jobCounts(); -process.on('SIGINT', () => { - console.log('Worker shutting down...'); - process.exit(); -}); + process.on('SIGINT', () => { + console.log('Worker shutting down...'); + process.exit(); + }); -export { workflowQueue, worker }; \ No newline at end of file + export { workflowQueue, worker }; + +export const temp = () => { + console.log('temp'); +} \ No newline at end of file From aa798702cdc9e0463dd5e46befd9708004352906 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 16:59:04 +0530 Subject: [PATCH 11/48] chore: lint --- server/src/worker.ts | 72 ++++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/server/src/worker.ts b/server/src/worker.ts index 12473fc2..f471470f 100644 --- a/server/src/worker.ts +++ b/server/src/worker.ts @@ -3,56 +3,56 @@ import IORedis from 'ioredis'; import logger from './logger'; import { handleRunRecording } from "./workflow-management/scheduler"; - const connection = new IORedis({ - host: 'localhost', - port: 6379, - maxRetriesPerRequest: null, - }); - - connection.on('connect', () => { - console.log('Connected to Redis!'); +const connection = new IORedis({ + host: 'localhost', + port: 6379, + maxRetriesPerRequest: null, }); - connection.on('error', (err) => { - console.error('Redis connection error:', err); - }); +connection.on('connect', () => { + console.log('Connected to Redis!'); +}); + +connection.on('error', (err) => { + console.error('Redis connection error:', err); +}); const workflowQueue = new Queue('workflow', { connection }); - const worker = new Worker('workflow', async job => { - const { runId, userId } = job.data; - try { - const result = await handleRunRecording(runId, userId); +const worker = new Worker('workflow', async job => { + const { runId, userId } = job.data; + try { + const result = await handleRunRecording(runId, userId); return result; } catch (error) { - logger.error('Error running workflow:', error); - throw error; - } - }, { connection }); + logger.error('Error running workflow:', error); + throw error; + } +}, { connection }); - worker.on('completed', async (job: any) => { - logger.log(`info`, `Job ${job.id} completed for ${job.data.runId}`); - }); +worker.on('completed', async (job: any) => { + logger.log(`info`, `Job ${job.id} completed for ${job.data.runId}`); +}); - worker.on('failed', async (job: any, err) => { - logger.log(`error`, `Job ${job.id} failed for ${job.data.runId}:`, err); - }); +worker.on('failed', async (job: any, err) => { + logger.log(`error`, `Job ${job.id} failed for ${job.data.runId}:`, err); +}); - console.log('Worker is running...'); +console.log('Worker is running...'); - async function jobCounts() { - const jobCounts = await workflowQueue.getJobCounts(); - console.log('Jobs:', jobCounts); - } +async function jobCounts() { + const jobCounts = await workflowQueue.getJobCounts(); + console.log('Jobs:', jobCounts); +} - jobCounts(); +jobCounts(); - process.on('SIGINT', () => { - console.log('Worker shutting down...'); - process.exit(); - }); +process.on('SIGINT', () => { + console.log('Worker shutting down...'); + process.exit(); +}); - export { workflowQueue, worker }; +export { workflowQueue, worker }; export const temp = () => { console.log('temp'); From c022d45b603d461b6ea6dbe758baf25fd431b965 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 16:59:18 +0530 Subject: [PATCH 12/48] chore: lint --- server/src/routes/storage.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index e98fb99a..52aa0306 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -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, @@ -304,16 +304,16 @@ 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 - } + await workflowQueue.add( + 'run workflow', + { id, runId, userId }, + { + repeat: { + pattern: cronExpression, + tz: timezone } - ); + } + ); res.status(200).json({ message: 'success', From 3675920d5d46341efcbb288b085d241a1da183ed Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 17:26:22 +0530 Subject: [PATCH 13/48] chore: remove unsued import --- server/src/models/Robot.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index d2dbca67..79afed38 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -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; From a368ea2cd05d00f285db6bccca4660465463bc9c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:25:32 +0530 Subject: [PATCH 14/48] feat: modify to handle minutes --- src/components/molecules/ScheduleSettings.tsx | 77 ++++++++++++------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index ef300a91..04c93f82 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -16,7 +16,8 @@ export interface ScheduleSettings { runEvery: number; runEveryUnit: string; startFrom: string; - atTime: string; + atTimeStart?: string; // Start time for "In Between" + atTimeEnd?: string; // End time for "In Between" timezone: string; } @@ -25,7 +26,8 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche runEvery: 1, runEveryUnit: 'HOURS', startFrom: 'MONDAY', - atTime: '00:00', + atTimeStart: '00:00', + atTimeEnd: '01:00', // Default end time timezone: 'UTC' }); @@ -33,8 +35,6 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche setSettings(prev => ({ ...prev, [field]: value })); }; - console.log(`Settings:`, settings); - const textStyle = { width: '150px', height: '52px', @@ -49,11 +49,12 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche }; const units = [ + 'MINUTES', 'HOURS', 'DAYS', 'WEEKS', 'MONTHS' - ] + ]; const days = [ 'MONDAY', @@ -63,7 +64,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche 'FRIDAY', 'SATURDAY', 'SUNDAY' - ] + ]; return ( - - - At around + {/* Conditional rendering based on the selected runEveryUnit */} + {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( + + + In Between + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + handleChange('atTimeEnd', e.target.value)} + sx={textStyle} + /> + + + ) : ( + + At Around handleChange('atTime', e.target.value)} + value={settings.atTimeStart} + onChange={(e) => handleChange('atTimeStart', e.target.value)} sx={textStyle} /> - - Timezone - handleChange('timezone', e.target.value)} - sx={dropDownStyle} - > - {validMomentTimezones.map((tz) => ( - {tz} - ))} - - + )} + + + Timezone + handleChange('timezone', e.target.value)} + sx={dropDownStyle} + > + {validMomentTimezones.map((tz) => ( + {tz} + ))} + ); -} - -export default ScheduleSettingsModal; \ No newline at end of file +}; From 0c45a62d6c0776c7c03ccd3ba66eeb9c32182cb2 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:26:28 +0530 Subject: [PATCH 15/48] feat: modify to handle minutes --- server/src/routes/storage.ts | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 52aa0306..fc05b8af 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -250,15 +250,16 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { runEvery, runEveryUnit, startFrom, - atTime, + atTimeStart, + atTimeEnd, timezone } = req.body; - if (!id || !runEvery || !runEveryUnit || !startFrom || !atTime || !timezone) { + 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 +267,13 @@ 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); + + // Validate the time format for In Between + 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 +284,21 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { let cronExpression; switch (runEveryUnit) { + case 'MINUTES': + cronExpression = `${startMinutes}-${endMinutes} */${runEvery} * * *`; + break; 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}`; @@ -318,8 +327,6 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { res.status(200).json({ message: 'success', runId, - // cronExpression, - // nextRunTime: getNextRunTime(cronExpression, timezone) }); } catch (error) { @@ -328,6 +335,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } }); + // function getNextRunTime(cronExpression, timezone) { // const schedule = cron.schedule(cronExpression, () => {}, { timezone }); // const nextDate = schedule.nextDate(); From c413b8eceda0058c71e681fa0754ed1512f8881a Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:26:58 +0530 Subject: [PATCH 16/48] feat: uncomment worker process --- server/src/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index eb435345..24c044b1 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -62,7 +62,7 @@ const workerProcess = fork(path.resolve(__dirname, './worker.ts')); workerProcess.on('message', (message) => { console.log(`Message from worker: ${message}`); }); -workerProcess.on('error', (error) => { + workerProcess.on('error', (error) => { console.error(`Error in worker: ${error}`); }); workerProcess.on('exit', (code) => { @@ -81,6 +81,6 @@ server.listen(SERVER_PORT, async () => { process.on('SIGINT', () => { console.log('Main app shutting down...'); - //workerProcess.kill(); + workerProcess.kill(); process.exit(); }); From 44ecb8018c25ebef49f2c3209bdde149b33c541d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:32:27 +0530 Subject: [PATCH 17/48] feat: schedule config interface --- server/src/models/Robot.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index 79afed38..c19ed19a 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -27,6 +27,19 @@ interface RobotAttributes { google_refresh_token?: string | null; } +interface ScheduleConfig { + enabled: boolean; + 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 { } class Robot extends Model implements RobotAttributes { From da3f731341562c9dbdb92438b9b0746728e171b5 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:33:11 +0530 Subject: [PATCH 18/48] feat: include schedule config in robot attributes --- server/src/models/Robot.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index c19ed19a..c99a01f0 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -25,6 +25,7 @@ interface RobotAttributes { google_sheet_id?: string | null; google_access_token?: string | null; google_refresh_token?: string | null; + schedule?: ScheduleConfig | null; } interface ScheduleConfig { @@ -52,6 +53,7 @@ class Robot extends Model 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( From 5b0229c19931820d5337685a157ba9c62d5dd814 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 18:34:38 +0530 Subject: [PATCH 19/48] feat: include schedule config --- server/src/models/Robot.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/server/src/models/Robot.ts b/server/src/models/Robot.ts index c99a01f0..edf0fc1b 100644 --- a/server/src/models/Robot.ts +++ b/server/src/models/Robot.ts @@ -95,6 +95,10 @@ Robot.init( type: DataTypes.STRING, allowNull: true, }, + schedule: { + type: DataTypes.JSONB, + allowNull: true, + }, }, { sequelize, From 2c6e31a0d85a638fbdf972c43731e1999485fa8c Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 19:20:03 +0530 Subject: [PATCH 20/48] feat: enable disable schedule --- server/src/routes/storage.ts | 76 +++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index fc05b8af..401e169d 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -247,6 +247,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { try { const { id } = req.params; const { + enabled = true, runEvery, runEveryUnit, startFrom, @@ -255,6 +256,31 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { timezone } = req.body; + 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' }); } @@ -313,7 +339,16 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { const runId = uuid(); const userId = req.user.id; - await workflowQueue.add( + // Remove existing jobs for this robot + 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 }, { @@ -324,9 +359,29 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } ); + // Calculate next run time + const nextRun = job.timestamp; + + // Update robot with schedule details + await robot.update({ + schedule: { + enabled: true, + runEvery, + runEveryUnit, + startFrom, + atTimeStart, + atTimeEnd, + timezone, + cronExpression, + lastRunAt: undefined, + nextRunAt: new Date(nextRun) + } + }); + res.status(200).json({ message: 'success', runId, + schedule: robot.schedule }); } catch (error) { @@ -335,6 +390,25 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } }); +// Add new endpoint to get schedule status +router.get('/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' }); + } + + return res.status(200).json({ + schedule: robot.schedule || { enabled: false } + }); + + } catch (error) { + console.error('Error getting schedule:', error); + res.status(500).json({ error: 'Failed to get schedule' }); + } +}); // function getNextRunTime(cronExpression, timezone) { // const schedule = cron.schedule(cronExpression, () => {}, { timezone }); From 2f719daae56d517a2676e7fe0c2aae8137f03d68 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 19:22:31 +0530 Subject: [PATCH 21/48] feat: include schedule config --- src/components/molecules/ScheduleSettings.tsx | 220 +++++++++++------- 1 file changed, 131 insertions(+), 89 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 04c93f82..d8bc8bb9 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -1,6 +1,6 @@ -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"; @@ -10,28 +10,38 @@ interface ScheduleSettingsProps { isOpen: boolean; handleStart: (settings: ScheduleSettings) => void; handleClose: () => void; + initialSettings?: ScheduleSettings | null; } export interface ScheduleSettings { + enabled: boolean; runEvery: number; runEveryUnit: string; startFrom: string; - atTimeStart?: string; // Start time for "In Between" - atTimeEnd?: string; // End time for "In Between" + atTimeStart?: string; + atTimeEnd?: string; timezone: string; } -export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: ScheduleSettingsProps) => { +export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: ScheduleSettingsProps) => { const [settings, setSettings] = useState({ + enabled: true, runEvery: 1, runEveryUnit: 'HOURS', startFrom: 'MONDAY', atTimeStart: '00:00', - atTimeEnd: '01:00', // Default end time + 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 })); }; @@ -66,6 +76,15 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose }: Sche 'SUNDAY' ]; + const handleSubmit = () => { + // If scheduling is disabled, only send the enabled flag + if (!settings.enabled) { + handleStart({ enabled: false } as ScheduleSettings); + return; + } + handleStart(settings); + }; + return ( Schedule Settings - - Run once every - handleChange('runEvery', parseInt(e.target.value))} - sx={textStyle} - inputProps={{ min: 1 }} - /> - handleChange('runEveryUnit', e.target.value)} - sx={dropDownStyle} - > - {units.map((unit) => ( - {unit} - ))} - - - - - Start from / On - handleChange('startFrom', e.target.value)} - sx={dropDownStyle} - > - {days.map((day) => ( - {day} - ))} - - - - {/* Conditional rendering based on the selected runEveryUnit */} - {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( - - - In Between - handleChange('atTimeStart', e.target.value)} - sx={textStyle} - /> - handleChange('atTimeEnd', e.target.value)} - sx={textStyle} - /> - - - ) : ( - - At Around - handleChange('atTimeStart', e.target.value)} - sx={textStyle} + handleChange('enabled', e.target.checked)} + color="primary" /> - + } + label="Enable Scheduling" + /> + + {settings.enabled && ( + <> + + Run once every + handleChange('runEvery', parseInt(e.target.value))} + sx={textStyle} + inputProps={{ min: 1 }} + /> + handleChange('runEveryUnit', e.target.value)} + sx={dropDownStyle} + > + {units.map((unit) => ( + {unit} + ))} + + + + + Start from / On + handleChange('startFrom', e.target.value)} + sx={dropDownStyle} + > + {days.map((day) => ( + {day} + ))} + + + + {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( + + + In Between + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + handleChange('atTimeEnd', e.target.value)} + sx={textStyle} + /> + + + ) : ( + + At Around + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + + )} + + + Timezone + handleChange('timezone', e.target.value)} + sx={dropDownStyle} + > + {validMomentTimezones.map((tz) => ( + {tz} + ))} + + + )} - - Timezone - handleChange('timezone', e.target.value)} - sx={dropDownStyle} - > - {validMomentTimezones.map((tz) => ( - {tz} - ))} - - + {/* {settings.enabled && ( + + {initialSettings?.nextRunAt && ( + `Next scheduled run: ${new Date(initialSettings.nextRunAt).toLocaleString()}` + )} + + )} */} ); -}; +}; \ No newline at end of file From 55bc4147ba7a5789186841aa56725b0df6f9336d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:34:50 +0530 Subject: [PATCH 22/48] fix: remove enable disable settings --- src/components/molecules/ScheduleSettings.tsx | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index d8bc8bb9..33df99d9 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -85,6 +85,10 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia handleStart(settings); }; + const deleteSchedule = () => { + + }; + return ( *': { marginBottom: '20px' }, }}> Schedule Settings - - handleChange('enabled', e.target.checked)} - color="primary" - /> - } - label="Enable Scheduling" - /> - - {settings.enabled && ( <> Run once every @@ -195,24 +186,26 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia - )} - {/* {settings.enabled && ( - - {initialSettings?.nextRunAt && ( - `Next scheduled run: ${new Date(initialSettings.nextRunAt).toLocaleString()}` - )} - - )} */} + + + - + + + + ); -}; \ No newline at end of file +}; From a0d7b1df2ffde7908a457b51b2c65ac6a9d7cd55 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:35:10 +0530 Subject: [PATCH 23/48] chore: lint --- src/components/molecules/ScheduleSettings.tsx | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 33df99d9..0fb0abaf 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -86,7 +86,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia }; const deleteSchedule = () => { - + }; return ( @@ -103,89 +103,89 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia '& > *': { marginBottom: '20px' }, }}> Schedule Settings - <> - - Run once every - handleChange('runEvery', parseInt(e.target.value))} - sx={textStyle} - inputProps={{ min: 1 }} - /> - handleChange('runEveryUnit', e.target.value)} - sx={dropDownStyle} - > - {units.map((unit) => ( - {unit} - ))} - - + <> + + Run once every + handleChange('runEvery', parseInt(e.target.value))} + sx={textStyle} + inputProps={{ min: 1 }} + /> + handleChange('runEveryUnit', e.target.value)} + sx={dropDownStyle} + > + {units.map((unit) => ( + {unit} + ))} + + - - Start from / On - handleChange('startFrom', e.target.value)} - sx={dropDownStyle} - > - {days.map((day) => ( - {day} - ))} - - + + Start from / On + handleChange('startFrom', e.target.value)} + sx={dropDownStyle} + > + {days.map((day) => ( + {day} + ))} + + - {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( - - - In Between - handleChange('atTimeStart', e.target.value)} - sx={textStyle} - /> - handleChange('atTimeEnd', e.target.value)} - sx={textStyle} - /> - - - ) : ( - - At Around + {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( + + + In Between handleChange('atTimeStart', e.target.value)} sx={textStyle} /> + handleChange('atTimeEnd', e.target.value)} + sx={textStyle} + /> - )} - - - Timezone - handleChange('timezone', e.target.value)} - sx={dropDownStyle} - > - {validMomentTimezones.map((tz) => ( - {tz} - ))} - - + ) : ( + + At Around + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + + )} + + + Timezone + handleChange('timezone', e.target.value)} + sx={dropDownStyle} + > + {validMomentTimezones.map((tz) => ( + {tz} + ))} + + + + From 035cdaf16aceea5ca2e80f4ec50b1f8012869e51 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:38:03 +0530 Subject: [PATCH 25/48] feat: margin left cancel --- src/components/molecules/ScheduleSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 6256de5c..155d0b9d 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -198,10 +198,10 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia - - From 8fac3ef4c228d11a26a8599e1aa2f5395c0e58c9 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:39:13 +0530 Subject: [PATCH 26/48] feat: remove handleSubmit --- src/components/molecules/ScheduleSettings.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 155d0b9d..04d151cb 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -76,15 +76,6 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia 'SUNDAY' ]; - const handleSubmit = () => { - // If scheduling is disabled, only send the enabled flag - if (!settings.enabled) { - handleStart({ enabled: false } as ScheduleSettings); - return; - } - handleStart(settings); - }; - const deleteSchedule = () => { }; From 2a7df010f96e53cc7f2b9fb7e032f501ca14c599 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:39:53 +0530 Subject: [PATCH 27/48] feat: reset local settings on delete --- src/components/molecules/ScheduleSettings.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 04d151cb..eb3adb3c 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -77,7 +77,15 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia ]; const deleteSchedule = () => { - + setSettings({ + enabled: false, + runEvery: 1, + runEveryUnit: 'HOURS', + startFrom: 'MONDAY', + atTimeStart: '00:00', + atTimeEnd: '01:00', + timezone: 'UTC' + }); }; return ( From ec5b4a8372f06b1ee358324b0ae1ea0e9309d816 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:43:49 +0530 Subject: [PATCH 28/48] feat: get schedule of a robot --- src/api/storage.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 98e4a3b4..d1b1edac 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -147,3 +147,18 @@ export const scheduleStoredRecording = async (id: string, settings: ScheduleSett return {message: '', runId: ''}; } } + + +export const getSchedule = async (id: string): Promise => { + try { + const response = await axios.get(`http://localhost:8080/storage/schedule/${id}`); + if (response.status === 200) { + return response.data; + } else { + throw new Error(`Couldn't retrieve schedule for recording ${id}`); + } + } catch(error: any) { + console.log(error); + return null; + } +} \ No newline at end of file From 64601fcc66a5e3ced6f8a10c2abe23aae2b4fb10 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:44:14 +0530 Subject: [PATCH 29/48] chore: lint --- src/api/storage.ts | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/api/storage.ts b/src/api/storage.ts index d1b1edac..1417f418 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -12,7 +12,7 @@ export const getStoredRecordings = async (): Promise => { } 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 => { } 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 = } 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 => { } 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 => } 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 => { +export const notifyAboutAbort = async (id: string): Promise => { 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 => { } 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,19 +136,18 @@ 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): Promise => { try { const response = await axios.get(`http://localhost:8080/storage/schedule/${id}`); @@ -157,7 +156,7 @@ export const getSchedule = async (id: string): Promise } else { throw new Error(`Couldn't retrieve schedule for recording ${id}`); } - } catch(error: any) { + } catch (error: any) { console.log(error); return null; } From ed9fa486d7555cd2f0090493580984dd2e7b468a Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 20:46:50 +0530 Subject: [PATCH 30/48] feat: remove enabled --- src/components/molecules/ScheduleSettings.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index eb3adb3c..457572e3 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -14,7 +14,6 @@ interface ScheduleSettingsProps { } export interface ScheduleSettings { - enabled: boolean; runEvery: number; runEveryUnit: string; startFrom: string; @@ -25,7 +24,6 @@ export interface ScheduleSettings { export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: ScheduleSettingsProps) => { const [settings, setSettings] = useState({ - enabled: true, runEvery: 1, runEveryUnit: 'HOURS', startFrom: 'MONDAY', @@ -78,7 +76,6 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia const deleteSchedule = () => { setSettings({ - enabled: false, runEvery: 1, runEveryUnit: 'HOURS', startFrom: 'MONDAY', From ef6fa8d21249c2d50895db7c23062c8a70d9698d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 21:05:52 +0530 Subject: [PATCH 31/48] feat: delete schedule --- src/api/storage.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 1417f418..ccd54d1d 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -160,4 +160,18 @@ export const getSchedule = async (id: string): Promise console.log(error); return null; } +} + +export const deleteSchedule = async (id: string): Promise => { + 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; + } } \ No newline at end of file From 1bd7b2fd4787c72d1b202b72da5699d9116e9be3 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 21:15:05 +0530 Subject: [PATCH 32/48] feat: get schedule of robot --- src/components/molecules/ScheduleSettings.tsx | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 457572e3..f0f7ff38 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -5,6 +5,8 @@ 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 } from '../../api/storage'; interface ScheduleSettingsProps { isOpen: boolean; @@ -85,6 +87,26 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia }); }; + const { recordingId } = useGlobalInfoStore(); + + const getRobotSchedule = async () => { + if (recordingId) { + const schedule = await getSchedule(recordingId); + if (schedule) { + setSettings(schedule); + } + } else { + console.error('No recording id provided'); + } + } + + useEffect(() => { + if (isOpen) { + getRobotSchedule(); + } + }, [isOpen]); + + return ( + + + + - - ); From 2a1b2ec8f4c0ca7335c0941a5d8c22c5c2a1d757 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 22:01:25 +0530 Subject: [PATCH 33/48] feat: send robot instead of schedule --- server/src/routes/storage.ts | 97 ++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 401e169d..9cf06619 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -247,7 +247,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { try { const { id } = req.params; const { - enabled = true, + // enabled = true, runEvery, runEveryUnit, startFrom, @@ -261,25 +261,25 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { 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(); - } - } + // 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 - }); + // // Update robot to disable scheduling + // await robot.update({ + // schedule: null + // }); - return res.status(200).json({ - message: 'Schedule disabled successfully' - }); - } + // 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' }); @@ -296,7 +296,6 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { const [startHours, startMinutes] = atTimeStart.split(':').map(Number); const [endHours, endMinutes] = atTimeEnd.split(':').map(Number); - // Validate the time format for In Between 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) { @@ -311,8 +310,6 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { let cronExpression; switch (runEveryUnit) { case 'MINUTES': - cronExpression = `${startMinutes}-${endMinutes} */${runEvery} * * *`; - break; case 'HOURS': cronExpression = `${startMinutes}-${endMinutes} */${runEvery} * * *`; break; @@ -339,13 +336,13 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { const runId = uuid(); const userId = req.user.id; - // Remove existing jobs for this robot - const existingJobs = await workflowQueue.getJobs(['delayed', 'waiting']); - for (const job of existingJobs) { - if (job.data.id === id) { - await job.remove(); - } - } + // 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( @@ -359,7 +356,6 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } ); - // Calculate next run time const nextRun = job.timestamp; // Update robot with schedule details @@ -378,10 +374,13 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } }); + // Fetch updated schedule details after setting it + const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } }); + res.status(200).json({ message: 'success', runId, - schedule: robot.schedule + robot: updatedRobot }); } catch (error) { @@ -390,7 +389,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { } }); -// Add new endpoint to get schedule status +// Endpoint to get schedule details router.get('/schedule/:id', requireSignIn, async (req, res) => { try { const { id } = req.params; @@ -410,12 +409,36 @@ router.get('/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 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. From 3a6f4485598cb0a1ea604bd76b1c2f6a4c149a45 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 23:17:09 +0530 Subject: [PATCH 34/48] feat: check schedule status --- src/components/molecules/ScheduleSettings.tsx | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index f0f7ff38..6449835c 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -25,6 +25,7 @@ export interface ScheduleSettings { } export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initialSettings }: ScheduleSettingsProps) => { + const [schedule, setSchedule] = useState(null); const [settings, setSettings] = useState({ runEvery: 1, runEveryUnit: 'HOURS', @@ -89,11 +90,14 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia const { recordingId } = useGlobalInfoStore(); + console.log(`Recoridng ID Shculde: ${recordingId}`); + const getRobotSchedule = async () => { if (recordingId) { const schedule = await getSchedule(recordingId); + console.log(`RObot found schedule: ${schedule}`) if (schedule) { - setSettings(schedule); + setSchedule(schedule); } } else { console.error('No recording id provided'); @@ -104,7 +108,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia if (isOpen) { getRobotSchedule(); } - }, [isOpen]); + }, [isOpen, getRobotSchedule]); return ( @@ -122,7 +126,20 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia }}> Schedule Settings <> - + { + (schedule != null) ? ( + + + + ) : ( + <> + Run once every - - + @@ -212,16 +228,10 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia Cancel - - - - + + ) + } + ); From 44e483b18bad9cd4c761689b3b11da571b8c5b10 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 23:17:39 +0530 Subject: [PATCH 35/48] feat: send robot schedule --- server/src/routes/storage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 9cf06619..5e768b6e 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -392,7 +392,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { // Endpoint to get schedule details router.get('/schedule/:id', requireSignIn, async (req, res) => { try { - const { id } = req.params; + const { id } = req.body; const robot = await Robot.findOne({ where: { 'recording_meta.id': id } }); if (!robot) { @@ -400,7 +400,7 @@ router.get('/schedule/:id', requireSignIn, async (req, res) => { } return res.status(200).json({ - schedule: robot.schedule || { enabled: false } + schedule: robot.schedule }); } catch (error) { From cc7d24d9d06849deb4804122ae9531caf29ba7b9 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 23:42:55 +0530 Subject: [PATCH 36/48] feat: separate debug port for worker child process --- server/src/server.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/server/src/server.ts b/server/src/server.ts index 24c044b1..1a6496aa 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -58,7 +58,10 @@ readdirSync(path.join(__dirname, 'api')).forEach((r) => { } }); -const workerProcess = fork(path.resolve(__dirname, './worker.ts')); +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}`); }); From b44cd12dac77c70d4b7f1e7b21b71c88e96d2131 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Tue, 22 Oct 2024 23:43:07 +0530 Subject: [PATCH 37/48] chore: lint --- server/src/server.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/src/server.ts b/server/src/server.ts index 1a6496aa..25660e11 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -58,19 +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'), [], { 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) => { +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.log(`Worker exited with code: ${code}`); - }); +}); app.get('/', function (req, res) { return res.send('Maxun server started 🚀'); From 0ae1fe8d3b42c356f8832f0d22307a156f615338 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Wed, 23 Oct 2024 00:10:48 +0530 Subject: [PATCH 38/48] fix: get id from req.params --- server/src/routes/storage.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 5e768b6e..f88bd74f 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -392,8 +392,7 @@ router.put('/schedule/:id/', requireSignIn, async (req, res) => { // Endpoint to get schedule details router.get('/schedule/:id', requireSignIn, async (req, res) => { try { - const { id } = req.body; - const robot = await Robot.findOne({ where: { 'recording_meta.id': id } }); + 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' }); From e0c014e98ae826fdbc82971a7954aa230982adf3 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Wed, 23 Oct 2024 00:13:29 +0530 Subject: [PATCH 39/48] feat: delete schedule --- src/components/molecules/ScheduleSettings.tsx | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 6449835c..11ef3185 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -6,7 +6,7 @@ import Button from "@mui/material/Button"; import { modalStyle } from "./AddWhereCondModal"; import { validMomentTimezones } from '../../constants/const'; import { useGlobalInfoStore } from '../../context/globalInfo'; -import { getSchedule } from '../../api/storage'; +import { getSchedule, deleteSchedule } from '../../api/storage'; interface ScheduleSettingsProps { isOpen: boolean; @@ -77,7 +77,18 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia 'SUNDAY' ]; - const deleteSchedule = () => { + 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', @@ -88,10 +99,6 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia }); }; - const { recordingId } = useGlobalInfoStore(); - - console.log(`Recoridng ID Shculde: ${recordingId}`); - const getRobotSchedule = async () => { if (recordingId) { const schedule = await getSchedule(recordingId); @@ -130,7 +137,7 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia (schedule != null) ? ( - + + ) : ( <> - - Run once every - handleChange('runEvery', parseInt(e.target.value))} - sx={textStyle} - inputProps={{ min: 1 }} - /> - handleChange('runEveryUnit', e.target.value)} - sx={dropDownStyle} - > - {units.map((unit) => ( - {unit} - ))} - - + + Run once every + handleChange('runEvery', parseInt(e.target.value))} + sx={textStyle} + inputProps={{ min: 1 }} + /> + handleChange('runEveryUnit', e.target.value)} + sx={dropDownStyle} + > + {units.map((unit) => ( + {unit} + ))} + + - - Start from / On - handleChange('startFrom', e.target.value)} - sx={dropDownStyle} - > - {days.map((day) => ( - {day} - ))} - - + + Start from / On + handleChange('startFrom', e.target.value)} + sx={dropDownStyle} + > + {days.map((day) => ( + {day} + ))} + + - {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( - - - In Between - handleChange('atTimeStart', e.target.value)} - sx={textStyle} - /> - handleChange('atTimeEnd', e.target.value)} - sx={textStyle} - /> - - - ) : ( - - At Around - handleChange('atTimeStart', e.target.value)} - sx={textStyle} - /> - - )} + {['MINUTES', 'HOURS'].includes(settings.runEveryUnit) ? ( + + + In Between + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + handleChange('atTimeEnd', e.target.value)} + sx={textStyle} + /> + + + ) : ( + + At Around + handleChange('atTimeStart', e.target.value)} + sx={textStyle} + /> + + )} - - Timezone - handleChange('timezone', e.target.value)} - sx={dropDownStyle} - > - {validMomentTimezones.map((tz) => ( - {tz} - ))} - - - - - - + + Timezone + handleChange('timezone', e.target.value)} + sx={dropDownStyle} + > + {validMomentTimezones.map((tz) => ( + {tz} + ))} + + + + + + ) } From ed57ff3d685128b8dd8d993524f6a5c15b9d931d Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Wed, 23 Oct 2024 00:15:07 +0530 Subject: [PATCH 41/48] feat: display message --- src/components/molecules/ScheduleSettings.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 1ae76590..4630920e 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -135,6 +135,8 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia <> { (schedule != null) ? ( + <> + Robot is scheduled to run every {schedule.runEvery} {schedule.runEveryUnit} starting from {schedule.startFrom} at {schedule.atTimeStart} to {schedule.atTimeEnd} in {schedule.timezone} timezone. - + Robot is scheduled to run every {schedule.runEvery} {schedule.runEveryUnit} starting from {schedule.startFrom} at {schedule.atTimeStart} to {schedule.atTimeEnd} in {schedule.timezone} timezone. + + + ) : ( <> From 01fac62284110631c05b3a27bdabf5e7d41ae960 Mon Sep 17 00:00:00 2001 From: karishmas6 Date: Wed, 23 Oct 2024 03:53:29 +0530 Subject: [PATCH 48/48] feat: change delete button style --- src/components/molecules/ScheduleSettings.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/ScheduleSettings.tsx b/src/components/molecules/ScheduleSettings.tsx index 06a08f83..1174b4dc 100644 --- a/src/components/molecules/ScheduleSettings.tsx +++ b/src/components/molecules/ScheduleSettings.tsx @@ -140,8 +140,8 @@ export const ScheduleSettingsModal = ({ isOpen, handleStart, handleClose, initia