From ce467dba2bd38352e76feb283860fd80cbba3ec9 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Mon, 4 Nov 2024 21:20:43 +0530 Subject: [PATCH 001/165] bug: tab name bug fix --- src/components/organisms/BrowserContent.tsx | 157 +++++++++++--------- 1 file changed, 89 insertions(+), 68 deletions(-) diff --git a/src/components/organisms/BrowserContent.tsx b/src/components/organisms/BrowserContent.tsx index 79fe6e631..11af4f2f9 100644 --- a/src/components/organisms/BrowserContent.tsx +++ b/src/components/organisms/BrowserContent.tsx @@ -1,12 +1,16 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useState } from "react"; import styled from "styled-components"; import BrowserNavBar from "../molecules/BrowserNavBar"; import { BrowserWindow } from "./BrowserWindow"; import { useBrowserDimensionsStore } from "../../context/browserDimensions"; import { BrowserTabs } from "../molecules/BrowserTabs"; import { useSocketStore } from "../../context/socket"; -import { getCurrentTabs, getCurrentUrl, interpretCurrentRecording } from "../../api/recording"; -import { Box } from '@mui/material'; +import { + getCurrentTabs, + getCurrentUrl, + interpretCurrentRecording, +} from "../../api/recording"; +import { Box } from "@mui/material"; import { InterpretationLog } from "../molecules/InterpretationLog"; // TODO: Tab !show currentUrl after recordingUrl global state @@ -14,107 +18,125 @@ export const BrowserContent = () => { const { width } = useBrowserDimensionsStore(); const { socket } = useSocketStore(); - const [tabs, setTabs] = useState(['current']); + const [tabs, setTabs] = useState(["current"]); const [tabIndex, setTabIndex] = React.useState(0); const [showOutputData, setShowOutputData] = useState(false); - const handleChangeIndex = useCallback((index: number) => { - setTabIndex(index); - }, [tabIndex]) + const handleChangeIndex = useCallback( + (index: number) => { + setTabIndex(index); + }, + [tabIndex] + ); - const handleCloseTab = useCallback((index: number) => { - // the tab needs to be closed on the backend - socket?.emit('closeTab', { - index, - isCurrent: tabIndex === index, - }); - // change the current index as current tab gets closed - if (tabIndex === index) { - if (tabs.length > index + 1) { - handleChangeIndex(index); + const handleCloseTab = useCallback( + (index: number) => { + // the tab needs to be closed on the backend + socket?.emit("closeTab", { + index, + isCurrent: tabIndex === index, + }); + // change the current index as current tab gets closed + if (tabIndex === index) { + if (tabs.length > index + 1) { + handleChangeIndex(index); + } else { + handleChangeIndex(index - 1); + } } else { - handleChangeIndex(index - 1); + handleChangeIndex(tabIndex - 1); } - } else { - handleChangeIndex(tabIndex - 1); - } - // update client tabs - setTabs((prevState) => [ - ...prevState.slice(0, index), - ...prevState.slice(index + 1) - ]) - }, [tabs, socket, tabIndex]); + // update client tabs + setTabs((prevState) => [ + ...prevState.slice(0, index), + ...prevState.slice(index + 1), + ]); + }, + [tabs, socket, tabIndex] + ); const handleAddNewTab = useCallback(() => { // Adds new tab by pressing the plus button - socket?.emit('addTab'); + socket?.emit("addTab"); // Adds a new tab to the end of the tabs array and shifts focus - setTabs((prevState) => [...prevState, 'new tab']); + setTabs((prevState) => [...prevState, "new tab"]); handleChangeIndex(tabs.length); }, [socket, tabs]); - const handleNewTab = useCallback((tab: string) => { - // Adds a new tab to the end of the tabs array and shifts focus - setTabs((prevState) => [...prevState, tab]); - // changes focus on the new tab - same happens in the remote browser - handleChangeIndex(tabs.length); - handleTabChange(tabs.length); - }, [tabs]); + const handleNewTab = useCallback( + (tab: string) => { + // Adds a new tab to the end of the tabs array and shifts focus + setTabs((prevState) => [...prevState, tab]); + // changes focus on the new tab - same happens in the remote browser + handleChangeIndex(tabs.length); + handleTabChange(tabs.length); + }, + [tabs] + ); - const handleTabChange = useCallback((index: number) => { - // page screencast and focus needs to be changed on backend - socket?.emit('changeTab', index); - }, [socket]); + const handleTabChange = useCallback( + (index: number) => { + // page screencast and focus needs to be changed on backend + socket?.emit("changeTab", index); + }, + [socket] + ); const handleUrlChanged = (url: string) => { const parsedUrl = new URL(url); if (parsedUrl.hostname) { - const host = parsedUrl.hostname.match(/\b(?!www\.)[a-zA-Z0-9]+/g)?.join('.') + const host = parsedUrl.hostname + .match(/\b(?!www\.)[a-zA-Z0-9]+/g) + ?.join("."); if (host && host !== tabs[tabIndex]) { setTabs((prevState) => [ ...prevState.slice(0, tabIndex), host, - ...prevState.slice(tabIndex + 1) - ]) + ...prevState.slice(tabIndex + 1), + ]); } } else { - if (tabs[tabIndex] !== 'new tab') { + if (tabs[tabIndex] !== "new tab") { setTabs((prevState) => [ ...prevState.slice(0, tabIndex), - 'new tab', - ...prevState.slice(tabIndex + 1) - ]) + "new tab", + ...prevState.slice(tabIndex + 1), + ]); } } - }; - const tabHasBeenClosedHandler = useCallback((index: number) => { - handleCloseTab(index); - }, [handleCloseTab]) + const tabHasBeenClosedHandler = useCallback( + (index: number) => { + handleCloseTab(index); + }, + [handleCloseTab] + ); useEffect(() => { if (socket) { - socket.on('newTab', handleNewTab); - socket.on('tabHasBeenClosed', tabHasBeenClosedHandler); + socket.on("newTab", handleNewTab); + socket.on("tabHasBeenClosed", tabHasBeenClosedHandler); } return () => { if (socket) { - socket.off('newTab', handleNewTab); - socket.off('tabHasBeenClosed', tabHasBeenClosedHandler); + socket.off("newTab", handleNewTab); + socket.off("tabHasBeenClosed", tabHasBeenClosedHandler); } - } - }, [socket, handleNewTab]) + }; + }, [socket, handleNewTab]); useEffect(() => { - getCurrentTabs().then((response) => { - if (response) { - setTabs(response); - } - }).catch((error) => { - console.log("Fetching current url failed"); - }) - }, []) + getCurrentTabs() + .then((response) => { + if (response) { + setTabs(response); + } + }) + .catch((error) => { + console.log("Fetching current url failed"); + }); + }, [handleUrlChanged]); return (
@@ -134,7 +156,6 @@ export const BrowserContent = () => {
); -} +}; -const BrowserContentWrapper = styled.div` -`; \ No newline at end of file +const BrowserContentWrapper = styled.div``; From 0a72039681801b68dcf56a044e45d2defd91143e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 4 Nov 2024 21:59:05 +0530 Subject: [PATCH 002/165] chore: expose minio webui port --- docker-compose.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1aeefd266..d03334e0b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -32,9 +32,10 @@ services: environment: MINIO_ROOT_USER: ${MINIO_ACCESS_KEY} MINIO_ROOT_PASSWORD: ${MINIO_SECRET_KEY} - command: server /data + command: server /data --console-address :9001 ports: - - "9000:9000" + - "9000:9000" # API port + - "9001:9001" # WebUI port volumes: - minio_data:/data From 6a187872c4e6a04593eab9e59d4b349fc5e28655 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 4 Nov 2024 22:10:39 +0530 Subject: [PATCH 003/165] feat: properly apply minio public-read policy --- server/src/storage/mino.ts | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/server/src/storage/mino.ts b/server/src/storage/mino.ts index 3b83e3860..03702c2ca 100644 --- a/server/src/storage/mino.ts +++ b/server/src/storage/mino.ts @@ -21,38 +21,39 @@ minioClient.bucketExists('maxun-test') console.error('Error connecting to MinIO:', err); }) -async function createBucketWithPolicy(bucketName: string, policy?: 'public-read' | 'private') { +async function createBucketWithPolicy(bucketName: string, policy = 'public-read') { try { const bucketExists = await minioClient.bucketExists(bucketName); if (!bucketExists) { await minioClient.makeBucket(bucketName); console.log(`Bucket ${bucketName} created successfully.`); - - if (policy === 'public-read') { - // Define a public-read policy - const policyJSON = { - Version: "2012-10-17", - Statement: [ - { - Effect: "Allow", - Principal: "", - Action: ["s3:GetObject"], - Resource: [`arn:aws:s3:::${bucketName}/*`] - } - ] - }; - await minioClient.setBucketPolicy(bucketName, JSON.stringify(policyJSON)); - console.log(`Public-read policy applied to bucket ${bucketName}.`); - } } else { console.log(`Bucket ${bucketName} already exists.`); } + + if (policy === 'public-read') { + // Apply public-read policy after confirming the bucket exists + const policyJSON = { + Version: "2012-10-17", + Statement: [ + { + Effect: "Allow", + Principal: "*", + Action: ["s3:GetObject"], + Resource: [`arn:aws:s3:::${bucketName}/*`] + } + ] + }; + await minioClient.setBucketPolicy(bucketName, JSON.stringify(policyJSON)); + console.log(`Public-read policy applied to bucket ${bucketName}.`); + } } catch (error) { console.error('Error in bucket creation or policy application:', error); } } + class BinaryOutputService { private bucketName: string; From f856d39d4eee286ab998af7e85df67f63defdf0a Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Mon, 4 Nov 2024 22:33:13 +0530 Subject: [PATCH 004/165] fix: logo path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index aad5dc2f0..33f30df26 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

- +
Maxun
From c43e56bc17868a95525f2f7e839fd62f5f9ba711 Mon Sep 17 00:00:00 2001 From: Joshua <80618548+joshua111000@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:38:27 +0530 Subject: [PATCH 005/165] chore: remove req.body print --- server/src/routes/auth.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 6766a3565..9240cc0ca 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -15,7 +15,7 @@ interface AuthenticatedRequest extends Request { router.post('/register', async (req, res) => { console.log('Received request at /auth/register'); - console.log('Received body:', req.body); + try { const { email, password } = req.body From 60f79a1afdbe8eee3c9b38968b71252f2ed5e396 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 5 Nov 2024 23:05:23 +0530 Subject: [PATCH 006/165] feat: set margin right to 30px --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 69fba3521..dccc788a1 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -74,7 +74,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => alignItems: 'center', borderRadius: '5px', padding: '8px', - marginRight: '10px', + marginRight: '30px', }} > From 0c632af5bf4c5b7ee835227475e2b9c5ead7da0d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 5 Nov 2024 23:05:50 +0530 Subject: [PATCH 007/165] chore: lint --- src/components/molecules/NavBar.tsx | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index dccc788a1..88d4091ad 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -65,21 +65,21 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => {!isRecording ? ( <> - + - + Date: Tue, 5 Nov 2024 23:13:23 +0530 Subject: [PATCH 008/165] feat: add key for each text field --- src/components/molecules/RobotSettings.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/molecules/RobotSettings.tsx b/src/components/molecules/RobotSettings.tsx index 98638120d..598311fe2 100644 --- a/src/components/molecules/RobotSettings.tsx +++ b/src/components/molecules/RobotSettings.tsx @@ -104,6 +104,7 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe <> Date: Tue, 5 Nov 2024 23:13:55 +0530 Subject: [PATCH 009/165] feat: check userEmail value to prevent label and text overlap --- src/components/molecules/RobotSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotSettings.tsx b/src/components/molecules/RobotSettings.tsx index 598311fe2..ffb7a2d09 100644 --- a/src/components/molecules/RobotSettings.tsx +++ b/src/components/molecules/RobotSettings.tsx @@ -123,7 +123,7 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe Date: Tue, 5 Nov 2024 23:22:43 +0530 Subject: [PATCH 010/165] feat: set modalStyle for integration modal --- src/components/molecules/IntegrationSettings.tsx | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 1150dd72c..20b03ce92 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -113,7 +113,7 @@ export const IntegrationSettingsModal = ({ isOpen, handleStart, handleClose }: I }, [recordingId]); return ( - +
Integrate with Google Sheet @@ -225,3 +225,15 @@ export const IntegrationSettingsModal = ({ isOpen, handleStart, handleClose }: I ); }; + +export const modalStyle = { + top: '40%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '50%', + backgroundColor: 'background.paper', + p: 4, + height:'fit-content', + display:'block', + padding: '20px', + }; From 25c7ac5b9c0c18951805b062ea0b13e39f70a680 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 5 Nov 2024 23:23:20 +0530 Subject: [PATCH 011/165] chore: lint --- src/components/molecules/IntegrationSettings.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 20b03ce92..2abac53f8 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -233,7 +233,7 @@ export const modalStyle = { width: '50%', backgroundColor: 'background.paper', p: 4, - height:'fit-content', - display:'block', + height: 'fit-content', + display: 'block', padding: '20px', - }; +}; From dd250717663adcf52ca70d9a7ac1411f58797878 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 5 Nov 2024 23:24:22 +0530 Subject: [PATCH 012/165] feat: remove margin bottom --- src/components/molecules/IntegrationSettings.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 2abac53f8..8db29a7ac 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -143,7 +143,6 @@ export const IntegrationSettingsModal = ({ isOpen, handleStart, handleClose }: I variant="contained" color="primary" onClick={authenticateWithGoogle} - style={{ marginBottom: '15px' }} > Authenticate with Google From 132c6da6a19fc9c3b0c19c738abfb9c8aeeb8faa Mon Sep 17 00:00:00 2001 From: amhsirak Date: Tue, 5 Nov 2024 23:26:04 +0530 Subject: [PATCH 013/165] feat: remove margin --- src/components/molecules/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 8db29a7ac..1489994c8 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -115,7 +115,7 @@ export const IntegrationSettingsModal = ({ isOpen, handleStart, handleClose }: I return (
- Integrate with Google Sheet + Integrate with Google Sheet {recording && recording.google_sheet_id ? ( <> From ac25c03de7f84aece72ad7e7702165da4f258776 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 6 Nov 2024 20:25:07 +0530 Subject: [PATCH 014/165] feat: redirect to localhost:5173 --- server/src/routes/auth.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 9240cc0ca..2e922d57f 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -333,12 +333,14 @@ router.get('/google/callback', requireSignIn, async (req: AuthenticatedRequest, const jwtToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET as string, { expiresIn: '12h' }); res.cookie('token', jwtToken, { httpOnly: true }); - res.json({ - message: 'Google authentication successful', - google_sheet_email: robot.google_sheet_email, - jwtToken, - files - }); + // res.json({ + // message: 'Google authentication successful', + // google_sheet_email: robot.google_sheet_email, + // jwtToken, + // files + // }); + + res.redirect(`http://localhost:5173`); } catch (error: any) { res.status(500).json({ message: `Google OAuth error: ${error.message}` }); } From 6db1bd605582f95e2fa120a25722509e5a8ed5ab Mon Sep 17 00:00:00 2001 From: amhsirak Date: Wed, 6 Nov 2024 23:43:44 +0530 Subject: [PATCH 015/165] feat: rename API Docs to Website To API --- src/components/organisms/MainMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/MainMenu.tsx b/src/components/organisms/MainMenu.tsx index edb6ed292..3f0aac79d 100644 --- a/src/components/organisms/MainMenu.tsx +++ b/src/components/organisms/MainMenu.tsx @@ -88,7 +88,7 @@ export const MainMenu = ({ value = 'recordings', handleChangeContent }: MainMenu
+ {tableData.length > 0 ? ( - <> - - - - - + +
+ + + {columns.map((column) => ( + {column} + ))} + + + + {tableData.map((row, index) => ( + {columns.map((column) => ( - {column} + {row[column]} ))} - - - {tableData.map((row, index) => ( - - {columns.map((column) => ( - {row[column]} - ))} - - ))} - -
-
- + ))} + + + ) : ( } - {row.binaryOutput - && Object.keys(row.binaryOutput).length !== 0 && + {row.binaryOutput && Object.keys(row.binaryOutput).length !== 0 &&
- Captured Screenshot + Captured Screenshot + {Object.keys(row.binaryOutput).map((key) => { try { const imageUrl = row.binaryOutput[key]; @@ -181,10 +184,10 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - + Download Screenshot - {key} + {key} ) } catch (e) { @@ -200,4 +203,4 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe ); -} +}; From ca28135962f3ebd837965295ac2e0a88b75b590e Mon Sep 17 00:00:00 2001 From: amit Date: Tue, 12 Nov 2024 10:42:40 +0530 Subject: [PATCH 035/165] changed button tag to typography --- src/components/molecules/RunContent.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/molecules/RunContent.tsx b/src/components/molecules/RunContent.tsx index 42f68a0d3..5b0415251 100644 --- a/src/components/molecules/RunContent.tsx +++ b/src/components/molecules/RunContent.tsx @@ -120,7 +120,7 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe Download as JSON - + {tableData.length > 0 ? ( From 8e039fea01050424f34c87fa59ac6f54b205d9fd Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 13 Nov 2024 00:05:01 +0530 Subject: [PATCH 036/165] chore: google integration auth notif --- server/src/routes/auth.ts | 850 +++++++++++++++++++++----------------- 1 file changed, 462 insertions(+), 388 deletions(-) diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 0cae9f598..88c27d273 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,490 +1,564 @@ -import { Router, Request, Response } from 'express'; -import User from '../models/User'; -import Robot from '../models/Robot'; -import jwt from 'jsonwebtoken'; -import { hashPassword, comparePassword } from '../utils/auth'; -import { requireSignIn } from '../middlewares/auth'; -import { genAPIKey } from '../utils/api'; -import { google } from 'googleapis'; -import { capture } from "../utils/analytics" +import { Router, Request, Response } from "express"; +import User from "../models/User"; +import Robot from "../models/Robot"; +import jwt from "jsonwebtoken"; +import { hashPassword, comparePassword } from "../utils/auth"; +import { requireSignIn } from "../middlewares/auth"; +import { genAPIKey } from "../utils/api"; +import { google } from "googleapis"; +import { capture } from "../utils/analytics"; export const router = Router(); interface AuthenticatedRequest extends Request { - user?: { id: string }; + user?: { id: string }; } -router.post('/register', async (req, res) => { - console.log('Received request at /auth/register'); - - try { - const { email, password } = req.body - - if (!email) return res.status(400).send('Email is required') - if (!password || password.length < 6) return res.status(400).send('Password is required and must be at least 6 characters') - - let userExist = await User.findOne({ raw: true, where: { email } }); - if (userExist) return res.status(400).send('User already exists') - - const hashedPassword = await hashPassword(password) - - let user: any; - - try { - user = await User.create({ email, password: hashedPassword }); - } catch ( - error: any - ) { - console.log(`Could not create user - ${error}`) - return res.status(500).send(`Could not create user - ${error.message}`) - } - - if (!process.env.JWT_SECRET) { - console.log('JWT_SECRET is not defined in the environment'); - return res.status(500).send('Internal Server Error'); - } - - const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { expiresIn: '12h' }); - user.password = undefined as unknown as string - res.cookie('token', token, { - httpOnly: true - }) - capture( - 'maxun-oss-user-registered', - { - email: user.email, - userId: user.id, - registeredAt: new Date().toISOString() - } - ) - console.log(`User registered - ${user.email}`) - res.json(user) - } catch (error: any) { - console.log(`Could not register user - ${error}`) - res.status(500).send(`Could not register user - ${error.message}`) - } -}) +router.post("/register", async (req, res) => { + console.log("Received request at /auth/register"); -router.post('/login', async (req, res) => { - try { - const { email, password } = req.body; - if (!email || !password) return res.status(400).send('Email and password are required') - if (password.length < 6) return res.status(400).send('Password must be at least 6 characters') - - let user = await User.findOne({ raw: true, where: { email } }); - if (!user) return res.status(400).send('User does not exist'); - - const match = await comparePassword(password, user.password) - if (!match) return res.status(400).send('Invalid email or password') - - const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string, { expiresIn: '12h' }); - - // return user and token to client, exclude hashed password - if (user) { - user.password = undefined as unknown as string; - } - res.cookie('token', token, { - httpOnly: true - }) - capture( - 'maxun-oss-user-login', - { - email: user.email, - userId: user.id, - loggedInAt: new Date().toISOString() - } - ) - res.json(user) - } catch (error: any) { - res.status(400).send(`Could not login user - ${error.message}`) - console.log(`Could not login user - ${error}`) - } -}) + try { + const { email, password } = req.body; + + if (!email) return res.status(400).send("Email is required"); + if (!password || password.length < 6) + return res + .status(400) + .send("Password is required and must be at least 6 characters"); + + let userExist = await User.findOne({ raw: true, where: { email } }); + if (userExist) return res.status(400).send("User already exists"); + + const hashedPassword = await hashPassword(password); + + let user: any; -router.get('/logout', async (req, res) => { try { - res.clearCookie('token') - return res.json({ message: 'Logout successful' }) + user = await User.create({ email, password: hashedPassword }); } catch (error: any) { - res.status(500).send(`Could not logout user - ${error.message}`) + console.log(`Could not create user - ${error}`); + return res.status(500).send(`Could not create user - ${error.message}`); } -}) -router.get('/current-user', requireSignIn, async (req: AuthenticatedRequest, res) => { - try { - if (!req.user) { - return res.status(401).json({ ok: false, error: 'Unauthorized' }); - } - const user = await User.findByPk(req.user.id, { - attributes: { exclude: ['password'] }, - }); - if (!user) { - return res.status(404).json({ ok: false, error: 'User not found' }); - } else { - return res.status(200).json({ ok: true, user: user }); - } - } catch (error: any) { - console.error('Error in current-user route:', error); - return res.status(500).json({ ok: false, error: `Could not fetch current user: ${error.message}` }); + if (!process.env.JWT_SECRET) { + console.log("JWT_SECRET is not defined in the environment"); + return res.status(500).send("Internal Server Error"); } + + const token = jwt.sign({ id: user.id }, process.env.JWT_SECRET as string, { + expiresIn: "12h", + }); + user.password = undefined as unknown as string; + res.cookie("token", token, { + httpOnly: true, + }); + capture("maxun-oss-user-registered", { + email: user.email, + userId: user.id, + registeredAt: new Date().toISOString(), + }); + console.log(`User registered - ${user.email}`); + res.json(user); + } catch (error: any) { + console.log(`Could not register user - ${error}`); + res.status(500).send(`Could not register user - ${error.message}`); + } }); -router.get('/user/:id', requireSignIn, async (req: AuthenticatedRequest, res) => { - try { - const { id } = req.params; - if (!id) { - return res.status(400).json({ message: 'User ID is required' }); - } +router.post("/login", async (req, res) => { + try { + const { email, password } = req.body; + if (!email || !password) + return res.status(400).send("Email and password are required"); + if (password.length < 6) + return res.status(400).send("Password must be at least 6 characters"); - const user = await User.findByPk(id, { - attributes: { exclude: ['password'] }, - }); + let user = await User.findOne({ raw: true, where: { email } }); + if (!user) return res.status(400).send("User does not exist"); - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } + const match = await comparePassword(password, user.password); + if (!match) return res.status(400).send("Invalid email or password"); - return res.status(200).json({ message: 'User fetched successfully', user }); - } catch (error: any) { - return res.status(500).json({ message: 'Error fetching user', error: error.message }); + const token = jwt.sign({ id: user?.id }, process.env.JWT_SECRET as string, { + expiresIn: "12h", + }); + + // return user and token to client, exclude hashed password + if (user) { + user.password = undefined as unknown as string; } + res.cookie("token", token, { + httpOnly: true, + }); + capture("maxun-oss-user-login", { + email: user.email, + userId: user.id, + loggedInAt: new Date().toISOString(), + }); + res.json(user); + } catch (error: any) { + res.status(400).send(`Could not login user - ${error.message}`); + console.log(`Could not login user - ${error}`); + } +}); + +router.get("/logout", async (req, res) => { + try { + res.clearCookie("token"); + return res.json({ message: "Logout successful" }); + } catch (error: any) { + res.status(500).send(`Could not logout user - ${error.message}`); + } }); -router.post('/generate-api-key', requireSignIn, async (req: AuthenticatedRequest, res) => { +router.get( + "/current-user", + requireSignIn, + async (req: AuthenticatedRequest, res) => { try { - if (!req.user) { - return res.status(401).json({ ok: false, error: 'Unauthorized' }); - } - const user = await User.findByPk(req.user.id, { - attributes: { exclude: ['password'] }, + if (!req.user) { + return res.status(401).json({ ok: false, error: "Unauthorized" }); + } + const user = await User.findByPk(req.user.id, { + attributes: { exclude: ["password"] }, + }); + if (!user) { + return res.status(404).json({ ok: false, error: "User not found" }); + } else { + return res.status(200).json({ ok: true, user: user }); + } + } catch (error: any) { + console.error("Error in current-user route:", error); + return res + .status(500) + .json({ + ok: false, + error: `Could not fetch current user: ${error.message}`, }); + } + } +); - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } - - if (user.api_key) { - return res.status(400).json({ message: 'API key already exists' }); - } - const apiKey = genAPIKey(); - - await user.update({ api_key: apiKey }); - - capture( - 'maxun-oss-api-key-created', - { - user_id: user.id, - created_at: new Date().toISOString() - } - ) +router.get( + "/user/:id", + requireSignIn, + async (req: AuthenticatedRequest, res) => { + try { + const { id } = req.params; + if (!id) { + return res.status(400).json({ message: "User ID is required" }); + } + + const user = await User.findByPk(id, { + attributes: { exclude: ["password"] }, + }); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + return res + .status(200) + .json({ message: "User fetched successfully", user }); + } catch (error: any) { + return res + .status(500) + .json({ message: "Error fetching user", error: error.message }); + } + } +); - return res.status(200).json({ - message: 'API key generated successfully', - api_key: apiKey, - }); +router.post( + "/generate-api-key", + requireSignIn, + async (req: AuthenticatedRequest, res) => { + try { + if (!req.user) { + return res.status(401).json({ ok: false, error: "Unauthorized" }); + } + const user = await User.findByPk(req.user.id, { + attributes: { exclude: ["password"] }, + }); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + if (user.api_key) { + return res.status(400).json({ message: "API key already exists" }); + } + const apiKey = genAPIKey(); + + await user.update({ api_key: apiKey }); + + capture("maxun-oss-api-key-created", { + user_id: user.id, + created_at: new Date().toISOString(), + }); + + return res.status(200).json({ + message: "API key generated successfully", + api_key: apiKey, + }); } catch (error) { - return res.status(500).json({ message: 'Error generating API key', error }); + return res + .status(500) + .json({ message: "Error generating API key", error }); } -}); + } +); -router.get('/api-key', requireSignIn, async (req: AuthenticatedRequest, res) => { +router.get( + "/api-key", + requireSignIn, + async (req: AuthenticatedRequest, res) => { try { - if (!req.user) { - return res.status(401).json({ ok: false, error: 'Unauthorized' }); - } - - const user = await User.findByPk(req.user.id, { - raw: true, - attributes: ['api_key'], - }); - - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } - - return res.status(200).json({ - message: 'API key fetched successfully', - api_key: user.api_key || null, - }); + if (!req.user) { + return res.status(401).json({ ok: false, error: "Unauthorized" }); + } + + const user = await User.findByPk(req.user.id, { + raw: true, + attributes: ["api_key"], + }); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + return res.status(200).json({ + message: "API key fetched successfully", + api_key: user.api_key || null, + }); } catch (error) { - return res.status(500).json({ message: 'Error fetching API key', error }); + return res.status(500).json({ message: "Error fetching API key", error }); } -}); - -router.delete('/delete-api-key', requireSignIn, async (req: AuthenticatedRequest, res) => { + } +); +router.delete( + "/delete-api-key", + requireSignIn, + async (req: AuthenticatedRequest, res) => { if (!req.user) { - return res.status(401).send({ error: 'Unauthorized' }); + return res.status(401).send({ error: "Unauthorized" }); } try { - const user = await User.findByPk(req.user.id, { raw: true }); + const user = await User.findByPk(req.user.id, { raw: true }); - if (!user) { - return res.status(404).json({ message: 'User not found' }); - } + if (!user) { + return res.status(404).json({ message: "User not found" }); + } - if (!user.api_key) { - return res.status(404).json({ message: 'API Key not found' }); - } + if (!user.api_key) { + return res.status(404).json({ message: "API Key not found" }); + } - await User.update({ api_key: null }, { where: { id: req.user.id } }); + await User.update({ api_key: null }, { where: { id: req.user.id } }); - capture( - 'maxun-oss-api-key-deleted', - { - user_id: user.id, - deleted_at: new Date().toISOString() - } - ) + capture("maxun-oss-api-key-deleted", { + user_id: user.id, + deleted_at: new Date().toISOString(), + }); - return res.status(200).json({ message: 'API Key deleted successfully' }); + return res.status(200).json({ message: "API Key deleted successfully" }); } catch (error: any) { - return res.status(500).json({ message: 'Error deleting API key', error: error.message }); + return res + .status(500) + .json({ message: "Error deleting API key", error: error.message }); } -}); + } +); const oauth2Client = new google.auth.OAuth2( - process.env.GOOGLE_CLIENT_ID, - process.env.GOOGLE_CLIENT_SECRET, - process.env.GOOGLE_REDIRECT_URI + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET, + process.env.GOOGLE_REDIRECT_URI ); // Step 1: Redirect to Google for authentication -router.get('/google', (req, res) => { - const { robotId } = req.query; - if (!robotId) { - return res.status(400).json({ message: 'Robot ID is required' }); - } - const scopes = [ - 'https://www.googleapis.com/auth/spreadsheets', - 'https://www.googleapis.com/auth/userinfo.email', - 'https://www.googleapis.com/auth/drive.readonly', - ]; - const url = oauth2Client.generateAuthUrl({ - access_type: 'offline', - prompt: 'consent', // Ensures you get a refresh token on first login - scope: scopes, - state: robotId.toString(), - }); - res.redirect(url); +router.get("/google", (req, res) => { + const { robotId } = req.query; + if (!robotId) { + return res.status(400).json({ message: "Robot ID is required" }); + } + const scopes = [ + "https://www.googleapis.com/auth/spreadsheets", + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/drive.readonly", + ]; + const url = oauth2Client.generateAuthUrl({ + access_type: "offline", + prompt: "consent", // Ensures you get a refresh token on first login + scope: scopes, + state: robotId.toString(), + }); + res.redirect(url); }); // Step 2: Handle Google OAuth callback -router.get('/google/callback', requireSignIn, async (req: AuthenticatedRequest, res) => { +router.get( + "/google/callback", + requireSignIn, + async (req: AuthenticatedRequest, res) => { const { code, state } = req.query; try { - if (!state) { - return res.status(400).json({ message: 'Robot ID is required' }); - } - - const robotId = state - - // Get access and refresh tokens - if (typeof code !== 'string') { - return res.status(400).json({ message: 'Invalid code' }); - } - const { tokens } = await oauth2Client.getToken(code); - oauth2Client.setCredentials(tokens); - - // Get user profile from Google - const oauth2 = google.oauth2({ version: 'v2', auth: oauth2Client }); - const { data: { email } } = await oauth2.userinfo.get(); - - if (!email) { - return res.status(400).json({ message: 'Email not found' }); - } - - if (!req.user) { - return res.status(401).send({ error: 'Unauthorized' }); - } - - // Get the currently authenticated user (from `requireSignIn`) - let user = await User.findOne({ where: { id: req.user.id } }); - - if (!user) { - return res.status(400).json({ message: 'User not found' }); - } - - let robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); - - if (!robot) { - return res.status(400).json({ message: 'Robot not found' }); - } - - robot = await robot.update({ - google_sheet_email: email, - google_access_token: tokens.access_token, - google_refresh_token: tokens.refresh_token, - }); - capture( - 'maxun-oss-google-sheet-integration-created', - { - user_id: user.id, - robot_id: robot.recording_meta.id, - created_at: new Date().toISOString() - } - ) - - // List user's Google Sheets from their Google Drive - const drive = google.drive({ version: 'v3', auth: oauth2Client }); - const response = await drive.files.list({ - q: "mimeType='application/vnd.google-apps.spreadsheet'", // List only Google Sheets files - fields: 'files(id, name)', // Retrieve the ID and name of each file - }); - - const files = response.data.files || []; - if (files.length === 0) { - return res.status(404).json({ message: 'No spreadsheets found.' }); - } - - // Generate JWT token for session - const jwtToken = jwt.sign({ userId: user.id }, process.env.JWT_SECRET as string, { expiresIn: '12h' }); - res.cookie('token', jwtToken, { httpOnly: true }); - - // res.json({ - // message: 'Google authentication successful', - // google_sheet_email: robot.google_sheet_email, - // jwtToken, - // files - // }); - - res.redirect(`http://localhost:5173`); + if (!state) { + return res.status(400).json({ message: "Robot ID is required" }); + } + + const robotId = state; + + // Get access and refresh tokens + if (typeof code !== "string") { + return res.status(400).json({ message: "Invalid code" }); + } + const { tokens } = await oauth2Client.getToken(code); + oauth2Client.setCredentials(tokens); + + // Get user profile from Google + const oauth2 = google.oauth2({ version: "v2", auth: oauth2Client }); + const { + data: { email }, + } = await oauth2.userinfo.get(); + + if (!email) { + return res.status(400).json({ message: "Email not found" }); + } + + if (!req.user) { + return res.status(401).send({ error: "Unauthorized" }); + } + + // Get the currently authenticated user (from `requireSignIn`) + let user = await User.findOne({ where: { id: req.user.id } }); + + if (!user) { + return res.status(400).json({ message: "User not found" }); + } + + let robot = await Robot.findOne({ + where: { "recording_meta.id": robotId }, + }); + + if (!robot) { + return res.status(400).json({ message: "Robot not found" }); + } + + robot = await robot.update({ + google_sheet_email: email, + google_access_token: tokens.access_token, + google_refresh_token: tokens.refresh_token, + }); + capture("maxun-oss-google-sheet-integration-created", { + user_id: user.id, + robot_id: robot.recording_meta.id, + created_at: new Date().toISOString(), + }); + + // List user's Google Sheets from their Google Drive + const drive = google.drive({ version: "v3", auth: oauth2Client }); + const response = await drive.files.list({ + q: "mimeType='application/vnd.google-apps.spreadsheet'", // List only Google Sheets files + fields: "files(id, name)", // Retrieve the ID and name of each file + }); + + const files = response.data.files || []; + if (files.length === 0) { + return res.status(404).json({ message: "No spreadsheets found." }); + } + + // Generate JWT token for session + const jwtToken = jwt.sign( + { userId: user.id }, + process.env.JWT_SECRET as string, + { expiresIn: "12h" } + ); + res.cookie("token", jwtToken, { httpOnly: true }); + + // res.json({ + // message: 'Google authentication successful', + // google_sheet_email: robot.google_sheet_email, + // jwtToken, + // files + // }); + + res.cookie("robot_auth_status", "success", { + httpOnly: false, + maxAge: 60000, + }); // 1-minute expiration + res.cookie("robot_auth_message", "Robot successfully authenticated", { + httpOnly: false, + maxAge: 60000, + }); + res.redirect(`http://localhost:5173`); } catch (error: any) { - res.status(500).json({ message: `Google OAuth error: ${error.message}` }); + res.status(500).json({ message: `Google OAuth error: ${error.message}` }); } -}); + } +); // Step 3: Get data from Google Sheets -router.post('/gsheets/data', requireSignIn, async (req: AuthenticatedRequest, res) => { +router.post( + "/gsheets/data", + requireSignIn, + async (req: AuthenticatedRequest, res) => { const { spreadsheetId, robotId } = req.body; if (!req.user) { - return res.status(401).send({ error: 'Unauthorized' }); + return res.status(401).send({ error: "Unauthorized" }); } const user = await User.findByPk(req.user.id, { raw: true }); if (!user) { - return res.status(400).json({ message: 'User not found' }); + return res.status(400).json({ message: "User not found" }); } - const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId }, raw: true }); + const robot = await Robot.findOne({ + where: { "recording_meta.id": robotId }, + raw: true, + }); if (!robot) { - return res.status(400).json({ message: 'Robot not found' }); + return res.status(400).json({ message: "Robot not found" }); } // Set Google OAuth credentials oauth2Client.setCredentials({ - access_token: robot.google_access_token, - refresh_token: robot.google_refresh_token, + access_token: robot.google_access_token, + refresh_token: robot.google_refresh_token, }); - const sheets = google.sheets({ version: 'v4', auth: oauth2Client }); + const sheets = google.sheets({ version: "v4", auth: oauth2Client }); try { - // Fetch data from the spreadsheet (you can let the user choose a specific range too) - const sheetData = await sheets.spreadsheets.values.get({ - spreadsheetId, - range: 'Sheet1!A1:D5', // Default range, could be dynamic based on user input - }); - res.json(sheetData.data); + // Fetch data from the spreadsheet (you can let the user choose a specific range too) + const sheetData = await sheets.spreadsheets.values.get({ + spreadsheetId, + range: "Sheet1!A1:D5", // Default range, could be dynamic based on user input + }); + res.json(sheetData.data); } catch (error: any) { - res.status(500).json({ message: `Error accessing Google Sheets: ${error.message}` }); + res + .status(500) + .json({ message: `Error accessing Google Sheets: ${error.message}` }); } -}); + } +); // Step 4: Get user's Google Sheets files (new route) -router.get('/gsheets/files', requireSignIn, async (req, res) => { - try { - const robotId = req.query.robotId; - const robot = await Robot.findOne({ where: { 'recording_meta.id': robotId }, raw: true }); - - if (!robot) { - return res.status(400).json({ message: 'Robot not found' }); - } +router.get("/gsheets/files", requireSignIn, async (req, res) => { + try { + const robotId = req.query.robotId; + const robot = await Robot.findOne({ + where: { "recording_meta.id": robotId }, + raw: true, + }); - oauth2Client.setCredentials({ - access_token: robot.google_access_token, - refresh_token: robot.google_refresh_token, - }); + if (!robot) { + return res.status(400).json({ message: "Robot not found" }); + } - // List user's Google Sheets files from their Google Drive - const drive = google.drive({ version: 'v3', auth: oauth2Client }); - const response = await drive.files.list({ - q: "mimeType='application/vnd.google-apps.spreadsheet'", - fields: 'files(id, name)', - }); + oauth2Client.setCredentials({ + access_token: robot.google_access_token, + refresh_token: robot.google_refresh_token, + }); - const files = response.data.files || []; - if (files.length === 0) { - return res.status(404).json({ message: 'No spreadsheets found.' }); - } + // List user's Google Sheets files from their Google Drive + const drive = google.drive({ version: "v3", auth: oauth2Client }); + const response = await drive.files.list({ + q: "mimeType='application/vnd.google-apps.spreadsheet'", + fields: "files(id, name)", + }); - res.json(files); - } catch (error: any) { - console.log('Error fetching Google Sheets files:', error); - res.status(500).json({ message: `Error retrieving Google Sheets files: ${error.message}` }); + const files = response.data.files || []; + if (files.length === 0) { + return res.status(404).json({ message: "No spreadsheets found." }); } + + res.json(files); + } catch (error: any) { + console.log("Error fetching Google Sheets files:", error); + res + .status(500) + .json({ + message: `Error retrieving Google Sheets files: ${error.message}`, + }); + } }); // Step 5: Update robot's google_sheet_id when a Google Sheet is selected -router.post('/gsheets/update', requireSignIn, async (req, res) => { - const { spreadsheetId, spreadsheetName, robotId } = req.body; +router.post("/gsheets/update", requireSignIn, async (req, res) => { + const { spreadsheetId, spreadsheetName, robotId } = req.body; + + if (!spreadsheetId || !robotId) { + return res + .status(400) + .json({ message: "Spreadsheet ID and Robot ID are required" }); + } + + try { + let robot = await Robot.findOne({ + where: { "recording_meta.id": robotId }, + }); - if (!spreadsheetId || !robotId) { - return res.status(400).json({ message: 'Spreadsheet ID and Robot ID are required' }); + if (!robot) { + return res.status(404).json({ message: "Robot not found" }); } - try { - let robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); - - if (!robot) { - return res.status(404).json({ message: 'Robot not found' }); - } - - await robot.update({ google_sheet_id: spreadsheetId, google_sheet_name: spreadsheetName }); + await robot.update({ + google_sheet_id: spreadsheetId, + google_sheet_name: spreadsheetName, + }); - res.json({ message: 'Robot updated with selected Google Sheet ID' }); - } catch (error: any) { - res.status(500).json({ message: `Error updating robot: ${error.message}` }); - } + res.json({ message: "Robot updated with selected Google Sheet ID" }); + } catch (error: any) { + res.status(500).json({ message: `Error updating robot: ${error.message}` }); + } }); -router.post('/gsheets/remove', requireSignIn, async (req: AuthenticatedRequest, res) => { +router.post( + "/gsheets/remove", + requireSignIn, + async (req: AuthenticatedRequest, res) => { const { robotId } = req.body; if (!robotId) { - return res.status(400).json({ message: 'Robot ID is required' }); + return res.status(400).json({ message: "Robot ID is required" }); } if (!req.user) { - return res.status(401).send({ error: 'Unauthorized' }); + return res.status(401).send({ error: "Unauthorized" }); } try { - let robot = await Robot.findOne({ where: { 'recording_meta.id': robotId } }); - - if (!robot) { - return res.status(404).json({ message: 'Robot not found' }); - } - - await robot.update({ - google_sheet_id: null, - google_sheet_name: null, - google_sheet_email: null, - google_access_token: null, - google_refresh_token: null - }); - - capture( - 'maxun-oss-google-sheet-integration-removed', - { - user_id: req.user.id, - robot_id: robotId, - deleted_at: new Date().toISOString() - } - ) - - res.json({ message: 'Google Sheets integration removed successfully' }); + let robot = await Robot.findOne({ + where: { "recording_meta.id": robotId }, + }); + + if (!robot) { + return res.status(404).json({ message: "Robot not found" }); + } + + await robot.update({ + google_sheet_id: null, + google_sheet_name: null, + google_sheet_email: null, + google_access_token: null, + google_refresh_token: null, + }); + + capture("maxun-oss-google-sheet-integration-removed", { + user_id: req.user.id, + robot_id: robotId, + deleted_at: new Date().toISOString(), + }); + + res.json({ message: "Google Sheets integration removed successfully" }); } catch (error: any) { - res.status(500).json({ message: `Error removing Google Sheets integration: ${error.message}` }); + res + .status(500) + .json({ + message: `Error removing Google Sheets integration: ${error.message}`, + }); } -}); + } +); From 7197bbffe60dd6d8b248d2b13a82165c63f19c8f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 13 Nov 2024 00:05:41 +0530 Subject: [PATCH 037/165] chore: js-cookie added --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 48d616788..b25715d43 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "devDependencies": { "@types/cookie-parser": "^1.4.7", "@types/express": "^4.17.13", + "@types/js-cookie": "^3.0.6", "@types/loglevel": "^1.6.3", "@types/node": "22.7.9", "@types/node-cron": "^3.0.11", @@ -103,6 +104,7 @@ "ajv": "^8.8.2", "concurrently": "^7.0.0", "cross-env": "^7.0.3", + "js-cookie": "^3.0.5", "nodemon": "^2.0.15", "ts-node": "^10.4.0", "vite": "^5.4.10" From c033ff5255d3b7f3dfcffd6fa73c24535396b5e8 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 13 Nov 2024 00:06:32 +0530 Subject: [PATCH 038/165] chore: google integration auth notif --- .../molecules/IntegrationSettings.tsx | 508 ++++++++++-------- 1 file changed, 296 insertions(+), 212 deletions(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 1489994c8..005a0e680 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -1,238 +1,322 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from "react"; import { GenericModal } from "../atoms/GenericModal"; -import { MenuItem, Typography, CircularProgress, Alert, AlertTitle, Chip } from "@mui/material"; +import { + MenuItem, + Typography, + CircularProgress, + Alert, + AlertTitle, + Chip, +} from "@mui/material"; import Button from "@mui/material/Button"; import TextField from "@mui/material/TextField"; -import axios from 'axios'; -import { useGlobalInfoStore } from '../../context/globalInfo'; -import { getStoredRecording } from '../../api/storage'; -import { apiUrl } from '../../apiConfig.js'; +import axios from "axios"; +import { useGlobalInfoStore } from "../../context/globalInfo"; +import { getStoredRecording } from "../../api/storage"; +import { apiUrl } from "../../apiConfig.js"; +import Cookies from 'js-cookie'; interface IntegrationProps { - isOpen: boolean; - handleStart: (data: IntegrationSettings) => void; - handleClose: () => void; + isOpen: boolean; + handleStart: (data: IntegrationSettings) => void; + handleClose: () => void; } export interface IntegrationSettings { - spreadsheetId: string; - spreadsheetName: string; - data: string; + spreadsheetId: string; + spreadsheetName: string; + data: string; } -export const IntegrationSettingsModal = ({ isOpen, handleStart, handleClose }: IntegrationProps) => { - const [settings, setSettings] = useState({ - spreadsheetId: '', - spreadsheetName: '', - data: '', - }); +export const IntegrationSettingsModal = ({ + isOpen, + handleStart, + handleClose, +}: IntegrationProps) => { + const [settings, setSettings] = useState({ + spreadsheetId: "", + spreadsheetName: "", + data: "", + }); - const [spreadsheets, setSpreadsheets] = useState<{ id: string, name: string }[]>([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); + const [spreadsheets, setSpreadsheets] = useState< + { id: string; name: string }[] + >([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); - const { recordingId, notify } = useGlobalInfoStore(); - const [recording, setRecording] = useState(null); + const { recordingId, notify } = useGlobalInfoStore(); + const [recording, setRecording] = useState(null); - const authenticateWithGoogle = () => { - window.location.href = `${apiUrl}/auth/google?robotId=${recordingId}`; - }; + const authenticateWithGoogle = () => { + window.location.href = `${apiUrl}/auth/google?robotId=${recordingId}`; + }; - const handleOAuthCallback = async () => { - try { - const response = await axios.get(`${apiUrl}/auth/google/callback`); - const { google_sheet_email, files } = response.data; - } catch (error) { - setError('Error authenticating with Google'); - } - }; + const handleOAuthCallback = async () => { + try { + const response = await axios.get(`${apiUrl}/auth/google/callback`); + const { google_sheet_email, files } = response.data; + } catch (error) { + setError("Error authenticating with Google"); + } + }; - const fetchSpreadsheetFiles = async () => { - try { - const response = await axios.get(`${apiUrl}/auth/gsheets/files?robotId=${recordingId}`, { - withCredentials: true, - }); - setSpreadsheets(response.data); - } catch (error: any) { - console.error('Error fetching spreadsheet files:', error.response?.data?.message || error.message); - notify('error', `Error fetching spreadsheet files: ${error.response?.data?.message || error.message}`); + const fetchSpreadsheetFiles = async () => { + try { + const response = await axios.get( + `${apiUrl}/auth/gsheets/files?robotId=${recordingId}`, + { + withCredentials: true, } - }; + ); + setSpreadsheets(response.data); + } catch (error: any) { + console.error( + "Error fetching spreadsheet files:", + error.response?.data?.message || error.message + ); + notify( + "error", + `Error fetching spreadsheet files: ${ + error.response?.data?.message || error.message + }` + ); + } + }; - const handleSpreadsheetSelect = (e: React.ChangeEvent) => { - const selectedSheet = spreadsheets.find(sheet => sheet.id === e.target.value); - if (selectedSheet) { - setSettings({ ...settings, spreadsheetId: selectedSheet.id, spreadsheetName: selectedSheet.name }); - } - }; + const handleSpreadsheetSelect = (e: React.ChangeEvent) => { + const selectedSheet = spreadsheets.find( + (sheet) => sheet.id === e.target.value + ); + if (selectedSheet) { + setSettings({ + ...settings, + spreadsheetId: selectedSheet.id, + spreadsheetName: selectedSheet.name, + }); + } + }; - const updateGoogleSheetId = async () => { - try { - const response = await axios.post( - `${apiUrl}/auth/gsheets/update`, - { spreadsheetId: settings.spreadsheetId, spreadsheetName: settings.spreadsheetName, robotId: recordingId }, - { withCredentials: true } - ); - console.log('Google Sheet ID updated:', response.data); - } catch (error: any) { - console.error('Error updating Google Sheet ID:', error.response?.data?.message || error.message); - } - }; + const updateGoogleSheetId = async () => { + try { + const response = await axios.post( + `${apiUrl}/auth/gsheets/update`, + { + spreadsheetId: settings.spreadsheetId, + spreadsheetName: settings.spreadsheetName, + robotId: recordingId, + }, + { withCredentials: true } + ); + console.log("Google Sheet ID updated:", response.data); + } catch (error: any) { + console.error( + "Error updating Google Sheet ID:", + error.response?.data?.message || error.message + ); + } + }; - const removeIntegration = async () => { - try { - await axios.post( - `${apiUrl}/auth/gsheets/remove`, - { robotId: recordingId }, - { withCredentials: true } - ); - - setRecording(null); - setSpreadsheets([]); - setSettings({ spreadsheetId: '', spreadsheetName: '', data: '' }); - } catch (error: any) { - console.error('Error removing Google Sheets integration:', error.response?.data?.message || error.message); - } + const removeIntegration = async () => { + try { + await axios.post( + `${apiUrl}/auth/gsheets/remove`, + { robotId: recordingId }, + { withCredentials: true } + ); + + setRecording(null); + setSpreadsheets([]); + setSettings({ spreadsheetId: "", spreadsheetName: "", data: "" }); + } catch (error: any) { + console.error( + "Error removing Google Sheets integration:", + error.response?.data?.message || error.message + ); + } + }; + + useEffect(() => { + // Check if there is a success message in cookies + const status = Cookies.get("robot_auth_status"); + const message = Cookies.get("robot_auth_message"); + + if (status === "success" && message) { + notify("success", message); + // Clear the cookies after reading + Cookies.remove("robot_auth_status"); + Cookies.remove("robot_auth_message"); + } + + // Check if we're on the callback URL + const urlParams = new URLSearchParams(window.location.search); + const code = urlParams.get("code"); + if (code) { + handleOAuthCallback(); + } + + const fetchRecordingInfo = async () => { + if (!recordingId) return; + const recording = await getStoredRecording(recordingId); + if (recording) { + setRecording(recording); + } }; - useEffect(() => { - // Check if we're on the callback URL - const urlParams = new URLSearchParams(window.location.search); - const code = urlParams.get('code'); - if (code) { - handleOAuthCallback(); - } + fetchRecordingInfo(); + }, [recordingId]); + + return ( + +
+ + Integrate with Google Sheet{" "} + + - const fetchRecordingInfo = async () => { - if (!recordingId) return; - const recording = await getStoredRecording(recordingId); - if (recording) { - setRecording(recording); - } - }; - - fetchRecordingInfo(); - }, [recordingId]); - - return ( - -
- Integrate with Google Sheet - - {recording && recording.google_sheet_id ? ( - <> - - Google Sheet Integrated Successfully. - Every time this robot creates a successful run, its captured data is appended to your {recording.google_sheet_name} Google Sheet. You can check the data updates here. -
- Note: The data extracted before integrating with Google Sheets will not be synced in the Google Sheet. Only the data extracted after the integration will be synced. -
- - + {recording && recording.google_sheet_id ? ( + <> + + Google Sheet Integrated Successfully. + Every time this robot creates a successful run, its captured data + is appended to your {recording.google_sheet_name} Google Sheet. + You can check the data updates{" "} + + here + + . +
+ Note: The data extracted before integrating with + Google Sheets will not be synced in the Google Sheet. Only the + data extracted after the integration will be synced. +
+ + + ) : ( + <> + {!recording?.google_sheet_email ? ( + <> +

+ If you enable this option, every time this robot runs a task + successfully, its captured data will be appended to your + Google Sheet. +

+ + + ) : ( + <> + {recording.google_sheet_email && ( + + Authenticated as: {recording.google_sheet_email} + + )} + + {loading ? ( + + ) : error ? ( + {error} + ) : spreadsheets.length === 0 ? ( + <> +
+ + +
+ ) : ( - <> - {!recording?.google_sheet_email ? ( - <> -

If you enable this option, every time this robot runs a task successfully, its captured data will be appended to your Google Sheet.

- - - ) : ( - <> - {recording.google_sheet_email && ( - - Authenticated as: {recording.google_sheet_email} - - )} - - {loading ? ( - - ) : error ? ( - {error} - ) : spreadsheets.length === 0 ? ( - <> -
- - -
- - ) : ( - <> - - {spreadsheets.map(sheet => ( - - {sheet.name} - - ))} - - - {settings.spreadsheetId && ( - - Selected Sheet: {spreadsheets.find(s => s.id === settings.spreadsheetId)?.name} (ID: {settings.spreadsheetId}) - - )} - - - - )} - - )} - + <> + + {spreadsheets.map((sheet) => ( + + {sheet.name} + + ))} + + + {settings.spreadsheetId && ( + + Selected Sheet:{" "} + { + spreadsheets.find( + (s) => s.id === settings.spreadsheetId + )?.name + }{" "} + (ID: {settings.spreadsheetId}) + + )} + + + )} -
-
- ); + + )} + + )} +
+
+ ); }; export const modalStyle = { - top: '40%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '50%', - backgroundColor: 'background.paper', - p: 4, - height: 'fit-content', - display: 'block', - padding: '20px', + top: "40%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "50%", + backgroundColor: "background.paper", + p: 4, + height: "fit-content", + display: "block", + padding: "20px", }; From d42462c520c642d39c944fbce57141144973b923 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 13 Nov 2024 19:40:35 +0530 Subject: [PATCH 039/165] fix: export to csv ui changes --- src/components/molecules/RunContent.tsx | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/components/molecules/RunContent.tsx b/src/components/molecules/RunContent.tsx index 5b0415251..ff22a3a85 100644 --- a/src/components/molecules/RunContent.tsx +++ b/src/components/molecules/RunContent.tsx @@ -122,19 +122,8 @@ export const RunContent = ({ row, currentLog, interpretationInProgress, logEndRe - Download as CSV + Download as CSV {tableData.length > 0 ? ( From 8538ca015b7cf53f3c21a50f608ea970387ced26 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 13 Nov 2024 23:08:44 +0530 Subject: [PATCH 040/165] fix: remote browser rendering --- server/src/browser-management/classes/RemoteBrowser.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index cbda39425..d63bd55ad 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -307,6 +307,7 @@ export class RemoteBrowser { const workflow = this.generator.AddGeneratedFlags(this.generator.getWorkflowFile()); await this.initializeNewPage(); if (this.currentPage) { + this.currentPage.setViewportSize({ height: 400, width: 900 }); const params = this.generator.getParams(); if (params) { this.interpreterSettings.params = params.reduce((acc, param) => { From a4a25a6f8355153a9f30c85ab8e03321c8a5db00 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 14 Nov 2024 06:58:48 +0530 Subject: [PATCH 041/165] chore: add jwt env example --- ENVEXAMPLE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ENVEXAMPLE b/ENVEXAMPLE index d17b1fc5d..4dc30b973 100644 --- a/ENVEXAMPLE +++ b/ENVEXAMPLE @@ -1,6 +1,6 @@ # App Setup NODE_ENV=production # Set to 'development' or 'production' as required -JWT_SECRET=your_jwt_secret_key # Replace with a secure JWT secret key +JWT_SECRET=a9Z$kLq7^f03GzNw!bP9dH4xV6sT2yXl3O8vR@uYq3 # Replace with a secure JWT secret key DB_NAME=maxun # Your PostgreSQL database name DB_USER=postgres # PostgreSQL username DB_PASSWORD=postgres # PostgreSQL password From e586ec54b758705e4def6a4625f0cc36cd1a8be5 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 14 Nov 2024 06:59:27 +0530 Subject: [PATCH 042/165] chore: example env value for encryption key --- ENVEXAMPLE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ENVEXAMPLE b/ENVEXAMPLE index 4dc30b973..a387785f6 100644 --- a/ENVEXAMPLE +++ b/ENVEXAMPLE @@ -6,7 +6,7 @@ DB_USER=postgres # PostgreSQL username DB_PASSWORD=postgres # PostgreSQL password DB_HOST=postgres # Host for PostgreSQL in Docker DB_PORT=5432 # Port for PostgreSQL (default: 5432) -ENCRYPTION_KEY=your_encryption_key # Key for encrypting sensitive data (passwords and proxies) +ENCRYPTION_KEY=f4d5e6a7b8c9d0e1f23456789abcdef01234567890abcdef123456789abcdef0 # Key for encrypting sensitive data (passwords and proxies) MINIO_ENDPOINT=minio # MinIO endpoint in Docker MINIO_PORT=9000 # Port for MinIO (default: 9000) MINIO_ACCESS_KEY=minio_access_key # MinIO access key From 6b16d4a0230869c87712c70ae7825fc752678fc4 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 14 Nov 2024 07:06:19 +0530 Subject: [PATCH 043/165] chore: add default values --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 602cb05ca..799484122 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ You can access the frontend at http://localhost:5173/ and backend at http://loca | Variable | Mandatory | Description | If Not Set | |-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| `BACKEND_URL` | Yes | URL to run backend on. | Backend won't start. If not sure, set to http://localhost:8080 | -| `VITE_BACKEND_URL` | Yes | URL to run backend on. | Backend won't start. If not sure, set to http://localhost:8080 | +| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | +| `VITE_BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | | `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | | `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | | `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | From 1bf716d800f64b250609f428357c0a1c39b5cc18 Mon Sep 17 00:00:00 2001 From: Naveen Date: Thu, 14 Nov 2024 07:06:59 +0530 Subject: [PATCH 044/165] chore: frontend backend connection url --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 799484122..4b59ad226 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,8 @@ You can access the frontend at http://localhost:5173/ and backend at http://loca | Variable | Mandatory | Description | If Not Set | |-----------------------|-----------|----------------------------------------------------------------------------------------------|--------------------------------------------------------------| -| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | -| `VITE_BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | +| `BACKEND_URL` | Yes | URL to run backend on. | Default value: http://localhost:8080 | +| `VITE_BACKEND_URL` | Yes | URL used by frontend to connect to backend | Default value: http://localhost:8080 | | `JWT_SECRET` | Yes | Secret key used to sign and verify JSON Web Tokens (JWTs) for authentication. | JWT authentication will not work. | | `DB_NAME` | Yes | Name of the Postgres database to connect to. | Database connection will fail. | | `DB_USER` | Yes | Username for Postgres database authentication. | Database connection will fail. | From fa7f5e3748ce5e53498e8b89995eb0d5ad6bbea2 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Thu, 14 Nov 2024 07:32:43 +0530 Subject: [PATCH 045/165] chore: add gh trending icon --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 4b59ad226..e072af784 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web Twitter | Join Maxun Cloud | Watch Tutorials +
+
+getmaxun%2Fmaxun | Trendshift

![maxun_demo](https://github.com/user-attachments/assets/a61ba670-e56a-4ae1-9681-0b4bd6ba9cdc) From 87a3dd4143e35ff4baa625d24bd19244c0680d15 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 15 Nov 2024 06:07:43 +0530 Subject: [PATCH 046/165] chore: -rm unwanted deps --- server/Dockerfile | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/server/Dockerfile b/server/Dockerfile index a99042693..ae26e8ebc 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -18,33 +18,6 @@ RUN npm install # Install Playwright browsers and dependencies RUN npx playwright install --with-deps chromium -# Install xvfb for display support -#RUN apt-get update && apt-get install -y xvfb - -# RUN apt-get update && apt-get install -y \ -# libgbm-dev \ -# libxkbcommon-x11-0 \ -# libxcomposite1 \ -# libxdamage1 \ -# libxrandr2 \ -# libxshmfence1 \ -# libxtst6 \ -# libnss3 \ -# libatk1.0-0 \ -# libatk-bridge2.0-0 \ -# libdrm2 \ -# libxcb1 \ -# libxkbcommon0 \ -# fonts-noto-color-emoji \ -# fonts-unifont \ -# libpango-1.0-0 \ -# libcairo2 \ -# libasound2 \ -# libglib2.0-0 \ -# libdbus-1-3 \ -# && rm -rf /var/lib/apt/lists/* - -# Create and set permissions for chrome directories # Create the Chromium data directory with necessary permissions RUN mkdir -p /tmp/chromium-data-dir && \ chmod -R 777 /tmp/chromium-data-dir From 8eedca1b1e7591db3cdcbc1ee25c59f929c68c0d Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Fri, 15 Nov 2024 16:16:01 +0530 Subject: [PATCH 047/165] Recording deletion warning while runs are active --- server/src/api/record.ts | 3 +- src/api/storage.ts | 44 +++++++++++++++++++- src/components/molecules/RecordingsTable.tsx | 16 ++++++- src/components/organisms/ApiKey.tsx | 3 ++ src/components/organisms/BrowserWindow.tsx | 3 +- 5 files changed, 64 insertions(+), 5 deletions(-) diff --git a/server/src/api/record.ts b/server/src/api/record.ts index b55f06bc2..573a8edb1 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -289,7 +289,7 @@ router.get("/robots/:id", requireAPIKey, async (req: Request, res: Response) => * type: string * example: "Failed to retrieve runs" */ -router.get("/robots/:id/runs", requireAPIKey, async (req: Request, res: Response) => { +router.get("/robots/:id/runs",requireAPIKey, async (req: Request, res: Response) => { try { const runs = await Run.findAll({ where: { @@ -321,6 +321,7 @@ router.get("/robots/:id/runs", requireAPIKey, async (req: Request, res: Response } ); + function formatRunResponse(run: any) { const formattedRun = { id: run.id, diff --git a/src/api/storage.ts b/src/api/storage.ts index 9b4b06b2d..22cd9a286 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -5,6 +5,10 @@ import { ScheduleSettings } from "../components/molecules/ScheduleSettings"; import { CreateRunResponse, ScheduleRunResponse } from "../pages/MainPage"; import { apiUrl } from "../apiConfig"; + + + + export const getStoredRecordings = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/recordings`); @@ -47,16 +51,52 @@ export const getStoredRecording = async (id: string) => { } } + +export const checkRunsForRecording = async (id: string): Promise => { + const apiKey = localStorage.getItem('x-api-key'); + + // Check if the API key exists + if (!apiKey) { + console.error('API key is missing.'); + return false; + } + + try { + const response = await axios.get(`${apiUrl}/api/robots/${id}/runs`, { + headers: { + 'x-api-key': apiKey, // Pass the valid API key in the header + }, + withCredentials: true, + }); + + const runs = response.data; + return runs.runs.totalCount > 0; + } catch (error) { + console.error('Error checking runs for recording:', error); + return false; + } +}; + export const deleteRecordingFromStorage = async (id: string): Promise => { + + const hasRuns = await checkRunsForRecording(id); + + if (hasRuns) { + + return false; + } + try { const response = await axios.delete(`${apiUrl}/storage/recordings/${id}`); if (response.status === 200) { - return response.data; + + return true; } else { throw new Error(`Couldn't delete stored recording ${id}`); } } catch (error: any) { console.log(error); + return false; } }; @@ -93,7 +133,7 @@ export const createRunForStoredRecording = async (id: string, settings: RunSetti try { const response = await axios.put( `${apiUrl}/storage/runs/${id}`, - { ...settings }); + { ...settings }); if (response.status === 200) { return response.data; } else { diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index c58951939..557b0b707 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -13,7 +13,7 @@ import { IconButton, Button, Box, Typography, TextField } from "@mui/material"; import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power } from "@mui/icons-material"; import LinkIcon from '@mui/icons-material/Link'; import { useGlobalInfoStore } from "../../context/globalInfo"; -import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage"; +import { checkRunsForRecording, deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage"; import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; @@ -159,6 +159,13 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl } }, []); + const hasAssociatedRuns = async (robotId: string): Promise => { + + const associatedRuns = await fetch(`/api/robot/${robotId}/runs`); + const data = await associatedRuns.json(); + return data.length > 0; + }; + return ( @@ -252,6 +259,13 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl return ( { + checkRunsForRecording(row.id).then((result: boolean) => { + if (result) { + notify('warning', 'Recording has associated runs, please delete them first'); + } + + }) + deleteRecordingFromStorage(row.id).then((result: boolean) => { if (result) { setRows([]); diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index d9f4c6feb..496755387 100644 --- a/src/components/organisms/ApiKey.tsx +++ b/src/components/organisms/ApiKey.tsx @@ -36,6 +36,8 @@ const ApiKeyManager = () => { const [copySuccess, setCopySuccess] = useState(false); const { notify } = useGlobalInfoStore(); + + useEffect(() => { const fetchApiKey = async () => { try { @@ -56,6 +58,7 @@ const ApiKeyManager = () => { try { const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); setApiKey(data.api_key); + localStorage.setItem('x-api-key', data.api_key); notify('success', `Generated API Key successfully`); } catch (error: any) { notify('error', `Failed to generate API Key - ${error.message}`); diff --git a/src/components/organisms/BrowserWindow.tsx b/src/components/organisms/BrowserWindow.tsx index cbc467315..697b4adb1 100644 --- a/src/components/organisms/BrowserWindow.tsx +++ b/src/components/organisms/BrowserWindow.tsx @@ -8,6 +8,7 @@ import { useActionContext } from '../../context/browserActions'; import { useBrowserSteps, TextStep } from '../../context/browserSteps'; import { useGlobalInfoStore } from '../../context/globalInfo'; + interface ElementInfo { tagName: string; hasOnlyText?: boolean; @@ -316,7 +317,7 @@ export const BrowserWindow = () => { } }, [paginationMode, resetPaginationSelector]); - + return (
{ From 8d4994c8c723b96bf386667d5020f49b58fcf045 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Fri, 15 Nov 2024 22:26:36 +0530 Subject: [PATCH 048/165] Route corrected --- server/src/routes/storage.ts | 55 ++++++++++++++++++++ src/api/storage.ts | 26 ++++----- src/components/molecules/RecordingsTable.tsx | 39 ++++++++------ src/components/organisms/ApiKey.tsx | 5 +- 4 files changed, 93 insertions(+), 32 deletions(-) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 228f60dee..833e6e6bd 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -16,6 +16,7 @@ import { workflowQueue } from '../worker'; import { AuthenticatedRequest } from './record'; import { computeNextRun } from '../utils/schedule'; import { capture } from "../utils/analytics"; +import { tryCatch } from 'bullmq'; export const router = Router(); @@ -57,6 +58,60 @@ router.get('/recordings/:id', requireSignIn, async (req, res) => { } }) +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; +} + /** * DELETE endpoint for deleting a recording from the storage. */ diff --git a/src/api/storage.ts b/src/api/storage.ts index 22cd9a286..e48c7091f 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -9,6 +9,7 @@ import { apiUrl } from "../apiConfig"; + export const getStoredRecordings = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/recordings`); @@ -52,24 +53,15 @@ export const getStoredRecording = async (id: string) => { } -export const checkRunsForRecording = async (id: string): Promise => { - const apiKey = localStorage.getItem('x-api-key'); - - // Check if the API key exists - if (!apiKey) { - console.error('API key is missing.'); - return false; - } +export const checkRunsForRecording = async (id: string): Promise => { + + try { - const response = await axios.get(`${apiUrl}/api/robots/${id}/runs`, { - headers: { - 'x-api-key': apiKey, // Pass the valid API key in the header - }, - withCredentials: true, - }); + const response = await axios.get(`${apiUrl}/storage/recordings/${id}/runs`); const runs = response.data; + console.log(runs.runs.totalCount) return runs.runs.totalCount > 0; } catch (error) { console.error('Error checking runs for recording:', error); @@ -77,6 +69,7 @@ export const checkRunsForRecording = async (id: string): Promise => { } }; + export const deleteRecordingFromStorage = async (id: string): Promise => { const hasRuns = await checkRunsForRecording(id); @@ -85,7 +78,6 @@ export const deleteRecordingFromStorage = async (id: string): Promise = return false; } - try { const response = await axios.delete(`${apiUrl}/storage/recordings/${id}`); if (response.status === 200) { @@ -99,6 +91,10 @@ export const deleteRecordingFromStorage = async (id: string): Promise = return false; } + + + + }; export const deleteRunFromStorage = async (id: string): Promise => { diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 557b0b707..36a66fbe6 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -18,6 +18,8 @@ import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; import { GenericModal } from '../atoms/GenericModal'; +import axios from 'axios'; +import { apiUrl } from '../../apiConfig'; /** TODO: @@ -159,12 +161,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl } }, []); - const hasAssociatedRuns = async (robotId: string): Promise => { - - const associatedRuns = await fetch(`/api/robot/${robotId}/runs`); - const data = await associatedRuns.json(); - return data.length > 0; - }; + return ( @@ -258,21 +255,31 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl case 'delete': return ( - { + { + checkRunsForRecording(row.id).then((result: boolean) => { if (result) { - notify('warning', 'Recording has associated runs, please delete them first'); + notify('warning', 'Cannot delete recording as it has active runs'); } - }) + + + deleteRecordingFromStorage(row.id).then((result: boolean) => { + if (result) { + setRows([]); + notify('success', 'Recording deleted successfully'); + fetchRecordings(); + } + }) + - deleteRecordingFromStorage(row.id).then((result: boolean) => { - if (result) { - setRows([]); - notify('success', 'Recording deleted successfully'); - fetchRecordings(); - } - }) + + + + + + + }}> diff --git a/src/components/organisms/ApiKey.tsx b/src/components/organisms/ApiKey.tsx index 496755387..675edb726 100644 --- a/src/components/organisms/ApiKey.tsx +++ b/src/components/organisms/ApiKey.tsx @@ -38,6 +38,8 @@ const ApiKeyManager = () => { + + useEffect(() => { const fetchApiKey = async () => { try { @@ -51,6 +53,7 @@ const ApiKeyManager = () => { }; fetchApiKey(); + }, []); const generateApiKey = async () => { @@ -58,7 +61,7 @@ const ApiKeyManager = () => { try { const { data } = await axios.post(`${apiUrl}/auth/generate-api-key`); setApiKey(data.api_key); - localStorage.setItem('x-api-key', data.api_key); + notify('success', `Generated API Key successfully`); } catch (error: any) { notify('error', `Failed to generate API Key - ${error.message}`); From 543c08bc3fc22c943856192baf9a71d90f7a0ed8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 15 Nov 2024 23:39:37 +0530 Subject: [PATCH 049/165] chore: -rm bucket was not found message --- server/src/storage/mino.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/storage/mino.ts b/server/src/storage/mino.ts index d2146228a..3fc3f3cfe 100644 --- a/server/src/storage/mino.ts +++ b/server/src/storage/mino.ts @@ -14,7 +14,7 @@ minioClient.bucketExists('maxun-test') if (exists) { console.log('MinIO was connected successfully.'); } else { - console.log('Bucket does not exist, but MinIO was connected.'); + console.log('MinIO was connected.'); } }) .catch((err) => { From 4b8d24294a33dabe9b1d32af513a658ef6916b6f Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 15 Nov 2024 23:40:21 +0530 Subject: [PATCH 050/165] chore: clearer succcess message --- server/src/storage/mino.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/storage/mino.ts b/server/src/storage/mino.ts index 3fc3f3cfe..0f29c3cdf 100644 --- a/server/src/storage/mino.ts +++ b/server/src/storage/mino.ts @@ -12,9 +12,9 @@ const minioClient = new Client({ minioClient.bucketExists('maxun-test') .then((exists) => { if (exists) { - console.log('MinIO was connected successfully.'); + console.log('MinIO connected successfully.'); } else { - console.log('MinIO was connected.'); + console.log('MinIO connected successfully.'); } }) .catch((err) => { From 079099692dff63b5366e21dbd1472bc0365f5df8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 15 Nov 2024 23:47:47 +0530 Subject: [PATCH 051/165] chore: create .dockerignore --- .dockerignore | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..d0f51343b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +node_modules +npm-debug.log +dist +.git +.gitignore +.env +*.md +.vscode +coverage +docker-compose.yml +Dockerfile* \ No newline at end of file From f2f883fae6e7c711ca6a52595b188b68c961ec1d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 15 Nov 2024 23:54:40 +0530 Subject: [PATCH 052/165] fix: syntax --- .dockerignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.dockerignore b/.dockerignore index d0f51343b..b675b2763 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,8 +4,8 @@ dist .git .gitignore .env -*.md +.md .vscode coverage docker-compose.yml -Dockerfile* \ No newline at end of file +Dockerfile \ No newline at end of file From 4f08bfac9d6bf871e8459bd014a5219e26aa96fe Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 15:51:25 +0530 Subject: [PATCH 053/165] centered the forms --- src/pages/Login.tsx | 7 ++++++- src/pages/Register.tsx | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 4e4a1013e..62db2f5d6 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -58,7 +58,11 @@ const Login = () => { display: 'flex', flexDirection: 'column', alignItems: 'center', - mt: 5, + justifyContent: 'center', + + + height: "calc(100vh - 64px)", + }} > @@ -112,6 +116,7 @@ const Login = () => { + ); }; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 8c92fe7bb..a138a9736 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -56,7 +56,10 @@ const Register = () => { display: 'flex', flexDirection: 'column', alignItems: 'center', - mt: 5, + justifyContent: 'center', + + + height: "calc(100vh - 64px)", }} > From 069bcadccdc6e45d468cb76b4e103c0360b64310 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 16:13:54 +0530 Subject: [PATCH 054/165] bg changed --- src/pages/Login.tsx | 5 +++-- src/pages/Register.tsx | 10 +++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 62db2f5d6..7a637ccfb 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -59,16 +59,17 @@ const Login = () => { flexDirection: 'column', alignItems: 'center', justifyContent: 'center', + backgroundColor: '#f5f5f5', height: "calc(100vh - 64px)", }} > - + + Welcome Back! - { flexDirection: 'column', alignItems: 'center', justifyContent: 'center', + height: "calc(100vh - 64px)", + backgroundColor: '#f5f5f5', }} > - + + + Create an account - { - + + ); }; From 0b933c3feeb093a260a60b17e2bef56ae547e95e Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 16:29:37 +0530 Subject: [PATCH 055/165] fill color changed to pink --- src/pages/Login.tsx | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 7a637ccfb..8ed712f39 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -79,6 +79,21 @@ const Login = () => { margin="normal" variant="outlined" required + sx={{ + '& .MuiOutlinedInput-root': { + backgroundColor: email ? '#ffe6f9' : '#ffffff', + // backgroundColor: ' #ffe6f9', // Change fill color here + '& fieldset': { + borderColor: '#ff33cc', // Border color (optional) + }, + '&:hover fieldset': { + borderColor: '#ff33cc', // Hover border color (optional) + }, + '&.Mui-focused fieldset': { + borderColor: '#ff33cc', // Focus border color (optional) + }, + }, + }} /> { margin="normal" variant="outlined" required + sx={{ + '& .MuiOutlinedInput-root': { + backgroundColor: password ? '#ffe6f9' : '#ffffff', + // backgroundColor: ' #ffe6f9', // Change fill color here + '& fieldset': { + borderColor: '#ff33cc', // Border color (optional) + }, + '&:hover fieldset': { + borderColor: '#ff33cc', // Hover border color (optional) + }, + '&.Mui-focused fieldset': { + borderColor: '#ff33cc', // Focus border color (optional) + }, + }, + }} /> + - - Don’t have an account?{' '} - - Register - - - - - - ); + + Don’t have an account?{" "} + + Register + + + + + ); }; export default Login; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index c5bdbde0b..59006c767 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,124 +1,202 @@ -import { useState, useContext, useEffect } from 'react'; -import { useNavigate, Link } from 'react-router-dom'; -import axios from 'axios'; -import { AuthContext } from '../context/auth'; -import { TextField, Button, CircularProgress, Typography, Box, Container } from '@mui/material'; +import { useState, useContext, useEffect } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import axios from "axios"; +import { AuthContext } from "../context/auth"; +import { + TextField, + Button, + CircularProgress, + Typography, + Box, + Container, +} from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; const Register = () => { - const [form, setForm] = useState({ - email: '', - password: '', - }); - const [loading, setLoading] = useState(false); - const { notify } = useGlobalInfoStore(); - const { email, password } = form; + const [form, setForm] = useState({ + email: "", + password: "", + }); + const [loading, setLoading] = useState(false); + const { notify } = useGlobalInfoStore(); + const { email, password } = form; - const { state, dispatch } = useContext(AuthContext); - const { user } = state; - const navigate = useNavigate(); + const { state, dispatch } = useContext(AuthContext); + const { user } = state; + const navigate = useNavigate(); - useEffect(() => { - if (user !== null) navigate('/'); - }, [user, navigate]); + useEffect(() => { + if (user !== null) navigate("/"); + }, [user, navigate]); - const handleChange = (e: any) => { - const { name, value } = e.target; - setForm({ ...form, [name]: value }); - }; + const handleChange = (e: any) => { + const { name, value } = e.target; + setForm({ ...form, [name]: value }); + }; - const submitForm = async (e: any) => { - e.preventDefault(); - setLoading(true); - try { - const { data } = await axios.post(`${apiUrl}/auth/register`, { - email, - password, - }); - dispatch({ - type: 'LOGIN', - payload: data, - }); - notify('success', 'Welcome to Maxun!'); - window.localStorage.setItem('user', JSON.stringify(data)); - navigate('/'); - } catch (err: any) { - notify('error', err.response.data || 'Registration Failed. Please try again.'); - } finally { - setLoading(false); - } - }; + const submitForm = async (e: any) => { + e.preventDefault(); + setLoading(true); + try { + const { data } = await axios.post(`${apiUrl}/auth/register`, { + email, + password, + }); + dispatch({ + type: "LOGIN", + payload: data, + }); + notify("success", "Welcome to Maxun!"); + window.localStorage.setItem("user", JSON.stringify(data)); + navigate("/"); + } catch (err: any) { + notify( + "error", + err.response.data || "Registration Failed. Please try again." + ); + } finally { + setLoading(false); + } + }; - return ( - + + + Create an account + + + + - - Already have an account?{' '} - - Login - - - - - - ); + {loading ? ( + <> + + Loading + + ) : ( + "Register" + )} + + + Already have an account?{" "} + + Login + + + + + ); }; export default Register; From db2f050c4ba3281bdea41eebfb0b9bc70d158d1a Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 21:56:03 +0530 Subject: [PATCH 057/165] design changed --- src/pages/Login.tsx | 282 ++++++++++++++++++--------------- src/pages/Register.tsx | 345 ++++++++++++++++++++++++----------------- 2 files changed, 359 insertions(+), 268 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index 9d075f987..d03718dc8 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,5 +1,5 @@ import axios from "axios"; -import { useState, useContext, useEffect } from "react"; +import { useState, useContext, useEffect, FormEvent } from "react"; import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; import { @@ -8,6 +8,7 @@ import { TextField, Button, CircularProgress, + Grid, } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; @@ -32,12 +33,12 @@ const Login = () => { } }, [user, navigate]); - const handleChange = (e: any) => { + const handleChange = (e:any) => { const { name, value } = e.target; setForm({ ...form, [name]: value }); }; - const submitForm = async (e: any) => { + const submitForm = async (e:any) => { e.preventDefault(); setLoading(true); try { @@ -49,148 +50,175 @@ const Login = () => { notify("success", "Welcome to Maxun!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); - } catch (err: any) { - notify("error", err.response.data || "Login Failed. Please try again."); + } catch (err) { + notify("error", "Login Failed. Please try again."); setLoading(false); } }; return ( - - + {/* Left Side: Login Form */} + - - Welcome Back! - - + + Welcome Back! + + - + + "& .MuiInputLabel-root": { + color: password ? "#ff33cc" : "#000000", + }, + "& .MuiInputLabel-root.Mui-focused": { + color: "#ff33cc", + }, + }} + /> - + - - Don’t have an account?{" "} - - Register - - - - + + Don’t have an account?{" "} + + Register + + + + + + {/* Right Side: Aesthetic Info Section */} + + + + Welcome to Maxun + + + Maxun is a powerful visual website scraper that makes extracting data from any website as easy as a few clicks. + No more complex code or time-consuming processes—Maxun does the heavy lifting for you. Start your data extraction journey with us! + + + + ); }; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 59006c767..3b90d4a54 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,14 +1,14 @@ -import { useState, useContext, useEffect } from "react"; -import { useNavigate, Link } from "react-router-dom"; import axios from "axios"; +import { useState, useContext, useEffect, FormEvent } from "react"; +import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; import { + Box, + Typography, TextField, Button, CircularProgress, - Typography, - Box, - Container, + Grid, } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; @@ -17,17 +17,21 @@ const Register = () => { const [form, setForm] = useState({ email: "", password: "", + confirmPassword: "", }); const [loading, setLoading] = useState(false); const { notify } = useGlobalInfoStore(); - const { email, password } = form; + const { email, password, confirmPassword } = form; const { state, dispatch } = useContext(AuthContext); const { user } = state; + const navigate = useNavigate(); useEffect(() => { - if (user !== null) navigate("/"); + if (user) { + navigate("/"); + } }, [user, navigate]); const handleChange = (e: any) => { @@ -37,165 +41,224 @@ const Register = () => { const submitForm = async (e: any) => { e.preventDefault(); + if (password !== confirmPassword) { + notify("error", "Passwords do not match."); + return; + } setLoading(true); try { const { data } = await axios.post(`${apiUrl}/auth/register`, { email, password, }); - dispatch({ - type: "LOGIN", - payload: data, - }); + dispatch({ type: "LOGIN", payload: data }); notify("success", "Welcome to Maxun!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); - } catch (err: any) { - notify( - "error", - err.response.data || "Registration Failed. Please try again." - ); - } finally { + } catch (err) { + notify("error", "Registration Failed. Please try again."); setLoading(false); } }; return ( - - + {/* Left Side: Register Form */} + - - Create an account - - + + Create an Account + + - + - - - Already have an account?{" "} - - Login - - - - + "& .MuiInputLabel-root": { + color: password ? "#ff33cc" : "#000000", + }, + "& .MuiInputLabel-root.Mui-focused": { + color: "#ff33cc", + }, + }} + /> + + + + + + Already have an account?{" "} + + Login + + + + + + {/* Right Side: Aesthetic Info Section */} + + + + Welcome to Maxun + + + Maxun is a powerful visual website scraper that makes extracting data from any website as easy as a few clicks. + No more complex code or time-consuming processes—Maxun does the heavy lifting for you. Start your data extraction journey with us! + + + + ); }; From dc098025c6f69d88317db293dd077906e111e2b1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 16 Nov 2024 22:49:48 +0530 Subject: [PATCH 058/165] feat: options column --- src/components/molecules/RecordingsTable.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index c58951939..1531c0a78 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -26,7 +26,7 @@ import { GenericModal } from '../atoms/GenericModal'; */ interface Column { - id: 'interpret' | 'name' | 'delete' | 'schedule' | 'integrate' | 'settings'; + id: 'interpret' | 'name' | 'delete' | 'schedule' | 'integrate' | 'settings' | 'options'; label: string; minWidth?: number; align?: 'right'; @@ -73,6 +73,11 @@ const columns: readonly Column[] = [ label: 'Delete', minWidth: 80, }, + { + id: 'options', + label: 'Options', + minWidth: 80, + }, ]; interface Data { From e8762859ccb02318d8b8de5ea835b0ed32af7e8a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sat, 16 Nov 2024 23:06:34 +0530 Subject: [PATCH 059/165] feat: dropdown for edit, delete, duplicate robot --- src/components/molecules/RecordingsTable.tsx | 104 ++++++++++++++----- 1 file changed, 79 insertions(+), 25 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 1531c0a78..71a67f521 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -9,8 +9,8 @@ import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; import { useEffect } from "react"; import { WorkflowFile } from "maxun-core"; -import { IconButton, Button, Box, Typography, TextField } from "@mui/material"; -import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power } from "@mui/icons-material"; +import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText } from "@mui/material"; +import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, } from "@mui/icons-material"; import LinkIcon from '@mui/icons-material/Link'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage"; @@ -18,7 +18,7 @@ import { Add } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; import { GenericModal } from '../atoms/GenericModal'; - +import { Menu as MenuIcon } from '@mui/icons-material'; /** TODO: * 1. allow editing existing robot after persisting browser steps @@ -26,7 +26,7 @@ import { GenericModal } from '../atoms/GenericModal'; */ interface Column { - id: 'interpret' | 'name' | 'delete' | 'schedule' | 'integrate' | 'settings' | 'options'; + id: 'interpret' | 'name' | 'options' | 'schedule' | 'integrate' | 'settings'; label: string; minWidth?: number; align?: 'right'; @@ -68,11 +68,6 @@ const columns: readonly Column[] = [ label: 'Settings', minWidth: 80, }, - { - id: 'delete', - label: 'Delete', - minWidth: 80, - }, { id: 'options', label: 'Options', @@ -253,22 +248,27 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl handleIntegrateRecording(row.id, row.name, row.params || [])} /> ); - case 'delete': - return ( - - { - deleteRecordingFromStorage(row.id).then((result: boolean) => { - if (result) { - setRows([]); - notify('success', 'Recording deleted successfully'); - fetchRecordings(); - } - }) - }}> - - - - ); + case 'options': + return ( + + handleEditRecording(row.id, row.name)} + handleDelete={() => { + deleteRecordingFromStorage(row.id).then((result: boolean) => { + if (result) { + setRows([]); + notify('success', 'Recording deleted successfully'); + fetchRecordings(); + } + }) + }} + handleDuplicate={() => { + notify('info', 'Duplicating recording...'); + // Implement duplication logic here + }} + /> + + ); case 'settings': return ( @@ -382,6 +382,60 @@ const SettingsButton = ({ handleSettings }: SettingsButtonProps) => { ) } +interface OptionsButtonProps { + handleEdit: () => void; + handleDelete: () => void; + handleDuplicate: () => void; +} + +const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsButtonProps) => { + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + { handleEdit(); handleClose(); }}> + + + + Edit + + { handleDelete(); handleClose(); }}> + + + + Delete + + { handleDuplicate(); handleClose(); }}> + + + + Duplicate + + + + ); +}; + const modalStyle = { top: '50%', left: '50%', From 459daed60cfbe040bf5bda1936f9313125521dce Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 23:21:59 +0530 Subject: [PATCH 060/165] removed overkilling stuff --- src/pages/Login.tsx | 127 +++++------------ src/pages/Register.tsx | 305 ++++++++++++++--------------------------- 2 files changed, 132 insertions(+), 300 deletions(-) diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index d03718dc8..de44af12a 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -2,14 +2,7 @@ import axios from "axios"; import { useState, useContext, useEffect, FormEvent } from "react"; import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; -import { - Box, - Typography, - TextField, - Button, - CircularProgress, - Grid, -} from "@mui/material"; +import { Box, Typography, TextField, Button, CircularProgress, Grid } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; @@ -33,12 +26,12 @@ const Login = () => { } }, [user, navigate]); - const handleChange = (e:any) => { + const handleChange = (e: any) => { const { name, value } = e.target; setForm({ ...form, [name]: value }); }; - const submitForm = async (e:any) => { + const submitForm = async (e: any) => { e.preventDefault(); setLoading(true); try { @@ -57,36 +50,35 @@ const Login = () => { }; return ( - - {/* Left Side: Login Form */} - + + - + logo + Welcome Back! { sx={{ "& .MuiOutlinedInput-root": { backgroundColor: email ? "#ffe6f9" : "#ffffff", - "& fieldset": { - borderColor: "#ff33cc", - }, - "&:hover fieldset": { - borderColor: "#ff33cc", - }, - "&.Mui-focused fieldset": { - borderColor: "#ff33cc", - }, + "& fieldset": { borderColor: "#ff33cc" }, + "&:hover fieldset": { borderColor: "#ff33cc" }, + "&.Mui-focused fieldset": { borderColor: "#ff33cc" }, }, "& input:-webkit-autofill": { WebkitBoxShadow: "0 0 0 1000px #ffe6f9 inset", WebkitTextFillColor: "#000", }, - "& .MuiInputLabel-root": { - color: email ? "#ff33cc" : "#000000", - }, - "& .MuiInputLabel-root.Mui-focused": { - color: "#ff33cc", - }, + "& .MuiInputLabel-root": { color: email ? "#ff33cc" : "#000000" }, + "& .MuiInputLabel-root.Mui-focused": { color: "#ff33cc" }, }} /> { sx={{ "& .MuiOutlinedInput-root": { backgroundColor: password ? "#ffe6f9" : "#ffffff", - "& fieldset": { - borderColor: "#ff33cc", - }, - "&:hover fieldset": { - borderColor: "#ff33cc", - }, - "&.Mui-focused fieldset": { - borderColor: "#ff33cc", - }, + "& fieldset": { borderColor: "#ff33cc" }, + "&:hover fieldset": { borderColor: "#ff33cc" }, + "&.Mui-focused fieldset": { borderColor: "#ff33cc" }, }, "& input:-webkit-autofill": { WebkitBoxShadow: "0 0 0 1000px #ffe6f9 inset", WebkitTextFillColor: "#000", }, - "& .MuiInputLabel-root": { - color: password ? "#ff33cc" : "#000000", - }, - "& .MuiInputLabel-root.Mui-focused": { - color: "#ff33cc", - }, + "& .MuiInputLabel-root": { color: password ? "#ff33cc" : "#000000" }, + "& .MuiInputLabel-root.Mui-focused": { color: "#ff33cc" }, }} /> - - Don’t have an account?{" "} @@ -184,41 +154,8 @@ const Login = () => { - - - {/* Right Side: Aesthetic Info Section */} - - - - Welcome to Maxun - - - Maxun is a powerful visual website scraper that makes extracting data from any website as easy as a few clicks. - No more complex code or time-consuming processes—Maxun does the heavy lifting for you. Start your data extraction journey with us! - - - + ); }; diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 3b90d4a54..90f33ed0d 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -1,15 +1,8 @@ import axios from "axios"; -import { useState, useContext, useEffect, FormEvent } from "react"; +import { useState, useContext, useEffect } from "react"; import { useNavigate, Link } from "react-router-dom"; import { AuthContext } from "../context/auth"; -import { - Box, - Typography, - TextField, - Button, - CircularProgress, - Grid, -} from "@mui/material"; +import { Box, Typography, TextField, Button, CircularProgress } from "@mui/material"; import { useGlobalInfoStore } from "../context/globalInfo"; import { apiUrl } from "../apiConfig"; @@ -17,11 +10,10 @@ const Register = () => { const [form, setForm] = useState({ email: "", password: "", - confirmPassword: "", }); const [loading, setLoading] = useState(false); const { notify } = useGlobalInfoStore(); - const { email, password, confirmPassword } = form; + const { email, password } = form; const { state, dispatch } = useContext(AuthContext); const { user } = state; @@ -41,10 +33,6 @@ const Register = () => { const submitForm = async (e: any) => { e.preventDefault(); - if (password !== confirmPassword) { - notify("error", "Passwords do not match."); - return; - } setLoading(true); try { const { data } = await axios.post(`${apiUrl}/auth/register`, { @@ -52,7 +40,7 @@ const Register = () => { password, }); dispatch({ type: "LOGIN", payload: data }); - notify("success", "Welcome to Maxun!"); + notify("success", "Registration Successful!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); } catch (err) { @@ -62,203 +50,110 @@ const Register = () => { }; return ( - - {/* Left Side: Register Form */} - + - + + Create an Account + + + + - - - Already have an account?{" "} - - Login - - - - - - {/* Right Side: Aesthetic Info Section */} - - - - Welcome to Maxun - - - Maxun is a powerful visual website scraper that makes extracting data from any website as easy as a few clicks. - No more complex code or time-consuming processes—Maxun does the heavy lifting for you. Start your data extraction journey with us! - - - - + {loading ? ( + <> + + Loading + + ) : ( + "Register" + )} + + + Already have an account?{" "} + + Login + + + + ); }; From cf5c8b11e92aa9070f6fbf2c884288d059b2c795 Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Sat, 16 Nov 2024 23:54:06 +0530 Subject: [PATCH 061/165] on registration failed msg --- src/pages/Register.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 90f33ed0d..d8378c73e 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -43,8 +43,8 @@ const Register = () => { notify("success", "Registration Successful!"); window.localStorage.setItem("user", JSON.stringify(data)); navigate("/"); - } catch (err) { - notify("error", "Registration Failed. Please try again."); + } catch (error:any) { + notify("error", error.response.data || "Registration Failed. Please try again."); setLoading(false); } }; From ee2c4b385ebf1e7d078a3e85da92999f8d681f84 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 00:30:38 +0530 Subject: [PATCH 062/165] feat: add handle edit robot modal --- src/components/molecules/RecordingsTable.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 71a67f521..84df8d0e5 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -90,9 +90,10 @@ interface RecordingsTableProps { handleScheduleRecording: (id: string, fileName: string, params: string[]) => void; handleIntegrateRecording: (id: string, fileName: string, params: string[]) => void; handleSettingsRecording: (id: string, fileName: string, params: string[]) => void; + handleEditRobot: (id: string, name: string, params: string[]) => void; } -export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording }: RecordingsTableProps) => { +export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot }: RecordingsTableProps) => { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const [rows, setRows] = React.useState([]); @@ -252,7 +253,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl return ( handleEditRecording(row.id, row.name)} + handleEdit={() => handleEditRobot(row.id, row.name, row.params || [])} handleDelete={() => { deleteRecordingFromStorage(row.id).then((result: boolean) => { if (result) { From 38bdf17317b93d4bfd682f9466dfda07a2ae3e75 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 00:31:36 +0530 Subject: [PATCH 063/165] feat: added robot edit modal --- src/components/molecules/RobotEdit.tsx | 135 +++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 src/components/molecules/RobotEdit.tsx diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx new file mode 100644 index 000000000..d5fdceb42 --- /dev/null +++ b/src/components/molecules/RobotEdit.tsx @@ -0,0 +1,135 @@ +import React, { useState, useEffect } from 'react'; +import { GenericModal } from "../atoms/GenericModal"; +import { TextField, Typography, Box, Button } from "@mui/material"; +import { modalStyle } from "./AddWhereCondModal"; +import { useGlobalInfoStore } from '../../context/globalInfo'; +import { getStoredRecording } from '../../api/storage'; +import { WhereWhatPair } from 'maxun-core'; +import { getUserById } from "../../api/auth"; + +interface RobotMeta { + name: string; + id: string; + createdAt: string; + pairs: number; + updatedAt: string; + params: any[]; +} + +interface RobotWorkflow { + workflow: WhereWhatPair[]; +} + +interface ScheduleConfig { + runEvery: number; + runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; + startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; + atTimeStart?: string; + atTimeEnd?: string; + timezone: string; + lastRunAt?: Date; + nextRunAt?: Date; + cronExpression?: string; +} + +export interface RobotSettings { + id: string; + userId?: number; + recording_meta: RobotMeta; + recording: RobotWorkflow; + google_sheet_email?: string | null; + google_sheet_name?: string | null; + google_sheet_id?: string | null; + google_access_token?: string | null; + google_refresh_token?: string | null; + schedule?: ScheduleConfig | null; +} + +interface RobotSettingsProps { + isOpen: boolean; + handleStart: (settings: RobotSettings) => void; + handleClose: () => void; + initialSettings?: RobotSettings | null; + +} + +export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + const [robot, setRobot] = useState(null); + const [userEmail, setUserEmail] = useState(null); + const { recordingId, notify } = useGlobalInfoStore(); + + useEffect(() => { + if (isOpen) { + getRobot(); + } + }, [isOpen]); + + const getRobot = async () => { + if (recordingId) { + const robot = await getStoredRecording(recordingId); + setRobot(robot); + } else { + notify('error', 'Could not find robot details. Please try again.'); + } + } + + const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; + + // Find the `goto` action in `what` and retrieve its arguments + const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; + + useEffect(() => { + const fetchUserEmail = async () => { + if (robot && robot.userId) { + const userData = await getUserById(robot.userId.toString()); + if (userData && userData.user) { + setUserEmail(userData.user.email); + } + } + }; + fetchUserEmail(); + }, [robot?.userId]); + + return ( + + <> + Edit Robot + + { + robot && ( + <> + + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit && ( + + )} + + + + + + + ) + } + + + + ); +}; From 464aeefddba46e4b98a536c23ed58ff2c525c822 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 00:33:23 +0530 Subject: [PATCH 064/165] feat: added robot edit modal --- src/components/organisms/Recordings.tsx | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/components/organisms/Recordings.tsx b/src/components/organisms/Recordings.tsx index 053a34799..ecb2b7698 100644 --- a/src/components/organisms/Recordings.tsx +++ b/src/components/organisms/Recordings.tsx @@ -5,6 +5,7 @@ import { RunSettings, RunSettingsModal } from "../molecules/RunSettings"; import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSettings"; import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings"; import { RobotSettings, RobotSettingsModal } from "../molecules/RobotSettings"; +import { RobotEditModal } from '../molecules/RobotEdit'; interface RecordingsProps { handleEditRecording: (id: string, fileName: string) => void; @@ -18,10 +19,12 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi const [scheduleSettingsAreOpen, setScheduleSettingsAreOpen] = useState(false); const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false); const [robotSettingsAreOpen, setRobotSettingsAreOpen] = useState(false); + const [robotEditAreOpen, setRobotEditAreOpen] = useState(false); const [params, setParams] = useState([]); const [selectedRecordingId, setSelectedRecordingId] = useState(''); const handleIntegrateRecording = (id: string, settings: IntegrationSettings) => {}; const handleSettingsRecording = (id: string, settings: RobotSettings) => {}; + const handleEditRobot = (id: string, settings: RobotSettings) => {}; const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => { if (params.length === 0) { @@ -75,6 +78,19 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi } } + const handleEditRobotOption = (id: string, name: string, params: string[]) => { + if (params.length === 0) { + setRobotEditAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } else { + setParams(params); + setRobotEditAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } + } + const handleClose = () => { setParams([]); setRunSettingsAreOpen(false); @@ -103,6 +119,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi setSelectedRecordingId(''); } + const handleRobotEditClose = () => { + setParams([]); + setRobotEditAreOpen(false); + setRecordingInfo('', ''); + setSelectedRecordingId(''); + } + return ( handleSettingsRecording(selectedRecordingId, settings)} /> + handleEditRobot(selectedRecordingId,settings)} + /> From fa523e528fecd83eeba59fda0f0cb40ea14f2920 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 02:27:11 +0530 Subject: [PATCH 065/165] feat: save robot edit changes --- src/components/molecules/RobotEdit.tsx | 112 ++++++++++++++++++++----- 1 file changed, 91 insertions(+), 21 deletions(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index d5fdceb42..f35e1048c 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -3,7 +3,7 @@ import { GenericModal } from "../atoms/GenericModal"; import { TextField, Typography, Box, Button } from "@mui/material"; import { modalStyle } from "./AddWhereCondModal"; import { useGlobalInfoStore } from '../../context/globalInfo'; -import { getStoredRecording } from '../../api/storage'; +import { getStoredRecording, updateRecording } from '../../api/storage'; import { WhereWhatPair } from 'maxun-core'; import { getUserById } from "../../api/auth"; @@ -20,6 +20,11 @@ interface RobotWorkflow { workflow: WhereWhatPair[]; } +interface RobotEditOptions { + name: string; + limit?: number; +} + interface ScheduleConfig { runEvery: number; runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; @@ -55,9 +60,17 @@ interface RobotSettingsProps { export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { const [robot, setRobot] = useState(null); - const [userEmail, setUserEmail] = useState(null); + // const [settings, setSettings] = useState({ + // name: '', + // }); + + // const [userEmail, setUserEmail] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); + // const handleChange = (field: keyof RobotEditOptions, value: string | number | boolean) => { + // setSettings(prev => ({ ...prev, [field]: value })); + // }; + useEffect(() => { if (isOpen) { getRobot(); @@ -73,22 +86,74 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin } } - const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; + const handleRobotNameChange = (newName: string) => { + setRobot((prev) => + prev ? { ...prev, recording_meta: { ...prev.recording_meta, name: newName } } : prev + ); + }; + + const handleLimitChange = (newLimit: number) => { + setRobot((prev) => { + if (!prev) return prev; + + const updatedWorkflow = [...prev.recording.workflow]; + + if ( + updatedWorkflow.length > 0 && + updatedWorkflow[0]?.what && + updatedWorkflow[0].what.length > 0 && + updatedWorkflow[0].what[0].args && + updatedWorkflow[0].what[0].args.length > 0 && + updatedWorkflow[0].what[0].args[0] + ) { + updatedWorkflow[0].what[0].args[0].limit = newLimit; + } + + return { ...prev, recording: { ...prev.recording, workflow: updatedWorkflow } }; + }); + }; + const handleSave = async () => { + if (!robot) return; + + try { + const payload = { + name: robot.recording_meta.name, + limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit, + }; + + const success = await updateRecording(robot.recording_meta.id, payload); + + if (success) { + notify('success', 'Robot updated successfully.'); + handleStart(robot); // Inform parent about the updated robot + handleClose(); // Close the modal + + window.location.reload(); + } else { + notify('error', 'Failed to update the robot. Please try again.'); + } + } catch (error) { + notify('error', 'An error occurred while updating the robot.'); + console.error('Error updating robot:', error); + } + }; + + // const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; // Find the `goto` action in `what` and retrieve its arguments - const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; + // const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; - useEffect(() => { - const fetchUserEmail = async () => { - if (robot && robot.userId) { - const userData = await getUserById(robot.userId.toString()); - if (userData && userData.user) { - setUserEmail(userData.user.email); - } - } - }; - fetchUserEmail(); - }, [robot?.userId]); + // useEffect(() => { + // const fetchUserEmail = async () => { + // if (robot && robot.userId) { + // const userData = await getUserById(robot.userId.toString()); + // if (userData && userData.user) { + // setUserEmail(userData.user.email); + // } + // } + // }; + // fetchUserEmail(); + // }, [robot?.userId]); return ( handleRobotNameChange(e.target.value)} style={{ marginBottom: '20px' }} /> - {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit && ( + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( + handleLimitChange(parseInt(e.target.value, 10) || 0) + } + style={{ marginBottom: '20px' }} /> )} - + From baf8ce9ecb43cdeba550494d4f9d648d6df3ec20 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 02:29:09 +0530 Subject: [PATCH 066/165] feat: add update recording api --- src/api/storage.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 9b4b06b2d..0ad51e770 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -19,6 +19,20 @@ export const getStoredRecordings = async (): Promise => { } }; +export const updateRecording = async (id: string, data: { name?: string; limit?: number }): Promise => { + try { + const response = await axios.put(`${apiUrl}/storage/recordings/${id}`, data); + if (response.status === 200) { + return true; + } else { + throw new Error(`Couldn't update recording with id ${id}`); + } + } catch (error: any) { + console.error(`Error updating recording: ${error.message}`); + return false; + } +}; + export const getStoredRuns = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/runs`); From e92bef2925551584db10949da4c4f5ee9aa70e4f Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 02:30:20 +0530 Subject: [PATCH 067/165] feat: add update route for robot --- server/src/routes/storage.ts | 89 ++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 228f60dee..7a356b27e 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -57,6 +57,95 @@ router.get('/recordings/:id', requireSignIn, async (req, res) => { } }) + +/** + * 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; + const { name, limit } = req.body; + + // 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 }); + } + + // Update the limit + if (limit !== undefined) { + const workflow = [...robot.recording.workflow]; // Create a copy of the workflow + + // 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 + const updatedWorkflow = workflow.map((step, index) => { + 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; + }); + + // Replace the workflow in the recording object + robot.set('recording', { ...robot.recording, workflow: updatedWorkflow }); + } else { + return res.status(400).json({ error: 'Invalid workflow structure for updating limit.' }); + } + } + + await robot.save(); + + const updatedRobot = await Robot.findOne({ where: { 'recording_meta.id': id } }); + console.log('After save:', updatedRobot); + + // Log the update + 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.' }); + } + } +}); + + + /** * DELETE endpoint for deleting a recording from the storage. */ From f360761e5bd607729d3f5fc510bd25e2d5077903 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 13:59:21 +0530 Subject: [PATCH 068/165] feat: add robot duplication modal --- src/components/molecules/RobotDuplicate.tsx | 152 ++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/components/molecules/RobotDuplicate.tsx diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx new file mode 100644 index 000000000..67b97e974 --- /dev/null +++ b/src/components/molecules/RobotDuplicate.tsx @@ -0,0 +1,152 @@ +import React, { useState, useEffect } from 'react'; +import { GenericModal } from "../atoms/GenericModal"; +import { TextField, Typography, Box, Button, Chip } from "@mui/material"; +import { modalStyle } from "./AddWhereCondModal"; +import { useGlobalInfoStore } from '../../context/globalInfo'; +import { duplicateRecording, getStoredRecording } from '../../api/storage'; +import { WhereWhatPair } from 'maxun-core'; +import { getUserById } from "../../api/auth"; + +interface RobotMeta { + name: string; + id: string; + createdAt: string; + pairs: number; + updatedAt: string; + params: any[]; +} + +interface RobotWorkflow { + workflow: WhereWhatPair[]; +} + +interface ScheduleConfig { + runEvery: number; + runEveryUnit: 'MINUTES' | 'HOURS' | 'DAYS' | 'WEEKS' | 'MONTHS'; + startFrom: 'SUNDAY' | 'MONDAY' | 'TUESDAY' | 'WEDNESDAY' | 'THURSDAY' | 'FRIDAY' | 'SATURDAY'; + atTimeStart?: string; + atTimeEnd?: string; + timezone: string; + lastRunAt?: Date; + nextRunAt?: Date; + cronExpression?: string; +} + +export interface RobotSettings { + id: string; + userId?: number; + recording_meta: RobotMeta; + recording: RobotWorkflow; + google_sheet_email?: string | null; + google_sheet_name?: string | null; + google_sheet_id?: string | null; + google_access_token?: string | null; + google_refresh_token?: string | null; + schedule?: ScheduleConfig | null; +} + +interface RobotSettingsProps { + isOpen: boolean; + handleStart: (settings: RobotSettings) => void; + handleClose: () => void; + initialSettings?: RobotSettings | null; + +} + +export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { + const [robot, setRobot] = useState(null); + const [targetUrl, setTargetUrl] = useState(''); + const { recordingId, notify } = useGlobalInfoStore(); + + useEffect(() => { + if (isOpen) { + getRobot(); + } + }, [isOpen]); + + useEffect(() => { + // Update the targetUrl when the robot data is loaded + if (robot) { + const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; + const url = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; + setTargetUrl(url); + } + }, [robot]); + + const getRobot = async () => { + if (recordingId) { + const robot = await getStoredRecording(recordingId); + setRobot(robot); + } else { + notify('error', 'Could not find robot details. Please try again.'); + } + } + + // const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; + + // // Find the `goto` action in `what` and retrieve its arguments + // const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; + + const handleTargetUrlChange = (e: React.ChangeEvent) => { + setTargetUrl(e.target.value); + }; + + const handleSave = async () => { + if (!robot || !targetUrl) { + notify('error', 'Target URL is required.'); + return; + } + + try { + const success = await duplicateRecording(robot.recording_meta.id, targetUrl); + + if (success) { + notify('success', 'Target URL updated successfully.'); + handleStart(robot); // Inform parent about the updated robot + handleClose(); // Close the modal + + window.location.reload(); + } else { + notify('error', 'Failed to update the Target URL. Please try again.'); + } + } catch (error) { + notify('error', 'An error occurred while updating the Target URL.'); + console.error('Error updating Target URL:', error); + } + }; + + return ( + + <> + Robot Settings + + { + robot && ( + <> + + + + + + + ) + } + + + + ); +}; From 1c225f5988e9433514d3f2d1b467e9d9cd31d50a Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:02:23 +0530 Subject: [PATCH 069/165] feat: add robot duplication modal in recordings table --- src/components/molecules/RecordingsTable.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 84df8d0e5..f309dcc82 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -91,9 +91,10 @@ interface RecordingsTableProps { handleIntegrateRecording: (id: string, fileName: string, params: string[]) => void; handleSettingsRecording: (id: string, fileName: string, params: string[]) => void; handleEditRobot: (id: string, name: string, params: string[]) => void; + handleDuplicateRobot: (id: string, name: string, params: string[]) => void; } -export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot }: RecordingsTableProps) => { +export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handleScheduleRecording, handleIntegrateRecording, handleSettingsRecording, handleEditRobot, handleDuplicateRobot }: RecordingsTableProps) => { const [page, setPage] = React.useState(0); const [rowsPerPage, setRowsPerPage] = React.useState(10); const [rows, setRows] = React.useState([]); @@ -264,8 +265,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl }) }} handleDuplicate={() => { - notify('info', 'Duplicating recording...'); - // Implement duplication logic here + handleDuplicateRobot(row.id, row.name, row.params || []); }} /> From 3b210b75ba1d4b24200de99ee1e1f8b00e860896 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:03:47 +0530 Subject: [PATCH 070/165] feat: add route for robot duplication --- server/src/routes/storage.ts | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 7a356b27e..912726632 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -145,6 +145,80 @@ router.put('/recordings/:id', requireSignIn, async (req: AuthenticatedRequest, r }); +/** + * 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 }, + 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.' }); + } + } +}); + + + /** * DELETE endpoint for deleting a recording from the storage. From 4ba986aaf7c2aee00c6f03c69174121c5e606d56 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:05:52 +0530 Subject: [PATCH 071/165] feat: add duplicate recording api --- src/api/storage.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/api/storage.ts b/src/api/storage.ts index 0ad51e770..90ab91aff 100644 --- a/src/api/storage.ts +++ b/src/api/storage.ts @@ -33,6 +33,22 @@ export const updateRecording = async (id: string, data: { name?: string; limit?: } }; +export const duplicateRecording = async (id: string, targetUrl: string): Promise => { + try { + const response = await axios.post(`${apiUrl}/storage/recordings/${id}/duplicate`, { + targetUrl, + }); + if (response.status === 201) { + return response.data; // Returns the duplicated robot details + } else { + throw new Error(`Couldn't duplicate recording with id ${id}`); + } + } catch (error: any) { + console.error(`Error duplicating recording: ${error.message}`); + return null; + } +}; + export const getStoredRuns = async (): Promise => { try { const response = await axios.get(`${apiUrl}/storage/runs`); From 7a0a23ad282180da9ba5d430cafc41b53d17776c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Sun, 17 Nov 2024 14:06:26 +0530 Subject: [PATCH 072/165] feat: add robot duplication modal --- src/components/organisms/Recordings.tsx | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/components/organisms/Recordings.tsx b/src/components/organisms/Recordings.tsx index ecb2b7698..495a25f88 100644 --- a/src/components/organisms/Recordings.tsx +++ b/src/components/organisms/Recordings.tsx @@ -6,6 +6,7 @@ import { ScheduleSettings, ScheduleSettingsModal } from "../molecules/ScheduleSe import { IntegrationSettings, IntegrationSettingsModal } from "../molecules/IntegrationSettings"; import { RobotSettings, RobotSettingsModal } from "../molecules/RobotSettings"; import { RobotEditModal } from '../molecules/RobotEdit'; +import { RobotDuplicationModal } from '../molecules/RobotDuplicate'; interface RecordingsProps { handleEditRecording: (id: string, fileName: string) => void; @@ -20,11 +21,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi const [integrateSettingsAreOpen, setIntegrateSettingsAreOpen] = useState(false); const [robotSettingsAreOpen, setRobotSettingsAreOpen] = useState(false); const [robotEditAreOpen, setRobotEditAreOpen] = useState(false); + const [robotDuplicateAreOpen, setRobotDuplicateAreOpen] = useState(false); const [params, setParams] = useState([]); const [selectedRecordingId, setSelectedRecordingId] = useState(''); const handleIntegrateRecording = (id: string, settings: IntegrationSettings) => {}; const handleSettingsRecording = (id: string, settings: RobotSettings) => {}; const handleEditRobot = (id: string, settings: RobotSettings) => {}; + const handleDuplicateRobot = (id: string, settings: RobotSettings) => {}; const handleSettingsAndIntegrate = (id: string, name: string, params: string[]) => { if (params.length === 0) { @@ -91,6 +94,19 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi } } + const handleDuplicateRobotOption = (id: string, name: string, params: string[]) => { + if (params.length === 0) { + setRobotDuplicateAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } else { + setParams(params); + setRobotDuplicateAreOpen(true); + setRecordingInfo(id, name); + setSelectedRecordingId(id); + } + } + const handleClose = () => { setParams([]); setRunSettingsAreOpen(false); @@ -126,6 +142,13 @@ export const Recordings = ({ handleEditRecording, handleRunRecording, setRecordi setSelectedRecordingId(''); } + const handleRobotDuplicateClose = () => { + setParams([]); + setRobotDuplicateAreOpen(false); + setRecordingInfo('', ''); + setSelectedRecordingId(''); + } + return ( handleEditRobot(selectedRecordingId,settings)} /> + handleDuplicateRobot(selectedRecordingId, settings)} + /> From 6622e4fa26369f2462a352d3a14420191760250e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 05:37:51 +0530 Subject: [PATCH 073/165] chore: console log cleanup --- src/components/molecules/RunsTable.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index b1cb97ef4..5091c6dac 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -63,8 +63,6 @@ export const RunsTable = ( const [rowsPerPage, setRowsPerPage] = useState(10); const [rows, setRows] = useState([]); - console.log(`rows runs: ${JSON.stringify(rows)}`); - const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleChangePage = (event: unknown, newPage: number) => { From 4fefe71d29ef2baa2a2aed65275c4dafdae164eb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 05:50:25 +0530 Subject: [PATCH 074/165] feat: include robotId and robotMetaId in run data interface --- src/components/molecules/RunsTable.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index 5091c6dac..3fde41a3b 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -45,6 +45,8 @@ export interface Data { // task: string; log: string; runId: string; + robotId: string; + robotMetaId: string; interpreterSettings: RunSettings; serializableOutput: any; binaryOutput: any; From f6e08aa7664d8606375b42d8bf13af487cd8d0c1 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 05:51:56 +0530 Subject: [PATCH 075/165] feat: group runs by robot meta id instead of robot name --- src/components/molecules/RunsTable.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index 3fde41a3b..f6d628df7 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -105,12 +105,12 @@ export const RunsTable = ( fetchRuns(); }; - // Group runs by recording name + // Group runs by robot meta id const groupedRows = rows.reduce((acc, row) => { - if (!acc[row.name]) { - acc[row.name] = []; + if (!acc[row.robotMetaId]) { + acc[row.robotMetaId] = []; } - acc[row.name].push(row); + acc[row.robotMetaId].push(row); return acc; }, {} as Record); From cba24f2f4b1910f06c9c6e2bb14ff02c6796f1fa Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 05:58:24 +0530 Subject: [PATCH 076/165] feat: render runs by robot id and display run name --- src/components/molecules/RunsTable.tsx | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index f6d628df7..ddbe66865 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -120,10 +120,10 @@ export const RunsTable = ( All Runs - {Object.entries(groupedRows).map(([name, group]) => ( - + {Object.entries(groupedRows).map(([id, data]) => ( + }> - {name} + {data[0].name} @@ -142,17 +142,19 @@ export const RunsTable = ( - {group.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => ( - - ))} + {data + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row) => ( + + ))}
From 67dfd84694f725e81d741a5e53e4ffd5503c54b2 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 06:09:01 +0530 Subject: [PATCH 077/165] feat: display most recent run name --- src/components/molecules/RunsTable.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index ddbe66865..dcb0cdd15 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -123,7 +123,7 @@ export const RunsTable = ( {Object.entries(groupedRows).map(([id, data]) => ( }> - {data[0].name} + {data[data.length - 1].name} From 874ee8d9e77ddfee533fbfea46453694ec2ec3c8 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 06:11:34 +0530 Subject: [PATCH 078/165] chore: remove unused import --- src/components/molecules/RobotEdit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index f35e1048c..ee8b8c8c1 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -5,7 +5,6 @@ import { modalStyle } from "./AddWhereCondModal"; import { useGlobalInfoStore } from '../../context/globalInfo'; import { getStoredRecording, updateRecording } from '../../api/storage'; import { WhereWhatPair } from 'maxun-core'; -import { getUserById } from "../../api/auth"; interface RobotMeta { name: string; From 1af3e68ffd5d3010189bc9a17b9f87738abafb55 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Mon, 18 Nov 2024 06:12:17 +0530 Subject: [PATCH 079/165] chore: remove unused code --- src/components/molecules/RobotEdit.tsx | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index ee8b8c8c1..467679e1c 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -59,17 +59,8 @@ interface RobotSettingsProps { export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { const [robot, setRobot] = useState(null); - // const [settings, setSettings] = useState({ - // name: '', - // }); - - // const [userEmail, setUserEmail] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); - // const handleChange = (field: keyof RobotEditOptions, value: string | number | boolean) => { - // setSettings(prev => ({ ...prev, [field]: value })); - // }; - useEffect(() => { if (isOpen) { getRobot(); @@ -137,23 +128,6 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin } }; - // const lastPair = robot?.recording.workflow[robot?.recording.workflow.length - 1]; - - // Find the `goto` action in `what` and retrieve its arguments - // const targetUrl = lastPair?.what.find(action => action.action === "goto")?.args?.[0]; - - // useEffect(() => { - // const fetchUserEmail = async () => { - // if (robot && robot.userId) { - // const userData = await getUserById(robot.userId.toString()); - // if (userData && userData.user) { - // setUserEmail(userData.user.email); - // } - // } - // }; - // fetchUserEmail(); - // }, [robot?.userId]); - return ( Date: Mon, 18 Nov 2024 06:12:35 +0530 Subject: [PATCH 080/165] chore: lint --- src/components/molecules/RobotEdit.tsx | 50 +++++++++++++------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 467679e1c..f26765a3c 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -20,8 +20,8 @@ interface RobotWorkflow { } interface RobotEditOptions { - name: string; - limit?: number; + name: string; + limit?: number; } interface ScheduleConfig { @@ -85,9 +85,9 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin const handleLimitChange = (newLimit: number) => { setRobot((prev) => { if (!prev) return prev; - + const updatedWorkflow = [...prev.recording.workflow]; - + if ( updatedWorkflow.length > 0 && updatedWorkflow[0]?.what && @@ -98,33 +98,33 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin ) { updatedWorkflow[0].what[0].args[0].limit = newLimit; } - + return { ...prev, recording: { ...prev.recording, workflow: updatedWorkflow } }; }); }; const handleSave = async () => { if (!robot) return; - + try { - const payload = { - name: robot.recording_meta.name, - limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit, - }; - - const success = await updateRecording(robot.recording_meta.id, payload); - - if (success) { - notify('success', 'Robot updated successfully.'); - handleStart(robot); // Inform parent about the updated robot - handleClose(); // Close the modal - - window.location.reload(); - } else { - notify('error', 'Failed to update the robot. Please try again.'); - } + const payload = { + name: robot.recording_meta.name, + limit: robot.recording.workflow[0]?.what[0]?.args?.[0]?.limit, + }; + + const success = await updateRecording(robot.recording_meta.id, payload); + + if (success) { + notify('success', 'Robot updated successfully.'); + handleStart(robot); // Inform parent about the updated robot + handleClose(); // Close the modal + + window.location.reload(); + } else { + notify('error', 'Failed to update the robot. Please try again.'); + } } catch (error) { - notify('error', 'An error occurred while updating the robot.'); - console.error('Error updating robot:', error); + notify('error', 'An error occurred while updating the robot.'); + console.error('Error updating robot:', error); } }; @@ -159,7 +159,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin style={{ marginBottom: '20px' }} /> )} - +
@@ -204,7 +208,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl - {rows.length !== 0 ? rows + {filteredRows.length !== 0 ? filteredRows .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((row) => { return ( @@ -226,16 +230,6 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl handleRunRecording(row.id, row.name, row.params || [])} /> ); - // case 'edit': - // return ( - // - // { - // handleEditRecording(row.id, row.name); - // }} sx={{ '&:hover': { color: '#1976d2', backgroundColor: 'transparent' } }}> - // - // - // - // ); case 'schedule': return ( @@ -285,7 +279,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl { ) } - interface ScheduleButtonProps { handleSchedule: () => void; } diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index b1cb97ef4..a7c67f2ae 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -12,8 +12,9 @@ import { useGlobalInfoStore } from "../../context/globalInfo"; import { getStoredRuns } from "../../api/storage"; import { RunSettings } from "./RunSettings"; import { CollapsibleRow } from "./ColapsibleRow"; -import { Accordion, AccordionSummary, AccordionDetails, Typography } from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Typography, Box, TextField } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import SearchIcon from '@mui/icons-material/Search'; interface Column { id: 'runStatus' | 'name' | 'startedAt' | 'finishedAt' | 'delete' | 'settings'; @@ -28,7 +29,6 @@ export const columns: readonly Column[] = [ { id: 'name', label: 'Robot Name', minWidth: 80 }, { id: 'startedAt', label: 'Started at', minWidth: 80 }, { id: 'finishedAt', label: 'Finished at', minWidth: 80 }, - // { id: 'task', label: 'Task', minWidth: 80 }, { id: 'settings', label: 'Settings', minWidth: 80 }, { id: 'delete', label: 'Delete', minWidth: 80 }, ]; @@ -42,7 +42,6 @@ export interface Data { runByUserId?: string; runByScheduleId?: string; runByAPI?: boolean; - // task: string; log: string; runId: string; interpreterSettings: RunSettings; @@ -62,6 +61,7 @@ export const RunsTable = ( const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const [rows, setRows] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); console.log(`rows runs: ${JSON.stringify(rows)}`); @@ -76,6 +76,11 @@ export const RunsTable = ( setPage(0); }; + const handleSearchChange = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + setPage(0); + }; + const fetchRuns = async () => { const runs = await getStoredRuns(); if (runs) { @@ -105,8 +110,13 @@ export const RunsTable = ( fetchRuns(); }; - // Group runs by recording name - const groupedRows = rows.reduce((acc, row) => { + // Filter rows based on search term + const filteredRows = rows.filter((row) => + row.name.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + // Group filtered runs by recording name + const groupedRows = filteredRows.reduce((acc, row) => { if (!acc[row.name]) { acc[row.name] = []; } @@ -116,9 +126,21 @@ export const RunsTable = ( return ( - - All Runs - + + + All Runs + + + }} + sx={{ width: '250px' }} + /> + {Object.entries(groupedRows).map(([name, group]) => ( @@ -162,7 +184,7 @@ export const RunsTable = ( ); -}; +}; \ No newline at end of file From f58a6409bfbbc7e5508a11bb1947fbe7590bb5cc Mon Sep 17 00:00:00 2001 From: AmitChauhan63390 Date: Wed, 20 Nov 2024 14:28:53 +0530 Subject: [PATCH 137/165] some fixes --- src/components/molecules/RunsTable.tsx | 44 ++++++++++++++------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/components/molecules/RunsTable.tsx b/src/components/molecules/RunsTable.tsx index a7c67f2ae..6c24803e0 100644 --- a/src/components/molecules/RunsTable.tsx +++ b/src/components/molecules/RunsTable.tsx @@ -44,6 +44,8 @@ export interface Data { runByAPI?: boolean; log: string; runId: string; + robotId: string; + robotMetaId: string; interpreterSettings: RunSettings; serializableOutput: any; binaryOutput: any; @@ -63,8 +65,6 @@ export const RunsTable = ( const [rows, setRows] = useState([]); const [searchTerm, setSearchTerm] = useState(''); - console.log(`rows runs: ${JSON.stringify(rows)}`); - const { notify, rerenderRuns, setRerenderRuns } = useGlobalInfoStore(); const handleChangePage = (event: unknown, newPage: number) => { @@ -115,12 +115,12 @@ export const RunsTable = ( row.name.toLowerCase().includes(searchTerm.toLowerCase()) ); - // Group filtered runs by recording name + // Group filtered rows by robot meta id const groupedRows = filteredRows.reduce((acc, row) => { - if (!acc[row.name]) { - acc[row.name] = []; + if (!acc[row.robotMetaId]) { + acc[row.robotMetaId] = []; } - acc[row.name].push(row); + acc[row.robotMetaId].push(row); return acc; }, {} as Record); @@ -142,10 +142,10 @@ export const RunsTable = ( /> - {Object.entries(groupedRows).map(([name, group]) => ( - + {Object.entries(groupedRows).map(([id, data]) => ( + }> - {name} + {data[data.length - 1].name}
@@ -164,17 +164,19 @@ export const RunsTable = ( - {group.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((row) => ( - - ))} + {data + .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) + .map((row) => ( + + ))}
@@ -192,4 +194,4 @@ export const RunsTable = ( />
); -}; \ No newline at end of file +}; From 3cbeb9d0821f891e8a7b608a3d6665289e34df25 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Wed, 20 Nov 2024 17:06:17 +0530 Subject: [PATCH 138/165] feat: add robot limit in robot settings modal --- src/components/molecules/RobotSettings.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/molecules/RobotSettings.tsx b/src/components/molecules/RobotSettings.tsx index ffb7a2d09..53d21d7bb 100644 --- a/src/components/molecules/RobotSettings.tsx +++ b/src/components/molecules/RobotSettings.tsx @@ -120,6 +120,17 @@ export const RobotSettingsModal = ({ isOpen, handleStart, handleClose, initialSe }} style={{ marginBottom: '20px' }} /> + {robot.recording.workflow?.[0]?.what?.[0]?.args?.[0]?.limit !== undefined && ( + + )} Date: Fri, 22 Nov 2024 20:41:59 +0530 Subject: [PATCH 139/165] feat: proxy form instructions (wip) --- src/components/organisms/ProxyForm.tsx | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 29b9534c8..5155fe8f4 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -243,6 +243,26 @@ const ProxyForm: React.FC = () => { )} + + + Proxy Instructions + + + 1. Enter the Proxy Server URL in the format: http://myproxy.com:3128 or socks5://myproxy.com:3128. + + + 2. If your proxy requires authentication, toggle the "Requires Authentication?" switch and provide the username and password. + + + 3. Click "Add Proxy" to save the configuration. + + + 4. Use the "Test Proxy" button to verify if the proxy configuration is working. + + + 5. If needed, you can remove the proxy configuration using the "Remove Proxy" button. + + ); }; From 8b42bc8f5e9467d5e300e8010f0ec82e138c33b4 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 20:42:18 +0530 Subject: [PATCH 140/165] chore: lint --- src/components/organisms/ProxyForm.tsx | 72 +++++++++++++------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 5155fe8f4..4f448fdae 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -145,22 +145,22 @@ const ProxyForm: React.FC = () => { {tabIndex === 0 && ( isProxyConfigured ? ( - - - - - Proxy URL - Requires Authentication - - - - - {proxy.proxy_url} - {proxy.auth ? 'Yes' : 'No'} - - -
-
+ + + + + Proxy URL + Requires Authentication + + + + + {proxy.proxy_url} + {proxy.auth ? 'Yes' : 'No'} + + +
+
@@ -237,32 +237,32 @@ const ProxyForm: React.FC = () => { Coming Soon - In Open Source (Basic Rotation) & Cloud (Advanced Rotation). If you don't want to manage the infrastructure, join our cloud waitlist to get early access.
)} - - Proxy Instructions - - - 1. Enter the Proxy Server URL in the format: http://myproxy.com:3128 or socks5://myproxy.com:3128. - - - 2. If your proxy requires authentication, toggle the "Requires Authentication?" switch and provide the username and password. - - - 3. Click "Add Proxy" to save the configuration. - - - 4. Use the "Test Proxy" button to verify if the proxy configuration is working. - - - 5. If needed, you can remove the proxy configuration using the "Remove Proxy" button. - - + + Proxy Instructions + + + 1. Enter the Proxy Server URL in the format: http://myproxy.com:3128 or socks5://myproxy.com:3128. + + + 2. If your proxy requires authentication, toggle the "Requires Authentication?" switch and provide the username and password. + + + 3. Click "Add Proxy" to save the configuration. + + + 4. Use the "Test Proxy" button to verify if the proxy configuration is working. + + + 5. If needed, you can remove the proxy configuration using the "Remove Proxy" button. + + ); }; From 9353f98cb3a248e5670b45221691a92b98bad63d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 20:46:56 +0530 Subject: [PATCH 141/165] feat: instructions ui --- src/components/organisms/ProxyForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 4f448fdae..356e9d15d 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -243,7 +243,7 @@ const ProxyForm: React.FC = () => { )} - + Proxy Instructions From 2813fa918adfc001bb69955731f8d1b56220ea18 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 20:54:27 +0530 Subject: [PATCH 142/165] feat: instructions ui alignment --- src/components/organisms/ProxyForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 356e9d15d..39e9a0f4b 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -231,7 +231,7 @@ const ProxyForm: React.FC = () => { ))} {tabIndex === 1 && ( - + <> Coming Soon - In Open Source (Basic Rotation) & Cloud (Advanced Rotation). If you don't want to manage the infrastructure, join our cloud waitlist to get early access. @@ -243,7 +243,7 @@ const ProxyForm: React.FC = () => { )} - + Proxy Instructions From 1341ac5b037fe1382d57568fb9fb878b1fa7db98 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:37:21 +0530 Subject: [PATCH 143/165] feat: use alert --- src/components/organisms/ProxyForm.tsx | 38 ++++++++++++-------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index 39e9a0f4b..cee698055 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { styled } from '@mui/system'; -import { TextField, Button, Switch, FormControlLabel, Box, Typography, Tabs, Tab, Table, TableContainer, TableHead, TableRow, TableBody, TableCell, Paper } from '@mui/material'; +import { Alert, AlertTitle, TextField, Button, Switch, FormControlLabel, Box, Typography, Tabs, Tab, Table, TableContainer, TableHead, TableRow, TableBody, TableCell, Paper } from '@mui/material'; import { sendProxyConfig, getProxyConfig, testProxyConfig, deleteProxyConfig } from '../../api/proxy'; import { useGlobalInfoStore } from '../../context/globalInfo'; @@ -243,26 +243,22 @@ const ProxyForm: React.FC = () => { )} - - - Proxy Instructions - - - 1. Enter the Proxy Server URL in the format: http://myproxy.com:3128 or socks5://myproxy.com:3128. - - - 2. If your proxy requires authentication, toggle the "Requires Authentication?" switch and provide the username and password. - - - 3. Click "Add Proxy" to save the configuration. - - - 4. Use the "Test Proxy" button to verify if the proxy configuration is working. - - - 5. If needed, you can remove the proxy configuration using the "Remove Proxy" button. - - + + If your proxy requires a username and password, always provide them separately from the proxy URL. +
+ The right way +
+ Proxy URL: http://proxy.com:1337 +
+ Username: myusername +
+ Password: mypassword +
+
+ The wrong way +
+ Proxy URL: http://myusername:mypassword@proxy.com:1337 +
); }; From a510f8abe6530c80670f536f7d47b9025456962e Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:44:45 +0530 Subject: [PATCH 144/165] feat: apply border --- src/components/organisms/ProxyForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/organisms/ProxyForm.tsx b/src/components/organisms/ProxyForm.tsx index cee698055..a581144ba 100644 --- a/src/components/organisms/ProxyForm.tsx +++ b/src/components/organisms/ProxyForm.tsx @@ -243,7 +243,7 @@ const ProxyForm: React.FC = () => {
)} - + If your proxy requires a username and password, always provide them separately from the proxy URL.
The right way From 9379671082ffe1a9078111896a8d4867fc873448 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:46:54 +0530 Subject: [PATCH 145/165] feat: apply border --- src/components/molecules/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 8eeb77273..24b0769ea 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -182,7 +182,7 @@ export const IntegrationSettingsModal = ({ {recording && recording.google_sheet_id ? ( <> - + Google Sheet Integrated Successfully. Every time this robot creates a successful run, its captured data is appended to your {recording.google_sheet_name} Google Sheet. From 6e60b735d36e1005268756b23d9183ebac471d73 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:47:36 +0530 Subject: [PATCH 146/165] feat: apply margin top --- src/components/molecules/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index 24b0769ea..b93858132 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -182,7 +182,7 @@ export const IntegrationSettingsModal = ({ {recording && recording.google_sheet_id ? ( <> - + Google Sheet Integrated Successfully. Every time this robot creates a successful run, its captured data is appended to your {recording.google_sheet_name} Google Sheet. From 8d50052984f025f047ebf62ca7c63ffbbbb3bd74 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:48:03 +0530 Subject: [PATCH 147/165] feat: remove beta tag --- src/components/molecules/IntegrationSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/IntegrationSettings.tsx b/src/components/molecules/IntegrationSettings.tsx index b93858132..c31605dea 100644 --- a/src/components/molecules/IntegrationSettings.tsx +++ b/src/components/molecules/IntegrationSettings.tsx @@ -177,7 +177,7 @@ export const IntegrationSettingsModal = ({ > Integrate with Google Sheet{" "} - + {/* */} {recording && recording.google_sheet_id ? ( From aefd64e40d770a19a74664d12182f841067927e0 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:49:22 +0530 Subject: [PATCH 148/165] chore: remove console log --- src/components/molecules/RobotEdit.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 26317260a..f68beee24 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -58,7 +58,6 @@ interface RobotSettingsProps { } export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettings }: RobotSettingsProps) => { - console.log("robot edit"); const [robot, setRobot] = useState(null); const { recordingId, notify } = useGlobalInfoStore(); From 9733c0133453ed579650b7fb9b41d92b6d5cdd92 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:51:23 +0530 Subject: [PATCH 149/165] feat: add beta tag --- src/components/molecules/NavBar.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 30feef51a..139583d07 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -3,7 +3,7 @@ import axios from 'axios'; import styled from "styled-components"; import { stopRecording } from "../../api/recording"; import { useGlobalInfoStore } from "../../context/globalInfo"; -import { IconButton, Menu, MenuItem, Typography, Avatar } from "@mui/material"; +import { IconButton, Menu, MenuItem, Typography, Avatar, Chip, } from "@mui/material"; import { AccountCircle, Logout, Clear } from "@mui/icons-material"; import { useNavigate } from 'react-router-dom'; import { AuthContext } from '../../context/auth'; @@ -58,6 +58,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => }}>
Maxun
+
{ user ? ( From 907dea67de7849764fc453d39aba7b3b4a11553d Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:51:45 +0530 Subject: [PATCH 150/165] chore: lint --- src/components/molecules/NavBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/NavBar.tsx b/src/components/molecules/NavBar.tsx index 139583d07..4c0b7296a 100644 --- a/src/components/molecules/NavBar.tsx +++ b/src/components/molecules/NavBar.tsx @@ -58,7 +58,7 @@ export const NavBar: React.FC = ({ recordingName, isRecording }) => }}>
Maxun
- +
{ user ? ( From 96d5feb4dfd321a4b383a45bd7ad5248f2f50e33 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:55:52 +0530 Subject: [PATCH 151/165] feat: use MoreHoriz icon for options --- src/components/molecules/RecordingsTable.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 09ee22394..2a9febe23 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -13,7 +13,7 @@ import { WorkflowFile } from "maxun-core"; import SearchIcon from '@mui/icons-material/Search'; import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText } from "@mui/material"; -import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, } from "@mui/icons-material"; +import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, MoreHoriz } from "@mui/icons-material"; import LinkIcon from '@mui/icons-material/Link'; import { useGlobalInfoStore } from "../../context/globalInfo"; @@ -417,7 +417,7 @@ const OptionsButton = ({ handleEdit, handleDelete, handleDuplicate }: OptionsBut size="small" onClick={handleClick} > - + Date: Fri, 22 Nov 2024 21:56:02 +0530 Subject: [PATCH 152/165] chore: lint --- src/components/molecules/RecordingsTable.tsx | 56 ++++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 2a9febe23..6021313db 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -161,7 +161,7 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl row.name.toLowerCase().includes(searchTerm.toLowerCase()) ); - + return ( @@ -254,33 +254,33 @@ export const RecordingsTable = ({ handleEditRecording, handleRunRecording, handl handleIntegrateRecording(row.id, row.name, row.params || [])} /> ); - case 'options': - return ( - - handleEditRobot(row.id, row.name, row.params || [])} - handleDelete={() => { - - checkRunsForRecording(row.id).then((result: boolean) => { - if (result) { - notify('warning', 'Cannot delete recording as it has active runs'); - } - }) - - deleteRecordingFromStorage(row.id).then((result: boolean) => { - if (result) { - setRows([]); - notify('success', 'Recording deleted successfully'); - fetchRecordings(); - } - }) - }} - handleDuplicate={() => { - handleDuplicateRobot(row.id, row.name, row.params || []); - }} - /> - - ); + case 'options': + return ( + + handleEditRobot(row.id, row.name, row.params || [])} + handleDelete={() => { + + checkRunsForRecording(row.id).then((result: boolean) => { + if (result) { + notify('warning', 'Cannot delete recording as it has active runs'); + } + }) + + deleteRecordingFromStorage(row.id).then((result: boolean) => { + if (result) { + setRows([]); + notify('success', 'Recording deleted successfully'); + fetchRecordings(); + } + }) + }} + handleDuplicate={() => { + handleDuplicateRobot(row.id, row.name, row.params || []); + }} + /> + + ); case 'settings': return ( From 426d02d649d0d142bfc7692f710ebfbdf3dbf093 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:56:46 +0530 Subject: [PATCH 153/165] chore: remove unused imports --- src/components/molecules/RecordingsTable.tsx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index 6021313db..f6a9373b2 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -9,13 +9,9 @@ import TablePagination from '@mui/material/TablePagination'; import TableRow from '@mui/material/TableRow'; import { useEffect } from "react"; import { WorkflowFile } from "maxun-core"; - - import SearchIcon from '@mui/icons-material/Search'; import { IconButton, Button, Box, Typography, TextField, MenuItem, Menu, ListItemIcon, ListItemText } from "@mui/material"; import { Schedule, DeleteForever, Edit, PlayCircle, Settings, Power, ContentCopy, MoreHoriz } from "@mui/icons-material"; - -import LinkIcon from '@mui/icons-material/Link'; import { useGlobalInfoStore } from "../../context/globalInfo"; import { checkRunsForRecording, deleteRecordingFromStorage, getStoredRecordings } from "../../api/storage"; import { Add } from "@mui/icons-material"; @@ -23,11 +19,6 @@ import { useNavigate } from 'react-router-dom'; import { stopRecording } from "../../api/recording"; import { GenericModal } from '../atoms/GenericModal'; -import axios from 'axios'; -import { apiUrl } from '../../apiConfig'; -import { Menu as MenuIcon } from '@mui/icons-material'; - - /** TODO: * 1. allow editing existing robot after persisting browser steps * 2. show robot settings: id, url, etc. From 483dcb02b3ca8ab847d70b8026f53c7aebd069c9 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 21:56:59 +0530 Subject: [PATCH 154/165] chore: remove todo --- src/components/molecules/RecordingsTable.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/molecules/RecordingsTable.tsx b/src/components/molecules/RecordingsTable.tsx index f6a9373b2..e9f0aebc2 100644 --- a/src/components/molecules/RecordingsTable.tsx +++ b/src/components/molecules/RecordingsTable.tsx @@ -21,7 +21,6 @@ import { GenericModal } from '../atoms/GenericModal'; /** TODO: * 1. allow editing existing robot after persisting browser steps - * 2. show robot settings: id, url, etc. */ interface Column { From e3b72d192ff728e9c16c381b2054dafd70bf41db Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 22:57:15 +0530 Subject: [PATCH 155/165] feat: use stealth plugin --- server/src/routes/record.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/routes/record.ts b/server/src/routes/record.ts index f3067fb07..51d3ff922 100644 --- a/server/src/routes/record.ts +++ b/server/src/routes/record.ts @@ -11,14 +11,14 @@ import { stopRunningInterpretation, getRemoteBrowserCurrentUrl, getRemoteBrowserCurrentTabs, } from '../browser-management/controller' -import { chromium } from 'playwright'; +import { chromium } from 'playwright-extra'; import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import logger from "../logger"; import { getDecryptedProxyConfig } from './proxy'; import { requireSignIn } from '../middlewares/auth'; export const router = Router(); -// chromium.use(stealthPlugin()); +chromium.use(stealthPlugin()); export interface AuthenticatedRequest extends Request { From 6375f1e4f5551cdec6996de79769ecf4b90e752c Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 23:04:03 +0530 Subject: [PATCH 156/165] feat: use stealth plugin --- server/src/api/record.ts | 4 +++- server/src/browser-management/classes/RemoteBrowser.ts | 5 +++-- server/src/routes/proxy.ts | 4 +++- server/src/routes/storage.ts | 4 +++- server/src/workflow-management/scheduler/index.ts | 4 +++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/server/src/api/record.ts b/server/src/api/record.ts index 22170b938..fef0be9af 100644 --- a/server/src/api/record.ts +++ b/server/src/api/record.ts @@ -1,6 +1,7 @@ import { readFile, readFiles } from "../workflow-management/storage"; import { Router, Request, Response } from 'express'; -import { chromium } from "playwright"; +import { chromium } from "playwright-extra"; +import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import { requireAPIKey } from "../middlewares/api"; import Robot from "../models/Robot"; import Run from "../models/Run"; @@ -14,6 +15,7 @@ import { io, Socket } from "socket.io-client"; import { BinaryOutputService } from "../storage/mino"; import { AuthenticatedRequest } from "../routes/record" import {capture} from "../utils/analytics"; +chromium.use(stealthPlugin()); const formatRecording = (recordingData: any) => { const recordingMeta = recordingData.recording_meta; diff --git a/server/src/browser-management/classes/RemoteBrowser.ts b/server/src/browser-management/classes/RemoteBrowser.ts index d62eeeaa1..07ea8780b 100644 --- a/server/src/browser-management/classes/RemoteBrowser.ts +++ b/server/src/browser-management/classes/RemoteBrowser.ts @@ -3,9 +3,10 @@ import { Browser, CDPSession, BrowserContext, - chromium, } from 'playwright'; import { Socket } from "socket.io"; +import { chromium } from 'playwright-extra'; +import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import { PlaywrightBlocker } from '@cliqz/adblocker-playwright'; import fetch from 'cross-fetch'; @@ -14,7 +15,7 @@ import { InterpreterSettings, RemoteBrowserOptions } from "../../types"; import { WorkflowGenerator } from "../../workflow-management/classes/Generator"; import { WorkflowInterpreter } from "../../workflow-management/classes/Interpreter"; import { getDecryptedProxyConfig } from '../../routes/proxy'; - +chromium.use(stealthPlugin()); /** diff --git a/server/src/routes/proxy.ts b/server/src/routes/proxy.ts index fe6bcd909..d68a889e4 100644 --- a/server/src/routes/proxy.ts +++ b/server/src/routes/proxy.ts @@ -1,8 +1,10 @@ import { Router, Request, Response } from 'express'; -import { chromium } from "playwright"; +import { chromium } from 'playwright-extra'; +import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import User from '../models/User'; import { encrypt, decrypt } from '../utils/auth'; import { requireSignIn } from '../middlewares/auth'; +chromium.use(stealthPlugin()); export const router = Router(); diff --git a/server/src/routes/storage.ts b/server/src/routes/storage.ts index 2ad14d40f..f84583d0b 100644 --- a/server/src/routes/storage.ts +++ b/server/src/routes/storage.ts @@ -1,7 +1,8 @@ import { Router } from 'express'; import logger from "../logger"; import { createRemoteBrowserForRun, destroyRemoteBrowser } from "../browser-management/controller"; -import { chromium } from "playwright"; +import { chromium } from 'playwright-extra'; +import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import { browserPool } from "../server"; import { uuid } from "uuidv4"; import moment from 'moment-timezone'; @@ -17,6 +18,7 @@ import { AuthenticatedRequest } from './record'; import { computeNextRun } from '../utils/schedule'; import { capture } from "../utils/analytics"; import { tryCatch } from 'bullmq'; +chromium.use(stealthPlugin()); export const router = Router(); diff --git a/server/src/workflow-management/scheduler/index.ts b/server/src/workflow-management/scheduler/index.ts index a94c6d16f..082fcf2ed 100644 --- a/server/src/workflow-management/scheduler/index.ts +++ b/server/src/workflow-management/scheduler/index.ts @@ -1,5 +1,6 @@ import { uuid } from "uuidv4"; -import { chromium } from "playwright"; +import { chromium } from 'playwright-extra'; +import stealthPlugin from 'puppeteer-extra-plugin-stealth'; import { io, Socket } from "socket.io-client"; import { createRemoteBrowserForRun, destroyRemoteBrowser } from '../../browser-management/controller'; import logger from '../../logger'; @@ -10,6 +11,7 @@ import Run from "../../models/Run"; import { getDecryptedProxyConfig } from "../../routes/proxy"; import { BinaryOutputService } from "../../storage/mino"; import { capture } from "../../utils/analytics"; +chromium.use(stealthPlugin()); async function createWorkflowAndStoreMetadata(id: string, userId: string) { try { From 66f2ed1a51f7983dec2c196f7f73e94b07059bab Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 23:13:31 +0530 Subject: [PATCH 157/165] chore(deps): install puppeteer-extra-plugin-recaptcha --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index b25715d43..f4f7a7584 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "playwright-extra": "^4.3.6", "posthog-node": "^4.2.1", "prismjs": "^1.28.0", + "puppeteer-extra-plugin-recaptcha": "^3.6.8", "puppeteer-extra-plugin-stealth": "^2.11.2", "react": "^18.0.0", "react-dom": "^18.0.0", From 101acd49e490235d2f67aad73480f69933bf44ea Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 22 Nov 2024 23:39:34 +0530 Subject: [PATCH 158/165] feat: add notification timeout --- src/components/molecules/RobotDuplicate.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 3fe6abb77..8029d7dd4 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -103,11 +103,13 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia const success = await duplicateRecording(robot.recording_meta.id, targetUrl); if (success) { - notify('success', 'Target URL updated successfully.'); + notify('success', 'Robot duplicated successfully.'); handleStart(robot); // Inform parent about the updated robot handleClose(); // Close the modal - window.location.reload(); + setTimeout(() => { + window.location.reload(); + }, 1000); } else { notify('error', 'Failed to update the Target URL. Please try again.'); } From 714c77e0c4a4be1104782da483d67a9dad0c4a47 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 22 Nov 2024 23:40:05 +0530 Subject: [PATCH 159/165] feat: add notification timeout --- src/components/molecules/RobotEdit.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index f68beee24..7148ba56b 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -118,7 +118,9 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin handleStart(robot); // Inform parent about the updated robot handleClose(); // Close the modal - window.location.reload(); + setTimeout(() => { + window.location.reload(); + }, 1000); } else { notify('error', 'Failed to update the robot. Please try again.'); } From 42746ec3ed2754216ab7ebf2513b9f93a3f2bea4 Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 22 Nov 2024 23:48:56 +0530 Subject: [PATCH 160/165] chore: rm comments for handleClose --- src/components/molecules/RobotDuplicate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotDuplicate.tsx b/src/components/molecules/RobotDuplicate.tsx index 8029d7dd4..850614b0f 100644 --- a/src/components/molecules/RobotDuplicate.tsx +++ b/src/components/molecules/RobotDuplicate.tsx @@ -105,7 +105,7 @@ export const RobotDuplicationModal = ({ isOpen, handleStart, handleClose, initia if (success) { notify('success', 'Robot duplicated successfully.'); handleStart(robot); // Inform parent about the updated robot - handleClose(); // Close the modal + handleClose(); setTimeout(() => { window.location.reload(); From c93953a7ad4658ac3d198bf33135dd8aaa32d81c Mon Sep 17 00:00:00 2001 From: RohitR311 Date: Fri, 22 Nov 2024 23:49:15 +0530 Subject: [PATCH 161/165] chore: rm comments for handleClose --- src/components/molecules/RobotEdit.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/molecules/RobotEdit.tsx b/src/components/molecules/RobotEdit.tsx index 7148ba56b..74b50f626 100644 --- a/src/components/molecules/RobotEdit.tsx +++ b/src/components/molecules/RobotEdit.tsx @@ -116,7 +116,7 @@ export const RobotEditModal = ({ isOpen, handleStart, handleClose, initialSettin if (success) { notify('success', 'Robot updated successfully.'); handleStart(robot); // Inform parent about the updated robot - handleClose(); // Close the modal + handleClose(); setTimeout(() => { window.location.reload(); From 7b1d36b0d979624f538c99463fe0071ff23a2aeb Mon Sep 17 00:00:00 2001 From: amhsirak Date: Fri, 22 Nov 2024 23:52:05 +0530 Subject: [PATCH 162/165] feat: use images from docker hub --- docker-compose.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 1aeefd266..4259aa4d0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,8 +40,9 @@ services: backend: build: - context: . - dockerfile: server/Dockerfile + #context: . + #dockerfile: server/Dockerfile + image: getmaxun/maxun-backend:latest ports: - "8080:8080" env_file: .env @@ -67,8 +68,9 @@ services: frontend: build: - context: . - dockerfile: Dockerfile + #context: . + #dockerfile: Dockerfile + image: getmaxun/maxun-frontend:latest ports: - "5173:5173" env_file: .env From 03888b9b96ba5fe8e49332c11bad7a4e0f5639d5 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 23 Nov 2024 00:00:32 +0530 Subject: [PATCH 163/165] chore: upgrade to v0.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f4f7a7584..8c914f558 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "maxun", - "version": "0.0.1", + "version": "0.0.2", "author": "Maxun", "license": "AGPL-3.0-or-later", "dependencies": { From 6648e3e2bdd0106e0dcaba51de6f643019d904e7 Mon Sep 17 00:00:00 2001 From: amhsirak Date: Sat, 23 Nov 2024 01:47:05 +0530 Subject: [PATCH 164/165] chore: use getmaxun images from docker hub --- docker-compose.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index e50fc4466..abd4565e3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,10 +40,10 @@ services: - minio_data:/data backend: - build: + #build: #context: . #dockerfile: server/Dockerfile - image: getmaxun/maxun-backend:latest + image: getmaxun/maxun-backend:v0.0.1 ports: - "8080:8080" env_file: .env @@ -58,6 +58,7 @@ services: - seccomp=unconfined # This might help with browser sandbox issues # Increase shared memory size for Chromium shm_size: '2gb' + mem_limit: 2g # Set a 2GB memory limit depends_on: - postgres - redis @@ -68,10 +69,10 @@ services: - /var/run/dbus:/var/run/dbus frontend: - build: + #build: #context: . #dockerfile: Dockerfile - image: getmaxun/maxun-frontend:latest + image: getmaxun/maxun-frontend:v0.0.1 ports: - "5173:5173" env_file: .env From 7d1e7f1868b0b4cea1f2cff74652983f25a86035 Mon Sep 17 00:00:00 2001 From: Karishma Shukla Date: Sat, 23 Nov 2024 02:11:28 +0530 Subject: [PATCH 165/165] chore: add note --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a2f2d35af..dd5624220 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ Maxun lets you train a robot in 2 minutes and scrape the web on auto-pilot. Web +> Note: We are in early stages of development and do not support self hosting yet. You can run Maxun locally. + # Local Setup ### Docker Compose ```