4
server/src/api/run.ts
Normal file
4
server/src/api/run.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { readFile, readFiles } from "../workflow-management/storage";
|
||||||
|
import { Router, Request, Response } from 'express';
|
||||||
|
import { requireAPIKey } from "../middlewares/api";
|
||||||
|
const router = Router();
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Sequelize } from 'sequelize';
|
import { Sequelize } from 'sequelize';
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
|
import setupAssociations from '../models/associations';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
const sequelize = new Sequelize(
|
const sequelize = new Sequelize(
|
||||||
@@ -22,6 +23,7 @@ export const connectDB = async () => {
|
|||||||
|
|
||||||
export const syncDB = async () => {
|
export const syncDB = async () => {
|
||||||
try {
|
try {
|
||||||
|
//setupAssociations();
|
||||||
await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run
|
await sequelize.sync({ force: false }); // force: true will drop and recreate tables on every run
|
||||||
console.log('Database synced successfully!');
|
console.log('Database synced successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
55
server/src/models/Robot.ts
Normal file
55
server/src/models/Robot.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { Model, DataTypes, Optional } from 'sequelize';
|
||||||
|
import sequelize from '../db/config';
|
||||||
|
import { WorkflowFile, Where, What, WhereWhatPair } from 'maxun-core';
|
||||||
|
|
||||||
|
interface RobotMeta {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
createdAt: string;
|
||||||
|
pairs: number;
|
||||||
|
updatedAt: string;
|
||||||
|
params: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotWorkflow {
|
||||||
|
workflow: WhereWhatPair[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotAttributes {
|
||||||
|
id: string;
|
||||||
|
recording_meta: RobotMeta;
|
||||||
|
recording: RobotWorkflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RobotCreationAttributes extends Optional<RobotAttributes, 'id'> { }
|
||||||
|
|
||||||
|
class Robot extends Model<RobotAttributes, RobotCreationAttributes> implements RobotAttributes {
|
||||||
|
public id!: string;
|
||||||
|
public recording_meta!: RobotMeta;
|
||||||
|
public recording!: RobotWorkflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
Robot.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
recording_meta: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
recording: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: 'robot',
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Robot;
|
||||||
116
server/src/models/Run.ts
Normal file
116
server/src/models/Run.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import { Model, DataTypes, Optional } from 'sequelize';
|
||||||
|
import sequelize from '../db/config';
|
||||||
|
import Robot from './Robot';
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// 1. rename variables
|
||||||
|
// 2. we might not need interpreter settings?
|
||||||
|
// 3. store binaryOutput in MinIO
|
||||||
|
interface InterpreterSettings {
|
||||||
|
maxConcurrency: number;
|
||||||
|
maxRepeats: number;
|
||||||
|
debug: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RunAttributes {
|
||||||
|
id: string;
|
||||||
|
status: string;
|
||||||
|
name: string;
|
||||||
|
robotId: string;
|
||||||
|
robotMetaId: string;
|
||||||
|
startedAt: string;
|
||||||
|
finishedAt: string;
|
||||||
|
browserId: string;
|
||||||
|
interpreterSettings: InterpreterSettings;
|
||||||
|
log: string;
|
||||||
|
runId: string;
|
||||||
|
serializableOutput: Record<string, any[]>;
|
||||||
|
binaryOutput: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RunCreationAttributes extends Optional<RunAttributes, 'id'> { }
|
||||||
|
|
||||||
|
class Run extends Model<RunAttributes, RunCreationAttributes> implements RunAttributes {
|
||||||
|
public id!: string;
|
||||||
|
public status!: string;
|
||||||
|
public name!: string;
|
||||||
|
public robotId!: string;
|
||||||
|
public robotMetaId!: string;
|
||||||
|
public startedAt!: string;
|
||||||
|
public finishedAt!: string;
|
||||||
|
public browserId!: string;
|
||||||
|
public interpreterSettings!: InterpreterSettings;
|
||||||
|
public log!: string;
|
||||||
|
public runId!: string;
|
||||||
|
public serializableOutput!: Record<string, any[]>;
|
||||||
|
public binaryOutput!: Record<string, any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Run.init(
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
defaultValue: DataTypes.UUIDV4,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: DataTypes.STRING(50),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING(255),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
robotId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
references: {
|
||||||
|
model: Robot,
|
||||||
|
key: 'id',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
robotMetaId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
startedAt: {
|
||||||
|
type: DataTypes.STRING(255),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
finishedAt: {
|
||||||
|
type: DataTypes.STRING(255),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
browserId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
interpreterSettings: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
log: {
|
||||||
|
type: DataTypes.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
runId: {
|
||||||
|
type: DataTypes.UUID,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
serializableOutput: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
binaryOutput: {
|
||||||
|
type: DataTypes.JSONB,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
sequelize,
|
||||||
|
tableName: 'run',
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Run;
|
||||||
7
server/src/models/associations.ts
Normal file
7
server/src/models/associations.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import Robot from './Robot';
|
||||||
|
import Run from './Run';
|
||||||
|
|
||||||
|
export default function setupAssociations() {
|
||||||
|
Run.belongsTo(Robot, { foreignKey: 'robotId' });
|
||||||
|
Robot.hasMany(Run, { foreignKey: 'robotId' });
|
||||||
|
}
|
||||||
@@ -11,12 +11,14 @@ import cron from 'node-cron';
|
|||||||
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from '../workflow-management/integrations/gsheet';
|
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from '../workflow-management/integrations/gsheet';
|
||||||
import { getDecryptedProxyConfig } from './proxy';
|
import { getDecryptedProxyConfig } from './proxy';
|
||||||
import { requireSignIn } from '../middlewares/auth';
|
import { requireSignIn } from '../middlewares/auth';
|
||||||
// import { workflowQueue } from '../worker';
|
import Robot from '../models/Robot';
|
||||||
|
import Run from '../models/Run';
|
||||||
|
import { workflowQueue } from '../worker';
|
||||||
|
|
||||||
// todo: move from here
|
// todo: move from here
|
||||||
export const getRecordingByFileName = async (fileName: string): Promise<any | null> => {
|
export const getRecordingByFileName = async (fileName: string): Promise<any | null> => {
|
||||||
try {
|
try {
|
||||||
const recording = await readFile(`./../storage/recordings/${fileName}.waw.json`)
|
const recording = await readFile(`./../storage/recordings/${fileName}.json`)
|
||||||
const parsedRecording = JSON.parse(recording);
|
const parsedRecording = JSON.parse(recording);
|
||||||
return parsedRecording;
|
return parsedRecording;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -40,7 +42,7 @@ router.all('/', requireSignIn, (req, res, next) => {
|
|||||||
*/
|
*/
|
||||||
router.get('/recordings', requireSignIn, async (req, res) => {
|
router.get('/recordings', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = await readFiles('./../storage/recordings/');
|
const data = await Robot.findAll();
|
||||||
return res.send(data);
|
return res.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log('info', 'Error while reading recordings');
|
logger.log('info', 'Error while reading recordings');
|
||||||
@@ -51,13 +53,15 @@ router.get('/recordings', requireSignIn, async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* DELETE endpoint for deleting a recording from the storage.
|
* DELETE endpoint for deleting a recording from the storage.
|
||||||
*/
|
*/
|
||||||
router.delete('/recordings/:fileName', requireSignIn, async (req, res) => {
|
router.delete('/recordings/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await deleteFile(`./../storage/recordings/${req.params.fileName}.waw.json`);
|
await Robot.destroy({
|
||||||
|
where: { 'recording_meta.id': req.params.id }
|
||||||
|
});
|
||||||
return res.send(true);
|
return res.send(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while deleting a recording with name: ${req.params.fileName}.waw.json`);
|
logger.log('info', `Error while deleting a recording with name: ${req.params.fileName}.json`);
|
||||||
return res.send(false);
|
return res.send(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -67,7 +71,7 @@ router.delete('/recordings/:fileName', requireSignIn, async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
router.get('/runs', requireSignIn, async (req, res) => {
|
router.get('/runs', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const data = await readFiles('./../storage/runs/');
|
const data = await Run.findAll();
|
||||||
return res.send(data);
|
return res.send(data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.log('info', 'Error while reading runs');
|
logger.log('info', 'Error while reading runs');
|
||||||
@@ -78,9 +82,9 @@ router.get('/runs', requireSignIn, async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* DELETE endpoint for deleting a run from the storage.
|
* DELETE endpoint for deleting a run from the storage.
|
||||||
*/
|
*/
|
||||||
router.delete('/runs/:fileName', requireSignIn, async (req, res) => {
|
router.delete('/runs/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await deleteFile(`./../storage/runs/${req.params.fileName}.json`);
|
await Run.destroy({ where: { runId: req.params.id } });
|
||||||
return res.send(true);
|
return res.send(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
@@ -93,9 +97,17 @@ router.delete('/runs/:fileName', requireSignIn, async (req, res) => {
|
|||||||
* PUT endpoint for starting a remote browser instance and saving run metadata to the storage.
|
* PUT endpoint for starting a remote browser instance and saving run metadata to the storage.
|
||||||
* Making it ready for interpretation and returning a runId.
|
* Making it ready for interpretation and returning a runId.
|
||||||
*/
|
*/
|
||||||
router.put('/runs/:fileName', requireSignIn, async (req, res) => {
|
router.put('/runs/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const recording = await getRecordingByFileName(req.params.fileName);
|
console.log(`Params recieved:`, req.params)
|
||||||
|
const recording = await Robot.findOne({
|
||||||
|
where: {
|
||||||
|
'recording_meta.id': req.params.id
|
||||||
|
},
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Recording found:`, recording)
|
||||||
|
|
||||||
if (!recording || !recording.recording_meta || !recording.recording_meta.id) {
|
if (!recording || !recording.recording_meta || !recording.recording_meta.id) {
|
||||||
return res.status(404).send({ error: 'Recording not found' });
|
return res.status(404).send({ error: 'Recording not found' });
|
||||||
@@ -124,33 +136,41 @@ router.put('/runs/:fileName', requireSignIn, async (req, res) => {
|
|||||||
|
|
||||||
const runId = uuid();
|
const runId = uuid();
|
||||||
|
|
||||||
const run_meta = {
|
const run = await Run.create({
|
||||||
status: 'RUNNING',
|
status: 'RUNNING',
|
||||||
name: req.params.fileName,
|
name: recording.recording_meta.name,
|
||||||
recordingId: recording.recording_meta.id,
|
robotId: recording.id,
|
||||||
|
robotMetaId: recording.recording_meta.id,
|
||||||
startedAt: new Date().toLocaleString(),
|
startedAt: new Date().toLocaleString(),
|
||||||
finishedAt: '',
|
finishedAt: '',
|
||||||
browserId: id,
|
browserId: id,
|
||||||
interpreterSettings: req.body,
|
interpreterSettings: req.body,
|
||||||
log: '',
|
log: '',
|
||||||
runId,
|
runId,
|
||||||
};
|
serializableOutput: {},
|
||||||
fs.mkdirSync('../storage/runs', { recursive: true })
|
binaryOutput: {},
|
||||||
await saveFile(
|
});
|
||||||
`../storage/runs/${req.params.fileName}_${runId}.json`,
|
|
||||||
JSON.stringify({ ...run_meta }, null, 2)
|
|
||||||
);
|
|
||||||
logger.log('debug', `Created run with name: ${req.params.fileName}.json`);
|
|
||||||
|
|
||||||
console.log('Run meta:', run_meta);
|
const plainRun = run.toJSON();
|
||||||
|
|
||||||
|
console.log(`Created run (plain object):`, plainRun);
|
||||||
|
|
||||||
|
// // we need to handle this via DB
|
||||||
|
// fs.mkdirSync('../storage/runs', { recursive: true })
|
||||||
|
// await saveFile(
|
||||||
|
// `../storage/runs/${req.params.fileName}_${runId}.json`,
|
||||||
|
// JSON.stringify({ ...run_meta }, null, 2)
|
||||||
|
// );
|
||||||
|
// logger.log('debug', `Created run with name: ${req.params.fileName}.json`);
|
||||||
|
|
||||||
|
// console.log('Run meta:', run_meta);
|
||||||
return res.send({
|
return res.send({
|
||||||
browserId: id,
|
browserId: id,
|
||||||
runId: runId,
|
runId: plainRun.runId,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while creating a run with name: ${req.params.fileName}.json`);
|
logger.log('info', `Error while creating a run with recording id: ${req.params.id} - ${message}`);
|
||||||
return res.send('');
|
return res.send('');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -158,15 +178,19 @@ router.put('/runs/:fileName', requireSignIn, async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* GET endpoint for getting a run from the storage.
|
* GET endpoint for getting a run from the storage.
|
||||||
*/
|
*/
|
||||||
router.get('/runs/run/:fileName/:runId', requireSignIn, async (req, res) => {
|
router.get('/runs/run/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
console.log(`Params for GET /runs/run/:id`, req.params.id)
|
||||||
// read the run from storage
|
// read the run from storage
|
||||||
const run = await readFile(`./../storage/runs/${req.params.fileName}_${req.params.runId}.json`)
|
const run = await Run.findOne({ where: { runId: req.params.runId }, raw: true });
|
||||||
const parsedRun = JSON.parse(run);
|
//const parsedRun = JSON.parse(run);
|
||||||
return res.send(parsedRun);
|
if (!run) {
|
||||||
|
return res.status(404).send(null);
|
||||||
|
}
|
||||||
|
return res.send(run);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('error', `Error ${message} while reading a run with name: ${req.params.fileName}_${req.params.runId}.json`);
|
logger.log('error', `Error ${message} while reading a run with id: ${req.params.id}.json`);
|
||||||
return res.send(null);
|
return res.send(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -174,38 +198,48 @@ router.get('/runs/run/:fileName/:runId', requireSignIn, async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* PUT endpoint for finishing a run and saving it to the storage.
|
* PUT endpoint for finishing a run and saving it to the storage.
|
||||||
*/
|
*/
|
||||||
router.post('/runs/run/:fileName/:runId', requireSignIn, async (req, res) => {
|
router.post('/runs/run/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const recording = await readFile(`./../storage/recordings/${req.params.fileName}.waw.json`)
|
// const recording = await readFile(`./../storage/recordings/${req.params.fileName}.json`)
|
||||||
const parsedRecording = JSON.parse(recording);
|
// const parsedRecording = JSON.parse(recording);
|
||||||
|
|
||||||
const run = await readFile(`./../storage/runs/${req.params.fileName}_${req.params.runId}.json`)
|
// const run = await readFile(`./../storage/runs/${req.params.fileName}_${req.params.runId}.json`)
|
||||||
const parsedRun = JSON.parse(run);
|
// const parsedRun = JSON.parse(run);
|
||||||
|
console.log(`Params for POST /runs/run/:id`, req.params.id)
|
||||||
|
|
||||||
|
const run = await Run.findOne({ where: { runId: req.params.id } });
|
||||||
|
if (!run) {
|
||||||
|
return res.status(404).send(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`found run: ${run}`)
|
||||||
|
|
||||||
|
const plainRun = run.toJSON();
|
||||||
|
|
||||||
|
const recording = await Robot.findOne({ where: { 'recording_meta.id': plainRun.robotMetaId }, raw: true });
|
||||||
|
if (!recording) {
|
||||||
|
return res.status(404).send(false);
|
||||||
|
}
|
||||||
|
|
||||||
// interpret the run in active browser
|
// interpret the run in active browser
|
||||||
const browser = browserPool.getRemoteBrowser(parsedRun.browserId);
|
const browser = browserPool.getRemoteBrowser(plainRun.browserId);
|
||||||
const currentPage = browser?.getCurrentPage();
|
const currentPage = browser?.getCurrentPage();
|
||||||
if (browser && currentPage) {
|
if (browser && currentPage) {
|
||||||
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
||||||
parsedRecording.recording, currentPage, parsedRun.interpreterSettings);
|
recording.recording, currentPage, plainRun.interpreterSettings);
|
||||||
await destroyRemoteBrowser(parsedRun.browserId);
|
await destroyRemoteBrowser(plainRun.browserId);
|
||||||
const run_meta = {
|
await run.update({
|
||||||
...parsedRun,
|
...run,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
finishedAt: new Date().toLocaleString(),
|
finishedAt: new Date().toLocaleString(),
|
||||||
browserId: parsedRun.browserId,
|
browserId: plainRun.browserId,
|
||||||
log: interpretationInfo.log.join('\n'),
|
log: interpretationInfo.log.join('\n'),
|
||||||
serializableOutput: interpretationInfo.serializableOutput,
|
serializableOutput: interpretationInfo.serializableOutput,
|
||||||
binaryOutput: interpretationInfo.binaryOutput,
|
binaryOutput: interpretationInfo.binaryOutput,
|
||||||
};
|
});
|
||||||
fs.mkdirSync('../storage/runs', { recursive: true })
|
googleSheetUpdateTasks[req.params.id] = {
|
||||||
await saveFile(
|
name: plainRun.name,
|
||||||
`../storage/runs/${parsedRun.name}_${req.params.runId}.json`,
|
runId: plainRun.runId,
|
||||||
JSON.stringify(run_meta, null, 2)
|
|
||||||
);
|
|
||||||
googleSheetUpdateTasks[req.params.runId] = {
|
|
||||||
name: parsedRun.name,
|
|
||||||
runId: req.params.runId,
|
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
retries: 5,
|
retries: 5,
|
||||||
};
|
};
|
||||||
@@ -216,15 +250,15 @@ router.post('/runs/run/:fileName/:runId', requireSignIn, async (req, res) => {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while running a recording with name: ${req.params.fileName}_${req.params.runId}.json`);
|
logger.log('info', `Error while running a recording with id: ${req.params.id} - ${message}`);
|
||||||
return res.send(false);
|
return res.send(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put('/schedule/:fileName/', requireSignIn, async (req, res) => {
|
router.put('/schedule/:id/', requireSignIn, async (req, res) => {
|
||||||
console.log(req.body);
|
console.log(req.body);
|
||||||
try {
|
try {
|
||||||
const { fileName } = req.params;
|
const { id } = req.params;
|
||||||
const {
|
const {
|
||||||
runEvery,
|
runEvery,
|
||||||
runEveryUnit,
|
runEveryUnit,
|
||||||
@@ -233,7 +267,7 @@ router.put('/schedule/:fileName/', requireSignIn, async (req, res) => {
|
|||||||
timezone
|
timezone
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
if (!fileName || !runEvery || !runEveryUnit || !startFrom || !atTime || !timezone) {
|
if (!id || !runEvery || !runEveryUnit || !startFrom || !atTime || !timezone) {
|
||||||
return res.status(400).json({ error: 'Missing required parameters' });
|
return res.status(400).json({ error: 'Missing required parameters' });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,17 +315,18 @@ router.put('/schedule/:fileName/', requireSignIn, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const runId = uuid();
|
const runId = uuid();
|
||||||
|
const userId = req.user.id;
|
||||||
|
|
||||||
// await workflowQueue.add(
|
await workflowQueue.add(
|
||||||
// 'run workflow',
|
'run workflow',
|
||||||
// { fileName, runId },
|
{ id, runId, userId },
|
||||||
// {
|
{
|
||||||
// repeat: {
|
repeat: {
|
||||||
// pattern: cronExpression,
|
pattern: cronExpression,
|
||||||
// tz: timezone
|
tz: timezone
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// );
|
);
|
||||||
|
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
message: 'success',
|
message: 'success',
|
||||||
@@ -316,12 +351,16 @@ router.put('/schedule/:fileName/', requireSignIn, async (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* POST endpoint for aborting a current interpretation of the run.
|
* POST endpoint for aborting a current interpretation of the run.
|
||||||
*/
|
*/
|
||||||
router.post('/runs/abort/:fileName/:runId', requireSignIn, async (req, res) => {
|
router.post('/runs/abort/:id', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const run = await readFile(`./../storage/runs/${req.params.fileName}_${req.params.runId}.json`)
|
console.log(`Params for POST /runs/abort/:id`, req.params.id)
|
||||||
const parsedRun = JSON.parse(run);
|
const run = await Run.findOne({ where: { runId: req.params.id } });
|
||||||
|
if (!run) {
|
||||||
|
return res.status(404).send(false);
|
||||||
|
}
|
||||||
|
const plainRun = run.toJSON();
|
||||||
|
|
||||||
const browser = browserPool.getRemoteBrowser(parsedRun.browserId);
|
const browser = browserPool.getRemoteBrowser(plainRun.browserId);
|
||||||
const currentLog = browser?.interpreter.debugMessages.join('/n');
|
const currentLog = browser?.interpreter.debugMessages.join('/n');
|
||||||
const serializableOutput = browser?.interpreter.serializableData.reduce((reducedObject, item, index) => {
|
const serializableOutput = browser?.interpreter.serializableData.reduce((reducedObject, item, index) => {
|
||||||
return {
|
return {
|
||||||
@@ -335,19 +374,21 @@ router.post('/runs/abort/:fileName/:runId', requireSignIn, async (req, res) => {
|
|||||||
...reducedObject,
|
...reducedObject,
|
||||||
}
|
}
|
||||||
}, {});
|
}, {});
|
||||||
const run_meta = {
|
await run.update({
|
||||||
...parsedRun,
|
...run,
|
||||||
status: 'aborted',
|
status: 'aborted',
|
||||||
finishedAt: null,
|
finishedAt: new Date().toLocaleString(),
|
||||||
browserId: null,
|
browserId: plainRun.browserId,
|
||||||
log: currentLog,
|
log: currentLog,
|
||||||
};
|
serializableOutput,
|
||||||
|
binaryOutput,
|
||||||
|
});
|
||||||
|
|
||||||
fs.mkdirSync('../storage/runs', { recursive: true })
|
// fs.mkdirSync('../storage/runs', { recursive: true })
|
||||||
await saveFile(
|
// await saveFile(
|
||||||
`../storage/runs/${parsedRun.name}_${req.params.runId}.json`,
|
// `../storage/runs/${run.name}_${req.params.runId}.json`,
|
||||||
JSON.stringify({ ...run_meta, serializableOutput, binaryOutput }, null, 2)
|
// JSON.stringify({ ...run_meta, serializableOutput, binaryOutput }, null, 2)
|
||||||
);
|
// );
|
||||||
return res.send(true);
|
return res.send(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import logger from "../logger";
|
|||||||
import { browserPool } from "../server";
|
import { browserPool } from "../server";
|
||||||
import { readFile } from "../workflow-management/storage";
|
import { readFile } from "../workflow-management/storage";
|
||||||
import { requireSignIn } from '../middlewares/auth';
|
import { requireSignIn } from '../middlewares/auth';
|
||||||
|
import Robot from '../models/Robot';
|
||||||
|
|
||||||
export const router = Router();
|
export const router = Router();
|
||||||
|
|
||||||
@@ -102,23 +103,60 @@ router.put('/pair/:index', requireSignIn, (req, res) => {
|
|||||||
/**
|
/**
|
||||||
* PUT endpoint for updating the currently generated workflow file from the one in the storage.
|
* PUT endpoint for updating the currently generated workflow file from the one in the storage.
|
||||||
*/
|
*/
|
||||||
router.put('/:browserId/:fileName', requireSignIn, async (req, res) => {
|
// router.put('/:browserId/:fileName', requireSignIn, async (req, res) => {
|
||||||
|
// try {
|
||||||
|
// const browser = browserPool.getRemoteBrowser(req.params.browserId);
|
||||||
|
// logger.log('debug', `Updating workflow file`);
|
||||||
|
// if (browser && browser.generator) {
|
||||||
|
// const recording = await readFile(`./../storage/recordings/${req.params.fileName}.json`)
|
||||||
|
// const parsedRecording = JSON.parse(recording);
|
||||||
|
// if (parsedRecording.recording) {
|
||||||
|
// browser.generator?.updateWorkflowFile(parsedRecording.recording, parsedRecording.recording_meta);
|
||||||
|
// const workflowFile = browser.generator?.getWorkflowFile();
|
||||||
|
// return res.send(workflowFile);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return res.send(null);
|
||||||
|
// } catch (e) {
|
||||||
|
// const { message } = e as Error;
|
||||||
|
// logger.log('info', `Error while reading a recording with name: ${req.params.fileName}.json`);
|
||||||
|
// return res.send(null);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
|
||||||
|
router.put('/:browserId/:robotId', requireSignIn, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const browser = browserPool.getRemoteBrowser(req.params.browserId);
|
const browser = browserPool.getRemoteBrowser(req.params.browserId);
|
||||||
logger.log('debug', `Updating workflow file`);
|
logger.log('debug', `Updating workflow for Robot ID: ${req.params.robotId}`);
|
||||||
|
|
||||||
if (browser && browser.generator) {
|
if (browser && browser.generator) {
|
||||||
const recording = await readFile(`./../storage/recordings/${req.params.fileName}.waw.json`)
|
const robot = await Robot.findByPk(req.params.robotId);
|
||||||
const parsedRecording = JSON.parse(recording);
|
|
||||||
if (parsedRecording.recording) {
|
if (!robot) {
|
||||||
browser.generator?.updateWorkflowFile(parsedRecording.recording, parsedRecording.recording_meta);
|
logger.log('info', `Robot not found with ID: ${req.params.robotId}`);
|
||||||
const workflowFile = browser.generator?.getWorkflowFile();
|
return res.status(404).send({ error: 'Robot not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { recording, recording_meta } = robot;
|
||||||
|
|
||||||
|
if (recording && recording.workflow) {
|
||||||
|
browser.generator.updateWorkflowFile(recording, recording_meta);
|
||||||
|
const workflowFile = browser.generator.getWorkflowFile();
|
||||||
return res.send(workflowFile);
|
return res.send(workflowFile);
|
||||||
|
} else {
|
||||||
|
logger.log('info', `Invalid recording data for Robot ID: ${req.params.robotId}`);
|
||||||
|
return res.status(400).send({ error: 'Invalid recording data' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res.send(null);
|
|
||||||
|
logger.log('info', `Browser or generator not available for ID: ${req.params.browserId}`);
|
||||||
|
return res.status(400).send({ error: 'Browser or generator not available' });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while reading a recording with name: ${req.params.fileName}.waw.json`);
|
logger.log('error', `Error while updating workflow for Robot ID: ${req.params.robotId}. Error: ${message}`);
|
||||||
return res.send(null);
|
return res.status(500).send({ error: 'Internal server error' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -20,9 +20,9 @@ connection.on('error', (err) => {
|
|||||||
const workflowQueue = new Queue('workflow', { connection });
|
const workflowQueue = new Queue('workflow', { connection });
|
||||||
|
|
||||||
const worker = new Worker('workflow', async job => {
|
const worker = new Worker('workflow', async job => {
|
||||||
const { fileName, runId } = job.data;
|
const { runId, userId } = job.data;
|
||||||
try {
|
try {
|
||||||
const result = await handleRunRecording(fileName, runId);
|
const result = await handleRunRecording(runId, userId);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error running workflow:', error);
|
logger.error('Error running workflow:', error);
|
||||||
@@ -31,11 +31,11 @@ const worker = new Worker('workflow', async job => {
|
|||||||
}, { connection });
|
}, { connection });
|
||||||
|
|
||||||
worker.on('completed', async (job: any) => {
|
worker.on('completed', async (job: any) => {
|
||||||
logger.log(`info`, `Job ${job.id} completed for ${job.data.fileName}_${job.data.runId}`);
|
logger.log(`info`, `Job ${job.id} completed for ${job.data.runId}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
worker.on('failed', async (job: any, err) => {
|
worker.on('failed', async (job: any, err) => {
|
||||||
logger.log(`error`, `Job ${job.id} failed for ${job.data.fileName}_${job.data.runId}:`, err);
|
logger.log(`error`, `Job ${job.id} failed for ${job.data.runId}:`, err);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Worker is running...');
|
console.log('Worker is running...');
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {
|
|||||||
} from "../selector";
|
} from "../selector";
|
||||||
import { CustomActions } from "../../../../src/shared/types";
|
import { CustomActions } from "../../../../src/shared/types";
|
||||||
import { workflow } from "../../routes";
|
import { workflow } from "../../routes";
|
||||||
|
import Robot from "../../models/Robot";
|
||||||
|
import Run from "../../models/Run";
|
||||||
import { saveFile } from "../storage";
|
import { saveFile } from "../storage";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { getBestSelectorForAction } from "../utils";
|
import { getBestSelectorForAction } from "../utils";
|
||||||
@@ -486,11 +488,12 @@ export class WorkflowGenerator {
|
|||||||
updatedAt: new Date().toLocaleString(),
|
updatedAt: new Date().toLocaleString(),
|
||||||
params: this.getParams() || [],
|
params: this.getParams() || [],
|
||||||
}
|
}
|
||||||
fs.mkdirSync('../storage/recordings', { recursive: true })
|
const robot = await Robot.create({
|
||||||
await saveFile(
|
recording_meta: this.recordingMeta,
|
||||||
`../storage/recordings/${fileName}.waw.json`,
|
recording: recording,
|
||||||
JSON.stringify({ recording_meta: this.recordingMeta, recording }, null, 2)
|
});
|
||||||
);
|
|
||||||
|
logger.log('info', `Robot saved with id: ${robot.id}`);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
|
|||||||
@@ -8,55 +8,76 @@ import logger from '../../logger';
|
|||||||
import { browserPool } from "../../server";
|
import { browserPool } from "../../server";
|
||||||
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from "../integrations/gsheet";
|
import { googleSheetUpdateTasks, processGoogleSheetUpdates } from "../integrations/gsheet";
|
||||||
import { getRecordingByFileName } from "../../routes/storage";
|
import { getRecordingByFileName } from "../../routes/storage";
|
||||||
|
import Robot from "../../models/Robot";
|
||||||
|
import Run from "../../models/Run";
|
||||||
|
import { getDecryptedProxyConfig } from "../../routes/proxy";
|
||||||
|
|
||||||
async function runWorkflow(fileName: string, runId: string) {
|
async function runWorkflow(id: string, userId: string) {
|
||||||
if (!runId) {
|
if (!id) {
|
||||||
runId = uuid();
|
id = uuid();
|
||||||
}
|
}
|
||||||
|
|
||||||
const recording = await getRecordingByFileName(fileName);
|
const recording = await Robot.findOne({
|
||||||
|
where: {
|
||||||
|
'recording_meta.id': id
|
||||||
|
},
|
||||||
|
raw: true
|
||||||
|
});
|
||||||
|
|
||||||
if (!recording || !recording.recording_meta || !recording.recording_meta.id) {
|
if (!recording || !recording.recording_meta || !recording.recording_meta.id) {
|
||||||
logger.log('info', `Recording with name: ${fileName} not found`);
|
return {
|
||||||
return {
|
success: false,
|
||||||
success: false,
|
error: 'Recording not found'
|
||||||
error: `Recording with name: ${fileName} not found`,
|
};
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
const proxyConfig = await getDecryptedProxyConfig(userId);
|
||||||
|
let proxyOptions: any = {};
|
||||||
|
|
||||||
|
if (proxyConfig.proxy_url) {
|
||||||
|
proxyOptions = {
|
||||||
|
server: proxyConfig.proxy_url,
|
||||||
|
...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
|
||||||
|
username: proxyConfig.proxy_username,
|
||||||
|
password: proxyConfig.proxy_password,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const browserId = createRemoteBrowserForRun({
|
const browserId = createRemoteBrowserForRun({
|
||||||
browser: chromium,
|
browser: chromium,
|
||||||
launchOptions: { headless: true }
|
launchOptions: {
|
||||||
|
headless: true,
|
||||||
|
proxy: proxyOptions.server ? proxyOptions : undefined,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
const run_meta = {
|
|
||||||
|
const run = await Run.create({
|
||||||
status: 'Scheduled',
|
status: 'Scheduled',
|
||||||
name: fileName,
|
name: recording.recording_meta.name,
|
||||||
recordingId: recording.recording_meta.id,
|
robotId: recording.id,
|
||||||
|
robotMetaId: recording.recording_meta.id,
|
||||||
startedAt: new Date().toLocaleString(),
|
startedAt: new Date().toLocaleString(),
|
||||||
finishedAt: '',
|
finishedAt: '',
|
||||||
browserId: browserId,
|
browserId: id,
|
||||||
interpreterSettings: { maxConcurrency: 1, maxRepeats: 1, debug: true },
|
interpreterSettings: { maxConcurrency: 1, maxRepeats: 1, debug: true },
|
||||||
log: '',
|
log: '',
|
||||||
runId: runId,
|
runId: id,
|
||||||
};
|
serializableOutput: {},
|
||||||
|
binaryOutput: {},
|
||||||
|
});
|
||||||
|
|
||||||
fs.mkdirSync('../storage/runs', { recursive: true });
|
const plainRun = run.toJSON();
|
||||||
await saveFile(
|
|
||||||
`../storage/runs/${fileName}_${runId}.json`,
|
|
||||||
JSON.stringify(run_meta, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.log('debug', `Scheduled run with name: ${fileName}_${runId}.json`);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
browserId,
|
browserId,
|
||||||
runId
|
runId: plainRun.runId,
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { message } = e as Error;
|
const { message } = e as Error;
|
||||||
logger.log('info', `Error while scheduling a run with name: ${fileName}_${runId}.json`);
|
logger.log('info', `Error while scheduling a run with id: ${id}`);
|
||||||
console.log(message);
|
console.log(message);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -65,21 +86,29 @@ async function runWorkflow(fileName: string, runId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeRun(fileName: string, runId: string) {
|
async function executeRun(id: string) {
|
||||||
try {
|
try {
|
||||||
const recording = await readFile(`./../storage/recordings/${fileName}.waw.json`);
|
const run = await Run.findOne({ where: { runId: id } });
|
||||||
const parsedRecording = JSON.parse(recording);
|
if (!run) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: 'Run not found'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const run = await readFile(`./../storage/runs/${fileName}_${runId}.json`);
|
const plainRun = run.toJSON();
|
||||||
const parsedRun = JSON.parse(run);
|
|
||||||
|
|
||||||
parsedRun.status = 'running';
|
const recording = await Robot.findOne({ where: { 'recording_meta.id': plainRun.robotMetaId }, raw: true });
|
||||||
await saveFile(
|
if (!recording) {
|
||||||
`../storage/runs/${fileName}_${runId}.json`,
|
return {
|
||||||
JSON.stringify(parsedRun, null, 2)
|
success: false,
|
||||||
);
|
error: 'Recording not found'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const browser = browserPool.getRemoteBrowser(parsedRun.browserId);
|
plainRun.status = 'running';
|
||||||
|
|
||||||
|
const browser = browserPool.getRemoteBrowser(plainRun.browserId);
|
||||||
if (!browser) {
|
if (!browser) {
|
||||||
throw new Error('Could not access browser');
|
throw new Error('Could not access browser');
|
||||||
}
|
}
|
||||||
@@ -90,61 +119,47 @@ async function executeRun(fileName: string, runId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
const interpretationInfo = await browser.interpreter.InterpretRecording(
|
||||||
parsedRecording.recording, currentPage, parsedRun.interpreterSettings);
|
recording.recording, currentPage, plainRun.interpreterSettings);
|
||||||
|
|
||||||
await destroyRemoteBrowser(parsedRun.browserId);
|
await destroyRemoteBrowser(plainRun.browserId);
|
||||||
|
|
||||||
const updated_run_meta = {
|
await run.update({
|
||||||
...parsedRun,
|
...run,
|
||||||
status: 'success',
|
status: 'success',
|
||||||
finishedAt: new Date().toLocaleString(),
|
finishedAt: new Date().toLocaleString(),
|
||||||
browserId: parsedRun.browserId,
|
browserId: plainRun.browserId,
|
||||||
log: interpretationInfo.log.join('\n'),
|
log: interpretationInfo.log.join('\n'),
|
||||||
serializableOutput: interpretationInfo.serializableOutput,
|
serializableOutput: interpretationInfo.serializableOutput,
|
||||||
binaryOutput: interpretationInfo.binaryOutput,
|
binaryOutput: interpretationInfo.binaryOutput,
|
||||||
};
|
});
|
||||||
|
|
||||||
await saveFile(
|
googleSheetUpdateTasks[id] = {
|
||||||
`../storage/runs/${fileName}_${runId}.json`,
|
name: plainRun.name,
|
||||||
JSON.stringify(updated_run_meta, null, 2)
|
runId: id,
|
||||||
);
|
|
||||||
googleSheetUpdateTasks[runId] = {
|
|
||||||
name: parsedRun.name,
|
|
||||||
runId: runId,
|
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
retries: 5,
|
retries: 5,
|
||||||
};
|
};
|
||||||
processGoogleSheetUpdates();
|
processGoogleSheetUpdates();
|
||||||
return true;
|
return true;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.log('info', `Error while running a recording with name: ${fileName}_${runId}.json`);
|
logger.log('info', `Error while running a recording with id: ${id} - ${error.message}`);
|
||||||
console.log(error.message);
|
console.log(error.message);
|
||||||
|
|
||||||
const errorRun = await readFile(`./../storage/runs/${fileName}_${runId}.json`);
|
|
||||||
const parsedErrorRun = JSON.parse(errorRun);
|
|
||||||
parsedErrorRun.status = 'ERROR';
|
|
||||||
parsedErrorRun.log += `\nError: ${error.message}`;
|
|
||||||
await saveFile(
|
|
||||||
`../storage/runs/${fileName}_${runId}.json`,
|
|
||||||
JSON.stringify(parsedErrorRun, null, 2)
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function readyForRunHandler(browserId: string, fileName: string, runId: string) {
|
async function readyForRunHandler(browserId: string, id: string) {
|
||||||
try {
|
try {
|
||||||
const interpretation = await executeRun(fileName, runId);
|
const interpretation = await executeRun(id);
|
||||||
|
|
||||||
if (interpretation) {
|
if (interpretation) {
|
||||||
logger.log('info', `Interpretation of ${fileName} succeeded`);
|
logger.log('info', `Interpretation of ${id} succeeded`);
|
||||||
} else {
|
} else {
|
||||||
logger.log('error', `Interpretation of ${fileName} failed`);
|
logger.log('error', `Interpretation of ${id} failed`);
|
||||||
await destroyRemoteBrowser(browserId);
|
await destroyRemoteBrowser(browserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
resetRecordingState(browserId, fileName, runId);
|
resetRecordingState(browserId, id);
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(`Error during readyForRunHandler: ${error.message}`);
|
logger.error(`Error during readyForRunHandler: ${error.message}`);
|
||||||
@@ -152,20 +167,18 @@ async function readyForRunHandler(browserId: string, fileName: string, runId: st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetRecordingState(browserId: string, fileName: string, runId: string) {
|
function resetRecordingState(browserId: string, id: string) {
|
||||||
browserId = '';
|
browserId = '';
|
||||||
fileName = '';
|
id = '';
|
||||||
runId = '';
|
|
||||||
logger.log(`info`, `reset values for ${browserId}, ${fileName}, and ${runId}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function handleRunRecording(fileName: string, runId: string) {
|
export async function handleRunRecording(id: string, userId: string) {
|
||||||
try {
|
try {
|
||||||
const result = await runWorkflow(fileName, runId);
|
const result = await runWorkflow(id, userId);
|
||||||
const { browserId, runId: newRunId } = result;
|
const { browserId, runId: newRunId } = result;
|
||||||
|
|
||||||
if (!browserId || !newRunId) {
|
if (!browserId || !newRunId || !userId) {
|
||||||
throw new Error('browserId or runId is undefined');
|
throw new Error('browserId or runId or userId is undefined');
|
||||||
}
|
}
|
||||||
|
|
||||||
const socket = io(`http://localhost:8080/${browserId}`, {
|
const socket = io(`http://localhost:8080/${browserId}`, {
|
||||||
@@ -173,9 +186,9 @@ export async function handleRunRecording(fileName: string, runId: string) {
|
|||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('ready-for-run', () => readyForRunHandler(browserId, fileName, newRunId));
|
socket.on('ready-for-run', () => readyForRunHandler(browserId, newRunId));
|
||||||
|
|
||||||
logger.log('info', `Running recording: ${fileName}`);
|
logger.log('info', `Running recording: ${id}`);
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
cleanupSocketListeners(socket, browserId, newRunId);
|
cleanupSocketListeners(socket, browserId, newRunId);
|
||||||
@@ -186,9 +199,9 @@ export async function handleRunRecording(fileName: string, runId: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanupSocketListeners(socket: Socket, browserId: string, runId: string) {
|
function cleanupSocketListeners(socket: Socket, browserId: string, id: string) {
|
||||||
socket.off('ready-for-run', () => readyForRunHandler(browserId, '', runId));
|
socket.off('ready-for-run', () => readyForRunHandler(browserId, id));
|
||||||
logger.log('info', `Cleaned up listeners for browserId: ${browserId}, runId: ${runId}`);
|
logger.log('info', `Cleaned up listeners for browserId: ${browserId}, runId: ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { runWorkflow };
|
export { runWorkflow };
|
||||||
@@ -32,13 +32,13 @@ export const getStoredRuns = async (): Promise<string[] | null> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteRecordingFromStorage = async (fileName: string): Promise<boolean> => {
|
export const deleteRecordingFromStorage = async (id: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.delete(`http://localhost:8080/storage/recordings/${fileName}`);
|
const response = await axios.delete(`http://localhost:8080/storage/recordings/${id}`);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't delete stored recording ${fileName}`);
|
throw new Error(`Couldn't delete stored recording ${id}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -46,13 +46,13 @@ export const deleteRecordingFromStorage = async (fileName: string): Promise<bool
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteRunFromStorage = async (fileName: string): Promise<boolean> => {
|
export const deleteRunFromStorage = async (id: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.delete(`http://localhost:8080/storage/runs/${fileName}`);
|
const response = await axios.delete(`http://localhost:8080/storage/runs/${id}`);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't delete stored recording ${fileName}`);
|
throw new Error(`Couldn't delete stored recording ${id}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -60,13 +60,13 @@ export const deleteRunFromStorage = async (fileName: string): Promise<boolean> =
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const editRecordingFromStorage = async (browserId: string, fileName: string): Promise<WorkflowFile | null> => {
|
export const editRecordingFromStorage = async (browserId: string, robotId: string): Promise<WorkflowFile | null> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(`http://localhost:8080/workflow/${browserId}/${fileName}`);
|
const response = await axios.put(`http://localhost:8080/workflow/${browserId}/${robotId}`);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't edit stored recording ${fileName}`);
|
throw new Error(`Couldn't edit stored recording ${robotId}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -74,15 +74,15 @@ export const editRecordingFromStorage = async (browserId: string, fileName: stri
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createRunForStoredRecording = async (fileName: string, settings: RunSettings): Promise<CreateRunResponse> => {
|
export const createRunForStoredRecording = async (id: string, settings: RunSettings): Promise<CreateRunResponse> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.put(
|
const response = await axios.put(
|
||||||
`http://localhost:8080/storage/runs/${fileName}`,
|
`http://localhost:8080/storage/runs/${id}`,
|
||||||
{...settings});
|
{...settings});
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't create a run for a recording ${fileName}`);
|
throw new Error(`Couldn't create a run for a recording ${id}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -90,13 +90,13 @@ export const createRunForStoredRecording = async (fileName: string, settings: Ru
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const interpretStoredRecording = async (fileName: string, runId: string): Promise<boolean> => {
|
export const interpretStoredRecording = async (id: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`http://localhost:8080/storage/runs/run/${fileName}/${runId}`);
|
const response = await axios.post(`http://localhost:8080/storage/runs/run/${id}`);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't run a recording ${fileName}`);
|
throw new Error(`Couldn't run a recording ${id}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
@@ -104,13 +104,13 @@ export const interpretStoredRecording = async (fileName: string, runId: string):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const notifyAboutAbort = async (fileName: string, runId:string): Promise<boolean> => {
|
export const notifyAboutAbort = async (id:string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.post(`http://localhost:8080/storage/runs/abort/${fileName}/${runId}`);
|
const response = await axios.post(`http://localhost:8080/storage/runs/abort/${id}`);
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response.data;
|
return response.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Couldn't abort a running recording ${fileName} with id ${runId}`);
|
throw new Error(`Couldn't abort a running recording with id ${id}`);
|
||||||
}
|
}
|
||||||
} catch(error: any) {
|
} catch(error: any) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const CollapsibleRow = ({ row, handleDelete, isOpen, currentLog, abortRun
|
|||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<IconButton aria-label="add" size= "small" onClick={() => {
|
<IconButton aria-label="add" size= "small" onClick={() => {
|
||||||
deleteRunFromStorage(`${row.name}_${row.runId}`).then((result: boolean) => {
|
deleteRunFromStorage(`${row.runId}`).then((result: boolean) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
handleDelete();
|
handleDelete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useGlobalInfoStore } from "../../context/globalInfo";
|
|||||||
import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
|
import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage";
|
||||||
|
|
||||||
interface Column {
|
interface Column {
|
||||||
id: 'interpret' | 'name' | 'createdAt' | 'edit' | 'updatedAt' | 'delete' | 'schedule' | 'integrate';
|
id: 'id' | 'interpret' | 'name' | 'createdAt' | 'edit' | 'updatedAt' | 'delete' | 'schedule' | 'integrate';
|
||||||
label: string;
|
label: string;
|
||||||
minWidth?: number;
|
minWidth?: number;
|
||||||
align?: 'right';
|
align?: 'right';
|
||||||
@@ -24,6 +24,7 @@ interface Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const columns: readonly Column[] = [
|
const columns: readonly Column[] = [
|
||||||
|
{ id: 'id', label: 'ID', minWidth: 80 },
|
||||||
{ id: 'interpret', label: 'Run', minWidth: 80 },
|
{ id: 'interpret', label: 'Run', minWidth: 80 },
|
||||||
{ id: 'name', label: 'Name', minWidth: 80 },
|
{ id: 'name', label: 'Name', minWidth: 80 },
|
||||||
{
|
{
|
||||||
@@ -61,7 +62,7 @@ const columns: readonly Column[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
interface Data {
|
interface Data {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
@@ -70,10 +71,10 @@ interface Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface RecordingsTableProps {
|
interface RecordingsTableProps {
|
||||||
handleEditRecording: (fileName: string) => void;
|
handleEditRecording: (id: string, fileName: string) => void;
|
||||||
handleRunRecording: (fileName: string, params: string[]) => void;
|
handleRunRecording: (id: string,fileName: string, params: string[]) => void;
|
||||||
handleScheduleRecording: (fileName: string, params: string[]) => void;
|
handleScheduleRecording: (id: string,fileName: string, params: string[]) => void;
|
||||||
handleIntegrateRecording: (fileName: string, params: string[]) => void;
|
handleIntegrateRecording: (id: string,fileName: string, params: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording }: RecordingsTableProps) => {
|
export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording }: RecordingsTableProps) => {
|
||||||
@@ -96,13 +97,12 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
|||||||
const recordings = await getStoredRecordings();
|
const recordings = await getStoredRecordings();
|
||||||
if (recordings) {
|
if (recordings) {
|
||||||
const parsedRows: Data[] = [];
|
const parsedRows: Data[] = [];
|
||||||
recordings.map((recording, index) => {
|
recordings.map((recording: any, index: number) => {
|
||||||
const parsedRecording = JSON.parse(recording);
|
if (recording && recording.recording_meta) {
|
||||||
if (parsedRecording.recording_meta) {
|
|
||||||
parsedRows.push({
|
parsedRows.push({
|
||||||
id: index,
|
id: index,
|
||||||
...parsedRecording.recording_meta,
|
...recording.recording_meta,
|
||||||
content: parsedRecording.recording
|
content: recording.recording
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -156,14 +156,14 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
|||||||
case 'interpret':
|
case 'interpret':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<InterpretButton handleInterpret={() => handleRunRecording(row.name, row.params || [])} />
|
<InterpretButton handleInterpret={() => handleRunRecording(row.id, row.name, row.params || [])} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
case 'edit':
|
case 'edit':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<IconButton aria-label="add" size="small" onClick={() => {
|
<IconButton aria-label="add" size="small" onClick={() => {
|
||||||
handleEditRecording(row.name);
|
handleEditRecording(row.id, row.name);
|
||||||
}} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
}} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}>
|
||||||
<Edit />
|
<Edit />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -172,20 +172,20 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl
|
|||||||
case 'schedule':
|
case 'schedule':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<ScheduleButton handleSchedule={() => handleScheduleRecording(row.name, row.params || [])} />
|
<ScheduleButton handleSchedule={() => handleScheduleRecording(row.id, row.name, row.params || [])} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
case 'integrate':
|
case 'integrate':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<IntegrateButton handleIntegrate={() => handleIntegrateRecording(row.name, row.params || [])} />
|
<IntegrateButton handleIntegrate={() => handleIntegrateRecording(row.id, row.name, row.params || [])} />
|
||||||
</TableCell>
|
</TableCell>
|
||||||
);
|
);
|
||||||
case 'delete':
|
case 'delete':
|
||||||
return (
|
return (
|
||||||
<TableCell key={column.id} align={column.align}>
|
<TableCell key={column.id} align={column.align}>
|
||||||
<IconButton aria-label="add" size="small" onClick={() => {
|
<IconButton aria-label="add" size="small" onClick={() => {
|
||||||
deleteRecordingFromStorage(row.name).then((result: boolean) => {
|
deleteRecordingFromStorage(row.id).then((result: boolean) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
setRows([]);
|
setRows([]);
|
||||||
notify('success', 'Recording deleted successfully');
|
notify('success', 'Recording deleted successfully');
|
||||||
|
|||||||
@@ -77,11 +77,11 @@ export const RunsTable = (
|
|||||||
const runs = await getStoredRuns();
|
const runs = await getStoredRuns();
|
||||||
if (runs) {
|
if (runs) {
|
||||||
const parsedRows: Data[] = [];
|
const parsedRows: Data[] = [];
|
||||||
runs.map((run, index) => {
|
runs.map((run: any, index) => {
|
||||||
const parsedRun = JSON.parse(run);
|
// const run = JSON.parse(run);
|
||||||
parsedRows.push({
|
parsedRows.push({
|
||||||
id: index,
|
id: index,
|
||||||
...parsedRun,
|
...run,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setRows(parsedRows);
|
setRows(parsedRows);
|
||||||
|
|||||||
@@ -6,68 +6,78 @@ import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSe
|
|||||||
import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings";
|
import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings";
|
||||||
|
|
||||||
interface RecordingsProps {
|
interface RecordingsProps {
|
||||||
handleEditRecording: (fileName: string) => void;
|
handleEditRecording: (id: string, fileName: string) => void;
|
||||||
handleRunRecording: (settings: RunSettings) => void;
|
handleRunRecording: (settings: RunSettings) => void;
|
||||||
handleScheduleRecording: (settings: ScheduleSettings) => void;
|
handleScheduleRecording: (settings: ScheduleSettings) => void;
|
||||||
handleIntegrateRecording: (settings: IntegrationSettings) => void;
|
handleIntegrateRecording: (settings: IntegrationSettings) => void;
|
||||||
setFileName: (fileName: string) => void;
|
setRecordingInfo: (id: string, name: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Recordings = ({ handleEditRecording, handleRunRecording, setFileName, handleScheduleRecording, handleIntegrateRecording }: RecordingsProps) => {
|
export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordingInfo, handleScheduleRecording, handleIntegrateRecording }: RecordingsProps) => {
|
||||||
const [runSettingsAreOpen, setRunSettingsAreOpen] = useState(false);
|
const [runSettingsAreOpen, setRunSettingsAreOpen] = useState(false);
|
||||||
const [scheduleSettingsAreOpen, setScheduleSettingsAreOpen] = useState(false);
|
const [scheduleSettingsAreOpen, setScheduleSettingsAreOpen] = useState(false);
|
||||||
const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false);
|
const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false);
|
||||||
const [params, setParams] = useState<string[]>([]);
|
const [params, setParams] = useState<string[]>([]);
|
||||||
|
const [selectedRecordingId, setSelectedRecordingId] = useState<string>('');
|
||||||
|
|
||||||
const handleSettingsAndIntegrate = (fileName: string, params: string[]) => {
|
const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => {
|
||||||
if (params.length === 0) {
|
if (params.length === 0) {
|
||||||
setIntegrateSettingsAreOpen(true);
|
setIntegrateSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
} else {
|
} else {
|
||||||
setParams(params);
|
setParams(params);
|
||||||
setIntegrateSettingsAreOpen(true);
|
setIntegrateSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSettingsAndRun = (fileName: string, params: string[]) => {
|
const handleSettingsAndRun = (id: string, name: string, params: string[]) => {
|
||||||
if (params.length === 0) {
|
if (params.length === 0) {
|
||||||
setRunSettingsAreOpen(true);
|
setRunSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
} else {
|
} else {
|
||||||
setParams(params);
|
setParams(params);
|
||||||
setRunSettingsAreOpen(true);
|
setRunSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSettingsAndSchedule = (fileName: string, params: string[]) => {
|
const handleSettingsAndSchedule = (id: string, name: string, params: string[]) => {
|
||||||
if (params.length === 0) {
|
if (params.length === 0) {
|
||||||
setScheduleSettingsAreOpen(true);
|
setScheduleSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
} else {
|
} else {
|
||||||
setParams(params);
|
setParams(params);
|
||||||
setScheduleSettingsAreOpen(true);
|
setScheduleSettingsAreOpen(true);
|
||||||
setFileName(fileName);
|
setRecordingInfo(id, name);
|
||||||
|
setSelectedRecordingId(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setParams([]);
|
setParams([]);
|
||||||
setRunSettingsAreOpen(false);
|
setRunSettingsAreOpen(false);
|
||||||
setFileName('');
|
setRecordingInfo('', '');
|
||||||
|
setSelectedRecordingId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleIntegrateClose = () => {
|
const handleIntegrateClose = () => {
|
||||||
setParams([]);
|
setParams([]);
|
||||||
setIntegrateSettingsAreOpen(false);
|
setIntegrateSettingsAreOpen(false);
|
||||||
setFileName('');
|
setRecordingInfo('', '');
|
||||||
|
setSelectedRecordingId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScheduleClose = () => {
|
const handleScheduleClose = () => {
|
||||||
setParams([]);
|
setParams([]);
|
||||||
setScheduleSettingsAreOpen(false);
|
setScheduleSettingsAreOpen(false);
|
||||||
setFileName('');
|
setRecordingInfo('', '');
|
||||||
|
setSelectedRecordingId('');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -98,4 +108,4 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setFileNam
|
|||||||
</Grid>
|
</Grid>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -16,6 +16,8 @@ interface GlobalInfo {
|
|||||||
setRerenderRuns: (rerenderRuns: boolean) => void;
|
setRerenderRuns: (rerenderRuns: boolean) => void;
|
||||||
recordingLength: number;
|
recordingLength: number;
|
||||||
setRecordingLength: (recordingLength: number) => void;
|
setRecordingLength: (recordingLength: number) => void;
|
||||||
|
recordingId: string | null;
|
||||||
|
setRecordingId: (newId: string | null) => void;
|
||||||
recordingName: string;
|
recordingName: string;
|
||||||
setRecordingName: (recordingName: string) => void;
|
setRecordingName: (recordingName: string) => void;
|
||||||
};
|
};
|
||||||
@@ -29,6 +31,7 @@ class GlobalInfoStore implements Partial<GlobalInfo> {
|
|||||||
message: '',
|
message: '',
|
||||||
isOpen: false,
|
isOpen: false,
|
||||||
};
|
};
|
||||||
|
recordingId = null;
|
||||||
recordings: string[] = [];
|
recordings: string[] = [];
|
||||||
rerenderRuns = false;
|
rerenderRuns = false;
|
||||||
recordingName = '';
|
recordingName = '';
|
||||||
@@ -46,6 +49,7 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
|
|||||||
const [recordings, setRecordings] = useState<string[]>(globalInfoStore.recordings);
|
const [recordings, setRecordings] = useState<string[]>(globalInfoStore.recordings);
|
||||||
const [rerenderRuns, setRerenderRuns] = useState<boolean>(globalInfoStore.rerenderRuns);
|
const [rerenderRuns, setRerenderRuns] = useState<boolean>(globalInfoStore.rerenderRuns);
|
||||||
const [recordingLength, setRecordingLength] = useState<number>(globalInfoStore.recordingLength);
|
const [recordingLength, setRecordingLength] = useState<number>(globalInfoStore.recordingLength);
|
||||||
|
const [recordingId, setRecordingId] = useState<string | null>(globalInfoStore.recordingId);
|
||||||
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
|
const [recordingName, setRecordingName] = useState<string>(globalInfoStore.recordingName);
|
||||||
|
|
||||||
const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => {
|
const notify = (severity: 'error' | 'warning' | 'info' | 'success', message: string) => {
|
||||||
@@ -79,6 +83,8 @@ export const GlobalInfoProvider = ({ children }: { children: JSX.Element }) => {
|
|||||||
setRerenderRuns,
|
setRerenderRuns,
|
||||||
recordingLength,
|
recordingLength,
|
||||||
setRecordingLength,
|
setRecordingLength,
|
||||||
|
recordingId,
|
||||||
|
setRecordingId,
|
||||||
recordingName,
|
recordingName,
|
||||||
setRecordingName
|
setRecordingName
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { ScheduleSettings } from "../components/molecules/ScheduleSettings";
|
|||||||
import { IntegrationSettings } from "../components/molecules/IntegrationSettings";
|
import { IntegrationSettings } from "../components/molecules/IntegrationSettings";
|
||||||
|
|
||||||
interface MainPageProps {
|
interface MainPageProps {
|
||||||
handleEditRecording: (fileName: string) => void;
|
handleEditRecording: (id: string, fileName: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateRunResponse {
|
export interface CreateRunResponse {
|
||||||
@@ -32,6 +32,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
|
|
||||||
const [content, setContent] = React.useState('recordings');
|
const [content, setContent] = React.useState('recordings');
|
||||||
const [sockets, setSockets] = React.useState<Socket[]>([]);
|
const [sockets, setSockets] = React.useState<Socket[]>([]);
|
||||||
|
const [runningRecordingId, setRunningRecordingId] = React.useState('');
|
||||||
const [runningRecordingName, setRunningRecordingName] = React.useState('');
|
const [runningRecordingName, setRunningRecordingName] = React.useState('');
|
||||||
const [currentInterpretationLog, setCurrentInterpretationLog] = React.useState('');
|
const [currentInterpretationLog, setCurrentInterpretationLog] = React.useState('');
|
||||||
const [ids, setIds] = React.useState<CreateRunResponse>({
|
const [ids, setIds] = React.useState<CreateRunResponse>({
|
||||||
@@ -45,7 +46,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
|
|
||||||
const abortRunHandler = (runId: string) => {
|
const abortRunHandler = (runId: string) => {
|
||||||
aborted = true;
|
aborted = true;
|
||||||
notifyAboutAbort(runningRecordingName, runId).then(async (response) => {
|
notifyAboutAbort(runId).then(async (response) => {
|
||||||
if (response) {
|
if (response) {
|
||||||
notify('success', `Interpretation of ${runningRecordingName} aborted successfully`);
|
notify('success', `Interpretation of ${runningRecordingName} aborted successfully`);
|
||||||
await stopRecording(ids.browserId);
|
await stopRecording(ids.browserId);
|
||||||
@@ -55,12 +56,13 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const setFileName = (fileName: string) => {
|
const setRecordingInfo = (id: string, name: string) => {
|
||||||
setRunningRecordingName(fileName);
|
setRunningRecordingId(id);
|
||||||
|
setRunningRecordingName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const readyForRunHandler = useCallback((browserId: string, runId: string) => {
|
const readyForRunHandler = useCallback((browserId: string, runId: string) => {
|
||||||
interpretStoredRecording(runningRecordingName, runId).then(async (interpretation: boolean) => {
|
interpretStoredRecording(runId).then(async (interpretation: boolean) => {
|
||||||
if (!aborted) {
|
if (!aborted) {
|
||||||
if (interpretation) {
|
if (interpretation) {
|
||||||
notify('success', `Interpretation of ${runningRecordingName} succeeded`);
|
notify('success', `Interpretation of ${runningRecordingName} succeeded`);
|
||||||
@@ -82,7 +84,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
}, [currentInterpretationLog])
|
}, [currentInterpretationLog])
|
||||||
|
|
||||||
const handleRunRecording = useCallback((settings: RunSettings) => {
|
const handleRunRecording = useCallback((settings: RunSettings) => {
|
||||||
createRunForStoredRecording(runningRecordingName, settings).then(({ browserId, runId }: CreateRunResponse) => {
|
createRunForStoredRecording(runningRecordingId, settings).then(({ browserId, runId }: CreateRunResponse) => {
|
||||||
setIds({ browserId, runId });
|
setIds({ browserId, runId });
|
||||||
const socket =
|
const socket =
|
||||||
io(`http://localhost:8080/${browserId}`, {
|
io(`http://localhost:8080/${browserId}`, {
|
||||||
@@ -98,7 +100,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
} else {
|
} else {
|
||||||
notify('error', `Failed to run recording: ${runningRecordingName}`);
|
notify('error', `Failed to run recording: ${runningRecordingName}`);
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
return (socket: Socket, browserId: string, runId: string) => {
|
return (socket: Socket, browserId: string, runId: string) => {
|
||||||
socket.off('ready-for-run', () => readyForRunHandler(browserId, runId));
|
socket.off('ready-for-run', () => readyForRunHandler(browserId, runId));
|
||||||
socket.off('debugMessage', debugMessageHandler);
|
socket.off('debugMessage', debugMessageHandler);
|
||||||
@@ -133,7 +135,7 @@ export const MainPage = ({ handleEditRecording }: MainPageProps) => {
|
|||||||
return <Recordings
|
return <Recordings
|
||||||
handleEditRecording={handleEditRecording}
|
handleEditRecording={handleEditRecording}
|
||||||
handleRunRecording={handleRunRecording}
|
handleRunRecording={handleRunRecording}
|
||||||
setFileName={setFileName}
|
setRecordingInfo={setRecordingInfo}
|
||||||
handleScheduleRecording={handleScheduleRecording}
|
handleScheduleRecording={handleScheduleRecording}
|
||||||
handleIntegrateRecording={handleIntegrateRecording}
|
handleIntegrateRecording={handleIntegrateRecording}
|
||||||
/>;
|
/>;
|
||||||
|
|||||||
@@ -18,20 +18,20 @@ export const PageWrapper = () => {
|
|||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { browserId, setBrowserId, notification, recordingName, setRecordingName } = useGlobalInfoStore();
|
const { browserId, setBrowserId, notification, recordingName, setRecordingName, recordingId, setRecordingId } = useGlobalInfoStore();
|
||||||
|
|
||||||
const handleNewRecording = () => {
|
const handleNewRecording = () => {
|
||||||
setBrowserId('new-recording');
|
setBrowserId('new-recording');
|
||||||
setRecordingName('');
|
setRecordingName('');
|
||||||
|
setRecordingId('');
|
||||||
navigate('/recording');
|
navigate('/recording');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleEditRecording = (fileName: string) => {
|
const handleEditRecording = (recordingId: string, fileName: string) => {
|
||||||
setRecordingName(fileName);
|
setRecordingName(fileName);
|
||||||
|
setRecordingId(recordingId);
|
||||||
setBrowserId('new-recording');
|
setBrowserId('new-recording');
|
||||||
navigate('/recording');
|
navigate('/recording');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isNotification = (): boolean => {
|
const isNotification = (): boolean => {
|
||||||
|
|||||||
Reference in New Issue
Block a user