2024-07-31 21:10:25 +05:30
|
|
|
import { Router } from 'express';
|
|
|
|
|
import logger from "../logger";
|
|
|
|
|
import { createRemoteBrowserForRun, destroyRemoteBrowser } from "../browser-management/controller";
|
2024-11-22 23:04:03 +05:30
|
|
|
import { chromium } from 'playwright-extra';
|
|
|
|
|
import stealthPlugin from 'puppeteer-extra-plugin-stealth';
|
2024-09-19 17:39:33 +05:30
|
|
|
import { browserPool } from "../server";
|
2024-07-31 21:10:25 +05:30
|
|
|
import { uuid } from "uuidv4";
|
2024-09-11 23:33:04 +05:30
|
|
|
import moment from 'moment-timezone';
|
|
|
|
|
import cron from 'node-cron';
|
2024-09-19 19:36:47 +05:30
|
|
|
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from '../workflow-management/integrations/gsheet';
|
2024-10-06 03:15:45 +05:30
|
|
|
import { getDecryptedProxyConfig } from './proxy';
|
2024-10-06 04:11:50 +05:30
|
|
|
import { requireSignIn } from '../middlewares/auth';
|
2024-10-09 23:08:30 +05:30
|
|
|
import Robot from '../models/Robot';
|
|
|
|
|
import Run from '../models/Run';
|
2024-10-15 22:20:29 +05:30
|
|
|
import { BinaryOutputService } from '../storage/mino';
|
2024-10-22 16:57:33 +05:30
|
|
|
import { workflowQueue } from '../worker';
|
2024-10-24 22:26:12 +05:30
|
|
|
import { AuthenticatedRequest } from './record';
|
2024-10-26 00:49:26 +05:30
|
|
|
import { computeNextRun } from '../utils/schedule';
|
2024-10-29 03:44:20 +05:30
|
|
|
import { capture } from "../utils/analytics";
|
2024-11-15 22:26:36 +05:30
|
|
|
import { tryCatch } from 'bullmq';
|
2025-01-21 22:26:29 +05:30
|
|
|
import { encrypt } from '../utils/auth';
|
2024-12-08 18:08:05 +05:30
|
|
|
import { WorkflowFile } from 'maxun-core';
|
|
|
|
|
import { Page } from 'playwright';
|
2024-11-22 23:04:03 +05:30
|
|
|
chromium.use(stealthPlugin());
|
2024-07-31 21:10:25 +05:30
|
|
|
|
|
|
|
|
export const router = Router();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Logs information about recordings API.
|
|
|
|
|
*/
|
2024-10-06 04:11:50 +05:30
|
|
|
router.all('/', requireSignIn, (req, res, next) => {
|
2024-09-19 17:39:33 +05:30
|
|
|
logger.log('debug', `The recordings API was invoked: ${req.url}`)
|
2024-07-31 21:10:25 +05:30
|
|
|
next() // pass control to the next handler
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET endpoint for getting an array of all stored recordings.
|
|
|
|
|
*/
|
2024-10-06 04:11:50 +05:30
|
|
|
router.get('/recordings', requireSignIn, async (req, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-21 21:03:42 +05:30
|
|
|
const data = await Robot.findAll();
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(data);
|
|
|
|
|
} catch (e) {
|
2024-11-29 22:12:16 +05:30
|
|
|
logger.log('info', 'Error while reading robots');
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(null);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-17 14:34:25 +05:30
|
|
|
/**
|
|
|
|
|
* GET endpoint for getting a recording.
|
|
|
|
|
*/
|
2024-10-17 14:33:51 +05:30
|
|
|
router.get('/recordings/:id', requireSignIn, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const data = await Robot.findOne({
|
2024-10-21 21:03:42 +05:30
|
|
|
where: { 'recording_meta.id': req.params.id },
|
2024-10-17 14:33:51 +05:30
|
|
|
raw: true
|
|
|
|
|
}
|
2024-10-22 16:59:18 +05:30
|
|
|
);
|
2024-10-17 14:33:51 +05:30
|
|
|
return res.send(data);
|
|
|
|
|
} catch (e) {
|
2024-11-29 22:12:16 +05:30
|
|
|
logger.log('info', 'Error while reading robots');
|
2024-10-17 14:33:51 +05:30
|
|
|
return res.send(null);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
2024-11-15 22:26:36 +05:30
|
|
|
router.get(('/recordings/:id/runs'), requireSignIn, async (req, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const runs = await Run.findAll({
|
|
|
|
|
where: {
|
|
|
|
|
robotMetaId: req.params.id
|
|
|
|
|
},
|
|
|
|
|
raw: true
|
|
|
|
|
});
|
|
|
|
|
const formattedRuns = runs.map(formatRunResponse);
|
|
|
|
|
const response = {
|
|
|
|
|
statusCode: 200,
|
|
|
|
|
messageCode: "success",
|
|
|
|
|
runs: {
|
|
|
|
|
totalCount: formattedRuns.length,
|
|
|
|
|
items: formattedRuns,
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
res.status(200).json(response);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Error fetching runs:", error);
|
|
|
|
|
res.status(500).json({
|
|
|
|
|
statusCode: 500,
|
|
|
|
|
messageCode: "error",
|
|
|
|
|
message: "Failed to retrieve runs",
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
function formatRunResponse(run: any) {
|
|
|
|
|
const formattedRun = {
|
|
|
|
|
id: run.id,
|
|
|
|
|
status: run.status,
|
|
|
|
|
name: run.name,
|
|
|
|
|
robotId: run.robotMetaId, // Renaming robotMetaId to robotId
|
|
|
|
|
startedAt: run.startedAt,
|
|
|
|
|
finishedAt: run.finishedAt,
|
|
|
|
|
runId: run.runId,
|
|
|
|
|
runByUserId: run.runByUserId,
|
|
|
|
|
runByScheduleId: run.runByScheduleId,
|
|
|
|
|
runByAPI: run.runByAPI,
|
|
|
|
|
data: {},
|
|
|
|
|
screenshot: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if (run.serializableOutput && run.serializableOutput['item-0']) {
|
|
|
|
|
formattedRun.data = run.serializableOutput['item-0'];
|
|
|
|
|
} else if (run.binaryOutput && run.binaryOutput['item-0']) {
|
|
|
|
|
formattedRun.screenshot = run.binaryOutput['item-0'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return formattedRun;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 22:26:29 +05:30
|
|
|
interface CredentialUpdate {
|
|
|
|
|
[selector: string]: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateTypeActionsInWorkflow(workflow: any[], credentials: CredentialUpdate) {
|
|
|
|
|
return workflow.map(step => {
|
|
|
|
|
if (!step.what) return step;
|
|
|
|
|
|
|
|
|
|
// First pass: mark indices to remove
|
|
|
|
|
const indicesToRemove = new Set<number>();
|
|
|
|
|
step.what.forEach((action: any, index: any) => {
|
|
|
|
|
if (!action.action || !action.args?.[0]) return;
|
|
|
|
|
|
|
|
|
|
// If it's a type/press action for a credential
|
|
|
|
|
if ((action.action === 'type' || action.action === 'press') && credentials[action.args[0]]) {
|
|
|
|
|
indicesToRemove.add(index);
|
|
|
|
|
// Check if next action is waitForLoadState
|
|
|
|
|
if (step.what[index + 1]?.action === 'waitForLoadState') {
|
|
|
|
|
indicesToRemove.add(index + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Filter out marked indices and create new what array
|
|
|
|
|
const filteredWhat = step.what.filter((_: any, index: any) => !indicesToRemove.has(index));
|
|
|
|
|
|
|
|
|
|
// Add new type actions after click actions
|
|
|
|
|
Object.entries(credentials).forEach(([selector, credential]) => {
|
|
|
|
|
const clickIndex = filteredWhat.findIndex((action: any) =>
|
|
|
|
|
action.action === 'click' && action.args?.[0] === selector
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (clickIndex !== -1) {
|
2025-01-21 23:37:53 +05:30
|
|
|
const chars = credential.split('');
|
2025-01-21 22:26:29 +05:30
|
|
|
chars.forEach((char, i) => {
|
|
|
|
|
// Add type action
|
|
|
|
|
filteredWhat.splice(clickIndex + 1 + (i * 2), 0, {
|
|
|
|
|
action: 'type',
|
2025-01-21 22:31:25 +05:30
|
|
|
args: [selector, encrypt(char)]
|
2025-01-21 22:26:29 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Add waitForLoadState
|
|
|
|
|
filteredWhat.splice(clickIndex + 2 + (i * 2), 0, {
|
|
|
|
|
action: 'waitForLoadState',
|
|
|
|
|
args: ['networkidle']
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...step,
|
|
|
|
|
what: filteredWhat
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-19 02:40:00 +05:30
|
|
|
/**
|
|
|
|
|
* PUT endpoint to update the name and limit of a robot.
|
|
|
|
|
*/
|
|
|
|
|
router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
2025-01-21 22:26:29 +05:30
|
|
|
const { name, limit, credentials } = req.body;
|
2024-11-19 02:40:00 +05:30
|
|
|
|
|
|
|
|
// Validate input
|
|
|
|
|
if (!name && limit === undefined) {
|
|
|
|
|
return res.status(400).json({ error: 'Either "name" or "limit" must be provided.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch the robot by ID
|
|
|
|
|
const robot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
|
|
|
|
|
|
|
|
|
if (!robot) {
|
|
|
|
|
return res.status(404).json({ error: 'Robot not found.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update fields if provided
|
|
|
|
|
if (name) {
|
|
|
|
|
robot.set('recording_meta', { ...robot.recording_meta, name });
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 22:26:29 +05:30
|
|
|
let workflow = [...robot.recording.workflow]; // Create a copy of the workflow
|
|
|
|
|
|
|
|
|
|
if (credentials) {
|
|
|
|
|
workflow = updateTypeActionsInWorkflow(workflow, credentials);
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-19 02:40:00 +05:30
|
|
|
// Update the limit
|
|
|
|
|
if (limit !== undefined) {
|
|
|
|
|
// Ensure the workflow structure is valid before updating
|
|
|
|
|
if (
|
|
|
|
|
workflow.length > 0 &&
|
|
|
|
|
workflow[0]?.what?.[0]
|
|
|
|
|
) {
|
|
|
|
|
// Create a new workflow object with the updated limit
|
2025-01-21 22:26:29 +05:30
|
|
|
workflow = workflow.map((step, index) => {
|
2024-11-19 02:40:00 +05:30
|
|
|
if (index === 0) { // Assuming you want to update the first step
|
|
|
|
|
return {
|
|
|
|
|
...step,
|
|
|
|
|
what: step.what.map((action, actionIndex) => {
|
|
|
|
|
if (actionIndex === 0) { // Assuming the first action needs updating
|
|
|
|
|
return {
|
|
|
|
|
...action,
|
|
|
|
|
args: (action.args ?? []).map((arg, argIndex) => {
|
|
|
|
|
if (argIndex === 0) { // Assuming the first argument needs updating
|
|
|
|
|
return { ...arg, limit };
|
|
|
|
|
}
|
|
|
|
|
return arg;
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return action;
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
return step;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
return res.status(400).json({ error: 'Invalid workflow structure for updating limit.' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-21 22:26:29 +05:30
|
|
|
robot.set('recording', { ...robot.recording, workflow });
|
|
|
|
|
|
2024-11-19 02:40:00 +05:30
|
|
|
await robot.save();
|
|
|
|
|
|
|
|
|
|
const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Robot with ID ${id} was updated successfully.`);
|
|
|
|
|
|
|
|
|
|
return res.status(200).json({ message: 'Robot updated successfully', robot });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Safely handle the error type
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
logger.log('error', `Error updating robot with ID ${req.params.id}: ${error.message}`);
|
|
|
|
|
return res.status(500).json({ error: error.message });
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('error', `Unknown error updating robot with ID ${req.params.id}`);
|
|
|
|
|
return res.status(500).json({ error: 'An unknown error occurred.' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* POST endpoint to duplicate a robot and update its target URL.
|
|
|
|
|
*/
|
|
|
|
|
router.post('/recordings/:id/duplicate', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
|
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
const { targetUrl } = req.body;
|
|
|
|
|
|
|
|
|
|
if (!targetUrl) {
|
|
|
|
|
return res.status(400).json({ error: 'The "targetUrl" field is required.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const originalRobot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
|
|
|
|
|
|
|
|
|
if (!originalRobot) {
|
|
|
|
|
return res.status(404).json({ error: 'Original robot not found.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastWord = targetUrl.split('/').filter(Boolean).pop() || 'Unnamed';
|
|
|
|
|
|
|
|
|
|
const workflow = originalRobot.recording.workflow.map((step) => {
|
|
|
|
|
if (step.where?.url && step.where.url !== "about:blank") {
|
|
|
|
|
step.where.url = targetUrl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
step.what.forEach((action) => {
|
|
|
|
|
if (action.action === "goto" && action.args?.length) {
|
|
|
|
|
action.args[0] = targetUrl;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return step;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const currentTimestamp = new Date().toISOString();
|
|
|
|
|
|
|
|
|
|
const newRobot = await Robot.create({
|
|
|
|
|
id: uuid(),
|
|
|
|
|
userId: originalRobot.userId,
|
|
|
|
|
recording_meta: {
|
|
|
|
|
...originalRobot.recording_meta,
|
|
|
|
|
id: uuid(),
|
|
|
|
|
name: `${originalRobot.recording_meta.name} (${lastWord})`,
|
|
|
|
|
createdAt: currentTimestamp,
|
|
|
|
|
updatedAt: currentTimestamp,
|
|
|
|
|
},
|
|
|
|
|
recording: { ...originalRobot.recording, workflow },
|
2025-01-20 17:18:19 +05:30
|
|
|
isLogin: originalRobot.isLogin,
|
2024-11-19 02:40:00 +05:30
|
|
|
google_sheet_email: null,
|
|
|
|
|
google_sheet_name: null,
|
|
|
|
|
google_sheet_id: null,
|
|
|
|
|
google_access_token: null,
|
|
|
|
|
google_refresh_token: null,
|
|
|
|
|
schedule: null,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logger.log('info', `Robot with ID ${id} duplicated successfully as ${newRobot.id}.`);
|
|
|
|
|
|
|
|
|
|
return res.status(201).json({
|
|
|
|
|
message: 'Robot duplicated and target URL updated successfully.',
|
|
|
|
|
robot: newRobot,
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
if (error instanceof Error) {
|
|
|
|
|
logger.log('error', `Error duplicating robot with ID ${req.params.id}: ${error.message}`);
|
|
|
|
|
return res.status(500).json({ error: error.message });
|
|
|
|
|
} else {
|
|
|
|
|
logger.log('error', `Unknown error duplicating robot with ID ${req.params.id}`);
|
|
|
|
|
return res.status(500).json({ error: 'An unknown error occurred.' });
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-07-31 21:10:25 +05:30
|
|
|
/**
|
|
|
|
|
* DELETE endpoint for deleting a recording from the storage.
|
|
|
|
|
*/
|
2024-10-28 02:47:21 +05:30
|
|
|
router.delete('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
|
|
|
|
if (!req.user) {
|
|
|
|
|
return res.status(401).send({ error: 'Unauthorized' });
|
|
|
|
|
}
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-09 23:22:38 +05:30
|
|
|
await Robot.destroy({
|
2024-10-21 21:03:42 +05:30
|
|
|
where: { 'recording_meta.id': req.params.id }
|
2024-10-09 23:22:38 +05:30
|
|
|
});
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-robot-deleted',
|
|
|
|
|
{
|
2024-10-28 02:47:21 +05:30
|
|
|
robotId: req.params.id,
|
|
|
|
|
user_id: req.user?.id,
|
|
|
|
|
deleted_at: new Date().toISOString(),
|
|
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
)
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(true);
|
|
|
|
|
} catch (e) {
|
2024-09-19 17:39:33 +05:30
|
|
|
const { message } = e as Error;
|
2024-10-09 03:10:22 +05:30
|
|
|
logger.log('info', `Error while deleting a recording with name: ${req.params.fileName}.json`);
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET endpoint for getting an array of runs from the storage.
|
|
|
|
|
*/
|
2024-10-06 04:11:50 +05:30
|
|
|
router.get('/runs', requireSignIn, async (req, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-09 23:08:30 +05:30
|
|
|
const data = await Run.findAll();
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(data);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
logger.log('info', 'Error while reading runs');
|
|
|
|
|
return res.send(null);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DELETE endpoint for deleting a run from the storage.
|
|
|
|
|
*/
|
2024-10-28 02:47:21 +05:30
|
|
|
router.delete('/runs/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
|
|
|
|
if (!req.user) {
|
|
|
|
|
return res.status(401).send({ error: 'Unauthorized' });
|
|
|
|
|
}
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-10 02:02:43 +05:30
|
|
|
await Run.destroy({ where: { runId: req.params.id } });
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-run-deleted',
|
|
|
|
|
{
|
2024-10-28 02:47:21 +05:30
|
|
|
runId: req.params.id,
|
|
|
|
|
user_id: req.user?.id,
|
|
|
|
|
deleted_at: new Date().toISOString(),
|
|
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
)
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(true);
|
|
|
|
|
} catch (e) {
|
2024-09-19 17:39:33 +05:30
|
|
|
const { message } = e as Error;
|
2024-07-31 21:10:25 +05:30
|
|
|
logger.log('info', `Error while deleting a run with name: ${req.params.fileName}.json`);
|
|
|
|
|
return res.send(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* PUT endpoint for starting a remote browser instance and saving run metadata to the storage.
|
|
|
|
|
* Making it ready for interpretation and returning a runId.
|
|
|
|
|
*/
|
2024-10-24 22:26:12 +05:30
|
|
|
router.put('/runs/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-09 23:08:30 +05:30
|
|
|
const recording = await Robot.findOne({
|
2024-10-10 01:19:31 +05:30
|
|
|
where: {
|
2024-10-21 21:03:42 +05:30
|
|
|
'recording_meta.id': req.params.id
|
2024-10-10 01:19:03 +05:30
|
|
|
},
|
|
|
|
|
raw: true
|
2024-10-09 23:08:30 +05:30
|
|
|
});
|
2024-10-08 23:25:24 +05:30
|
|
|
|
|
|
|
|
if (!recording || !recording.recording_meta || !recording.recording_meta.id) {
|
2024-10-08 22:00:05 +05:30
|
|
|
return res.status(404).send({ error: 'Recording not found' });
|
|
|
|
|
}
|
2024-10-08 22:01:25 +05:30
|
|
|
|
2024-10-24 22:26:12 +05:30
|
|
|
if (!req.user) {
|
|
|
|
|
return res.status(401).send({ error: 'Unauthorized' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-06 03:15:45 +05:30
|
|
|
const proxyConfig = await getDecryptedProxyConfig(req.user.id);
|
|
|
|
|
let proxyOptions: any = {};
|
|
|
|
|
|
|
|
|
|
if (proxyConfig.proxy_url) {
|
2024-10-06 03:15:54 +05:30
|
|
|
proxyOptions = {
|
|
|
|
|
server: proxyConfig.proxy_url,
|
|
|
|
|
...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
|
|
|
|
|
username: proxyConfig.proxy_username,
|
|
|
|
|
password: proxyConfig.proxy_password,
|
|
|
|
|
}),
|
|
|
|
|
};
|
2024-10-06 03:15:45 +05:30
|
|
|
}
|
|
|
|
|
|
2024-10-27 17:56:02 +05:30
|
|
|
console.log(`Proxy config for run: ${JSON.stringify(proxyOptions)}`)
|
|
|
|
|
|
2024-11-03 02:59:30 +05:30
|
|
|
const id = createRemoteBrowserForRun(req.user.id);
|
2024-07-31 21:10:25 +05:30
|
|
|
|
|
|
|
|
const runId = uuid();
|
|
|
|
|
|
2024-10-09 23:08:30 +05:30
|
|
|
const run = await Run.create({
|
2024-10-29 00:44:38 +05:30
|
|
|
status: 'running',
|
2024-10-10 01:19:03 +05:30
|
|
|
name: recording.recording_meta.name,
|
|
|
|
|
robotId: recording.id,
|
|
|
|
|
robotMetaId: recording.recording_meta.id,
|
2024-07-31 21:10:25 +05:30
|
|
|
startedAt: new Date().toLocaleString(),
|
|
|
|
|
finishedAt: '',
|
|
|
|
|
browserId: id,
|
|
|
|
|
interpreterSettings: req.body,
|
|
|
|
|
log: '',
|
|
|
|
|
runId,
|
2024-10-21 18:55:11 +05:30
|
|
|
runByUserId: req.user.id,
|
2024-10-09 23:08:30 +05:30
|
|
|
serializableOutput: {},
|
|
|
|
|
binaryOutput: {},
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-10 01:55:23 +05:30
|
|
|
const plainRun = run.toJSON();
|
|
|
|
|
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send({
|
|
|
|
|
browserId: id,
|
2024-10-10 01:55:23 +05:30
|
|
|
runId: plainRun.runId,
|
2024-07-31 21:10:25 +05:30
|
|
|
});
|
|
|
|
|
} catch (e) {
|
2024-09-19 17:39:33 +05:30
|
|
|
const { message } = e as Error;
|
2024-11-29 22:13:03 +05:30
|
|
|
logger.log('info', `Error while creating a run with robot id: ${req.params.id} - ${message}`);
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send('');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET endpoint for getting a run from the storage.
|
|
|
|
|
*/
|
2024-10-09 23:08:30 +05:30
|
|
|
router.get('/runs/run/:id', requireSignIn, async (req, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-10 01:19:03 +05:30
|
|
|
const run = await Run.findOne({ where: { runId: req.params.runId }, raw: true });
|
2024-10-09 23:08:30 +05:30
|
|
|
if (!run) {
|
|
|
|
|
return res.status(404).send(null);
|
|
|
|
|
}
|
|
|
|
|
return res.send(run);
|
2024-07-31 21:10:25 +05:30
|
|
|
} catch (e) {
|
|
|
|
|
const { message } = e as Error;
|
2024-10-10 01:55:23 +05:30
|
|
|
logger.log('error', `Error ${message} while reading a run with id: ${req.params.id}.json`);
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(null);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-12-08 18:08:05 +05:30
|
|
|
function AddGeneratedFlags(workflow: WorkflowFile) {
|
|
|
|
|
const copy = JSON.parse(JSON.stringify(workflow));
|
|
|
|
|
for (let i = 0; i < workflow.workflow.length; i++) {
|
|
|
|
|
copy.workflow[i].what.unshift({
|
|
|
|
|
action: 'flag',
|
|
|
|
|
args: ['generated'],
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
return copy;
|
|
|
|
|
};
|
|
|
|
|
|
2024-07-31 21:10:25 +05:30
|
|
|
/**
|
|
|
|
|
* PUT endpoint for finishing a run and saving it to the storage.
|
|
|
|
|
*/
|
2024-10-28 02:47:21 +05:30
|
|
|
router.post('/runs/run/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-28 02:47:21 +05:30
|
|
|
if (!req.user) { return res.status(401).send({ error: 'Unauthorized' }); }
|
2024-10-09 23:08:30 +05:30
|
|
|
|
2024-10-10 02:23:33 +05:30
|
|
|
const run = await Run.findOne({ where: { runId: req.params.id } });
|
2024-10-09 23:08:30 +05:30
|
|
|
if (!run) {
|
|
|
|
|
return res.status(404).send(false);
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-10 02:23:33 +05:30
|
|
|
const plainRun = run.toJSON();
|
|
|
|
|
|
2024-10-21 21:03:42 +05:30
|
|
|
const recording = await Robot.findOne({ where: { 'recording_meta.id': plainRun.robotMetaId }, raw: true });
|
2024-10-09 23:08:30 +05:30
|
|
|
if (!recording) {
|
|
|
|
|
return res.status(404).send(false);
|
|
|
|
|
}
|
2024-07-31 21:10:25 +05:30
|
|
|
|
|
|
|
|
// interpret the run in active browser
|
2024-10-10 02:23:33 +05:30
|
|
|
const browser = browserPool.getRemoteBrowser(plainRun.browserId);
|
2024-12-08 18:08:05 +05:30
|
|
|
let currentPage = browser?.getCurrentPage();
|
2024-07-31 21:10:25 +05:30
|
|
|
if (browser && currentPage) {
|
2024-12-08 18:08:05 +05:30
|
|
|
const workflow = AddGeneratedFlags(recording.recording);
|
2024-07-31 21:10:25 +05:30
|
|
|
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
2024-12-08 18:08:05 +05:30
|
|
|
workflow, currentPage, (newPage: Page) => currentPage = newPage, plainRun.interpreterSettings);
|
2024-10-22 16:59:18 +05:30
|
|
|
const binaryOutputService = new BinaryOutputService('maxun-run-screenshots');
|
|
|
|
|
const uploadedBinaryOutput = await binaryOutputService.uploadAndStoreBinaryOutput(run, interpretationInfo.binaryOutput);
|
2024-10-10 02:23:33 +05:30
|
|
|
await destroyRemoteBrowser(plainRun.browserId);
|
2024-10-09 23:08:30 +05:30
|
|
|
await run.update({
|
|
|
|
|
...run,
|
2024-09-19 17:39:33 +05:30
|
|
|
status: 'success',
|
|
|
|
|
finishedAt: new Date().toLocaleString(),
|
2024-10-10 02:23:33 +05:30
|
|
|
browserId: plainRun.browserId,
|
2024-09-19 17:39:33 +05:30
|
|
|
log: interpretationInfo.log.join('\n'),
|
|
|
|
|
serializableOutput: interpretationInfo.serializableOutput,
|
2024-10-15 22:20:29 +05:30
|
|
|
binaryOutput: uploadedBinaryOutput,
|
2024-10-09 23:08:30 +05:30
|
|
|
});
|
2024-10-28 04:49:54 +05:30
|
|
|
|
|
|
|
|
let totalRowsExtracted = 0;
|
2024-10-29 00:44:38 +05:30
|
|
|
let extractedScreenshotsCount = 0;
|
|
|
|
|
let extractedItemsCount = 0;
|
|
|
|
|
|
2024-10-29 04:22:19 +05:30
|
|
|
if (run.dataValues.binaryOutput && run.dataValues.binaryOutput["item-0"]) {
|
|
|
|
|
extractedScreenshotsCount = 1;
|
2024-10-29 00:44:38 +05:30
|
|
|
}
|
2024-10-29 04:22:19 +05:30
|
|
|
|
|
|
|
|
if (run.dataValues.serializableOutput && run.dataValues.serializableOutput["item-0"]) {
|
|
|
|
|
const itemsArray = run.dataValues.serializableOutput["item-0"];
|
|
|
|
|
extractedItemsCount = itemsArray.length;
|
|
|
|
|
|
|
|
|
|
totalRowsExtracted = itemsArray.reduce((total, item) => {
|
|
|
|
|
return total + Object.keys(item).length;
|
|
|
|
|
}, 0);
|
2024-10-28 04:49:54 +05:30
|
|
|
}
|
2024-10-29 00:44:38 +05:30
|
|
|
|
2024-10-29 04:22:19 +05:30
|
|
|
console.log(`Extracted Items Count: ${extractedItemsCount}`);
|
|
|
|
|
console.log(`Extracted Screenshots Count: ${extractedScreenshotsCount}`);
|
|
|
|
|
console.log(`Total Rows Extracted: ${totalRowsExtracted}`);
|
2024-10-28 04:49:54 +05:30
|
|
|
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-run-created-manual',
|
|
|
|
|
{
|
2024-10-28 02:47:21 +05:30
|
|
|
runId: req.params.id,
|
|
|
|
|
user_id: req.user?.id,
|
|
|
|
|
created_at: new Date().toISOString(),
|
2024-10-28 04:17:17 +05:30
|
|
|
status: 'success',
|
2024-10-29 04:22:19 +05:30
|
|
|
totalRowsExtracted,
|
2024-10-29 00:44:38 +05:30
|
|
|
extractedItemsCount,
|
|
|
|
|
extractedScreenshotsCount,
|
2024-10-28 02:47:21 +05:30
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
)
|
2024-10-18 00:05:56 +05:30
|
|
|
try {
|
|
|
|
|
googleSheetUpdateTasks[plainRun.runId] = {
|
|
|
|
|
robotId: plainRun.robotMetaId,
|
|
|
|
|
runId: plainRun.runId,
|
|
|
|
|
status: 'pending',
|
|
|
|
|
retries: 5,
|
|
|
|
|
};
|
|
|
|
|
processGoogleSheetUpdates();
|
|
|
|
|
} catch (err: any) {
|
|
|
|
|
logger.log('error', `Failed to update Google Sheet for run: ${plainRun.runId}: ${err.message}`);
|
|
|
|
|
}
|
2024-09-19 19:36:47 +05:30
|
|
|
return res.send(true);
|
2024-09-19 17:39:33 +05:30
|
|
|
} else {
|
|
|
|
|
throw new Error('Could not destroy browser');
|
|
|
|
|
}
|
2024-07-31 21:10:25 +05:30
|
|
|
} catch (e) {
|
2024-09-19 17:39:33 +05:30
|
|
|
const { message } = e as Error;
|
2024-10-28 04:49:54 +05:30
|
|
|
// If error occurs, set run status to failed
|
|
|
|
|
const run = await Run.findOne({ where: { runId: req.params.id } });
|
|
|
|
|
if (run) {
|
|
|
|
|
await run.update({
|
|
|
|
|
status: 'failed',
|
|
|
|
|
finishedAt: new Date().toLocaleString(),
|
|
|
|
|
});
|
|
|
|
|
}
|
2024-11-29 22:13:03 +05:30
|
|
|
logger.log('info', `Error while running a robot with id: ${req.params.id} - ${message}`);
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-run-created-manual',
|
|
|
|
|
{
|
2024-10-28 04:17:17 +05:30
|
|
|
runId: req.params.id,
|
|
|
|
|
user_id: req.user?.id,
|
|
|
|
|
created_at: new Date().toISOString(),
|
|
|
|
|
status: 'failed',
|
|
|
|
|
error_message: message,
|
|
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
);
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-24 22:26:12 +05:30
|
|
|
router.put('/schedule/:id/', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
2024-09-11 12:53:28 +05:30
|
|
|
try {
|
2024-10-10 02:39:45 +05:30
|
|
|
const { id } = req.params;
|
2024-10-29 01:27:13 +05:30
|
|
|
const { runEvery, runEveryUnit, startFrom, dayOfMonth, atTimeStart, atTimeEnd, timezone } = req.body;
|
2024-09-11 23:33:04 +05:30
|
|
|
|
2024-10-22 19:20:03 +05:30
|
|
|
const robot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
|
|
|
|
if (!robot) {
|
|
|
|
|
return res.status(404).json({ error: 'Robot not found' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Validate required parameters
|
|
|
|
|
if (!runEvery || !runEveryUnit || !startFrom || !atTimeStart || !atTimeEnd || !timezone) {
|
2024-09-11 12:53:28 +05:30
|
|
|
return res.status(400).json({ error: 'Missing required parameters' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Validate time zone
|
2024-09-11 23:33:04 +05:30
|
|
|
if (!moment.tz.zone(timezone)) {
|
|
|
|
|
return res.status(400).json({ error: 'Invalid timezone' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Validate and parse start and end times
|
2024-10-22 18:26:28 +05:30
|
|
|
const [startHours, startMinutes] = atTimeStart.split(':').map(Number);
|
|
|
|
|
const [endHours, endMinutes] = atTimeEnd.split(':').map(Number);
|
2024-10-28 04:49:54 +05:30
|
|
|
|
2024-10-22 18:26:28 +05:30
|
|
|
if (isNaN(startHours) || isNaN(startMinutes) || isNaN(endHours) || isNaN(endMinutes) ||
|
2024-10-28 04:49:54 +05:30
|
|
|
startHours < 0 || startHours > 23 || startMinutes < 0 || startMinutes > 59 ||
|
|
|
|
|
endHours < 0 || endHours > 23 || endMinutes < 0 || endMinutes > 59) {
|
2024-09-11 23:33:04 +05:30
|
|
|
return res.status(400).json({ error: 'Invalid time format' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const days = ['SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY'];
|
2024-09-13 08:11:29 +05:30
|
|
|
if (!days.includes(startFrom)) {
|
2024-09-11 23:33:04 +05:30
|
|
|
return res.status(400).json({ error: 'Invalid start day' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Build cron expression based on run frequency and starting day
|
2024-09-11 23:33:04 +05:30
|
|
|
let cronExpression;
|
2024-10-26 00:48:07 +05:30
|
|
|
const dayIndex = days.indexOf(startFrom);
|
|
|
|
|
|
2024-09-13 08:11:29 +05:30
|
|
|
switch (runEveryUnit) {
|
2024-10-22 18:26:28 +05:30
|
|
|
case 'MINUTES':
|
2024-10-29 00:44:38 +05:30
|
|
|
cronExpression = `${startMinutes} */${runEvery} * * *`;
|
2024-09-11 23:33:04 +05:30
|
|
|
break;
|
2024-10-26 00:48:07 +05:30
|
|
|
case 'HOURS':
|
2024-10-29 00:44:38 +05:30
|
|
|
cronExpression = `${startMinutes} */${runEvery} * * *`;
|
2024-10-26 00:48:07 +05:30
|
|
|
break;
|
2024-09-11 23:33:04 +05:30
|
|
|
case 'DAYS':
|
2024-10-22 18:26:28 +05:30
|
|
|
cronExpression = `${startMinutes} ${startHours} */${runEvery} * *`;
|
2024-09-11 23:33:04 +05:30
|
|
|
break;
|
|
|
|
|
case 'WEEKS':
|
2024-10-26 00:48:07 +05:30
|
|
|
cronExpression = `${startMinutes} ${startHours} * * ${dayIndex}`;
|
2024-09-11 23:33:04 +05:30
|
|
|
break;
|
|
|
|
|
case 'MONTHS':
|
2024-10-29 01:27:13 +05:30
|
|
|
// todo: handle leap year
|
2024-10-29 01:47:46 +05:30
|
|
|
cronExpression = `0 ${atTimeStart} ${dayOfMonth} * *`;
|
2024-09-13 08:11:29 +05:30
|
|
|
if (startFrom !== 'SUNDAY') {
|
2024-09-11 23:33:04 +05:30
|
|
|
cronExpression += ` ${dayIndex}`;
|
|
|
|
|
}
|
|
|
|
|
break;
|
2024-10-26 00:48:07 +05:30
|
|
|
default:
|
|
|
|
|
return res.status(400).json({ error: 'Invalid runEveryUnit' });
|
2024-09-11 23:33:04 +05:30
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Validate cron expression
|
2024-09-11 23:33:04 +05:30
|
|
|
if (!cronExpression || !cron.validate(cronExpression)) {
|
|
|
|
|
return res.status(400).json({ error: 'Invalid cron expression generated' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-24 22:26:12 +05:30
|
|
|
if (!req.user) {
|
2024-10-26 00:48:07 +05:30
|
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
2024-10-24 22:26:12 +05:30
|
|
|
}
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
// Create the job in the queue with the cron expression
|
2024-10-22 19:20:03 +05:30
|
|
|
const job = await workflowQueue.add(
|
2024-10-22 16:59:18 +05:30
|
|
|
'run workflow',
|
2024-10-26 00:48:07 +05:30
|
|
|
{ id, runId: uuid(), userId: req.user.id },
|
2024-10-22 16:59:18 +05:30
|
|
|
{
|
|
|
|
|
repeat: {
|
|
|
|
|
pattern: cronExpression,
|
2024-10-26 00:48:07 +05:30
|
|
|
tz: timezone,
|
|
|
|
|
},
|
2024-10-22 16:59:18 +05:30
|
|
|
}
|
|
|
|
|
);
|
2024-09-11 23:33:04 +05:30
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
const nextRunAt = computeNextRun(cronExpression, timezone);
|
2024-10-22 19:20:03 +05:30
|
|
|
|
|
|
|
|
await robot.update({
|
|
|
|
|
schedule: {
|
|
|
|
|
runEvery,
|
|
|
|
|
runEveryUnit,
|
|
|
|
|
startFrom,
|
2024-10-29 01:27:13 +05:30
|
|
|
dayOfMonth,
|
2024-10-22 19:20:03 +05:30
|
|
|
atTimeStart,
|
|
|
|
|
atTimeEnd,
|
|
|
|
|
timezone,
|
|
|
|
|
cronExpression,
|
|
|
|
|
lastRunAt: undefined,
|
2024-10-26 00:49:26 +05:30
|
|
|
nextRunAt: nextRunAt || undefined,
|
2024-10-26 00:48:07 +05:30
|
|
|
},
|
2024-10-22 19:20:03 +05:30
|
|
|
});
|
|
|
|
|
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-robot-scheduled',
|
|
|
|
|
{
|
2024-10-28 02:47:21 +05:30
|
|
|
robotId: id,
|
|
|
|
|
user_id: req.user.id,
|
|
|
|
|
scheduled_at: new Date().toISOString(),
|
|
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
)
|
2024-10-28 02:47:21 +05:30
|
|
|
|
2024-10-22 22:01:25 +05:30
|
|
|
// Fetch updated schedule details after setting it
|
|
|
|
|
const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } });
|
|
|
|
|
|
2024-09-19 17:39:33 +05:30
|
|
|
res.status(200).json({
|
|
|
|
|
message: 'success',
|
2024-10-26 00:48:07 +05:30
|
|
|
robot: updatedRobot,
|
2024-09-11 23:33:04 +05:30
|
|
|
});
|
2024-09-11 12:53:28 +05:30
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error scheduling workflow:', error);
|
|
|
|
|
res.status(500).json({ error: 'Failed to schedule workflow' });
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-26 00:48:07 +05:30
|
|
|
|
2024-10-22 22:01:25 +05:30
|
|
|
// Endpoint to get schedule details
|
2024-10-22 19:20:03 +05:30
|
|
|
router.get('/schedule/:id', requireSignIn, async (req, res) => {
|
|
|
|
|
try {
|
2024-10-23 00:10:48 +05:30
|
|
|
const robot = await Robot.findOne({ where: { 'recording_meta.id': req.params.id }, raw: true });
|
2024-10-22 19:20:03 +05:30
|
|
|
|
|
|
|
|
if (!robot) {
|
|
|
|
|
return res.status(404).json({ error: 'Robot not found' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.status(200).json({
|
2024-10-22 23:17:39 +05:30
|
|
|
schedule: robot.schedule
|
2024-10-22 19:20:03 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error getting schedule:', error);
|
|
|
|
|
res.status(500).json({ error: 'Failed to get schedule' });
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-10-22 18:26:28 +05:30
|
|
|
|
2024-10-22 22:01:25 +05:30
|
|
|
// Endpoint to delete schedule
|
2024-10-28 02:47:21 +05:30
|
|
|
router.delete('/schedule/:id', requireSignIn, async (req: AuthenticatedRequest, res) => {
|
2024-10-22 22:01:25 +05:30
|
|
|
try {
|
|
|
|
|
const { id } = req.params;
|
|
|
|
|
|
2024-10-28 02:47:21 +05:30
|
|
|
if (!req.user) {
|
|
|
|
|
return res.status(401).json({ error: 'Unauthorized' });
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-22 22:01:25 +05:30
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
|
2024-10-29 03:44:20 +05:30
|
|
|
capture(
|
|
|
|
|
'maxun-oss-robot-schedule-deleted',
|
|
|
|
|
{
|
2024-10-28 02:47:21 +05:30
|
|
|
robotId: id,
|
|
|
|
|
user_id: req.user?.id,
|
|
|
|
|
unscheduled_at: new Date().toISOString(),
|
|
|
|
|
}
|
2024-10-29 03:44:20 +05:30
|
|
|
)
|
2024-10-28 02:47:21 +05:30
|
|
|
|
2024-10-22 22:01:25 +05:30
|
|
|
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' });
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-09-11 23:33:04 +05:30
|
|
|
|
2024-07-31 21:10:25 +05:30
|
|
|
/**
|
|
|
|
|
* POST endpoint for aborting a current interpretation of the run.
|
|
|
|
|
*/
|
2024-10-09 23:08:30 +05:30
|
|
|
router.post('/runs/abort/:id', requireSignIn, async (req, res) => {
|
2024-07-31 21:10:25 +05:30
|
|
|
try {
|
2024-10-10 02:32:46 +05:30
|
|
|
const run = await Run.findOne({ where: { runId: req.params.id } });
|
2024-10-09 23:08:30 +05:30
|
|
|
if (!run) {
|
|
|
|
|
return res.status(404).send(false);
|
|
|
|
|
}
|
2024-10-10 02:40:52 +05:30
|
|
|
const plainRun = run.toJSON();
|
2024-07-31 21:10:25 +05:30
|
|
|
|
2024-10-10 02:40:52 +05:30
|
|
|
const browser = browserPool.getRemoteBrowser(plainRun.browserId);
|
2024-07-31 21:10:25 +05:30
|
|
|
const currentLog = browser?.interpreter.debugMessages.join('/n');
|
|
|
|
|
const serializableOutput = browser?.interpreter.serializableData.reduce((reducedObject, item, index) => {
|
|
|
|
|
return {
|
|
|
|
|
[`item-${index}`]: item,
|
|
|
|
|
...reducedObject,
|
|
|
|
|
}
|
|
|
|
|
}, {});
|
|
|
|
|
const binaryOutput = browser?.interpreter.binaryData.reduce((reducedObject, item, index) => {
|
|
|
|
|
return {
|
|
|
|
|
[`item-${index}`]: item,
|
|
|
|
|
...reducedObject,
|
|
|
|
|
}
|
|
|
|
|
}, {});
|
2024-10-09 23:08:30 +05:30
|
|
|
await run.update({
|
|
|
|
|
...run,
|
2024-10-08 21:18:16 +05:30
|
|
|
status: 'aborted',
|
2024-10-09 23:08:30 +05:30
|
|
|
finishedAt: new Date().toLocaleString(),
|
2024-10-10 02:40:52 +05:30
|
|
|
browserId: plainRun.browserId,
|
2024-07-31 21:10:25 +05:30
|
|
|
log: currentLog,
|
2024-10-10 01:19:03 +05:30
|
|
|
serializableOutput,
|
|
|
|
|
binaryOutput,
|
2024-10-09 23:08:30 +05:30
|
|
|
});
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(true);
|
|
|
|
|
} catch (e) {
|
2024-09-19 17:39:33 +05:30
|
|
|
const { message } = e as Error;
|
2024-11-29 22:13:03 +05:30
|
|
|
logger.log('info', `Error while running a robot with name: ${req.params.fileName}_${req.params.runId}.json`);
|
2024-07-31 21:10:25 +05:30
|
|
|
return res.send(false);
|
|
|
|
|
}
|
2024-09-19 17:58:38 +05:30
|
|
|
});
|