diff --git a/api/package.json b/api/package.json index 58a03e9b..325a9ce0 100644 --- a/api/package.json +++ b/api/package.json @@ -26,14 +26,17 @@ "homepage": "https://github.com/contentstack-expert-services/migration-v2-node-server#readme", "dependencies": { "axios": "^1.6.5", + "chokidar": "^3.6.0", "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.18.2", "express-validator": "^7.0.1", "express-winston": "^4.2.0", "helmet": "^7.1.0", + "http": "^0.0.1-security", "jsonwebtoken": "^9.0.2", "lowdb": "^7.0.1", + "socket.io": "^4.7.5", "uuid": "^9.0.1", "winston": "^3.11.0" }, diff --git a/api/src/constants/index.ts b/api/src/constants/index.ts index e3a8c613..19e712e4 100644 --- a/api/src/constants/index.ts +++ b/api/src/constants/index.ts @@ -72,10 +72,8 @@ export const HTTP_TEXTS = { "Sorry, the requested content mapper id does not exists.", ADMIN_LOGIN_ERROR: "Sorry, You Don't have admin access in any of the Organisation", - PROJECT_DELETE: - "Project Deleted Successfully", - PROJECT_REVERT: - "Project Reverted Successfully" + PROJECT_DELETE: "Project Deleted Successfully", + PROJECT_REVERT: "Project Reverted Successfully", }; export const HTTP_RESPONSE_HEADERS = { @@ -145,4 +143,4 @@ export const CONTENT_TYPE_STATUS = { 2: 2, //verified 3: 3, //mapping failed 4: 4, //auto-dump -} +}; diff --git a/api/src/controllers/projects.controller.ts b/api/src/controllers/projects.controller.ts index 223254c9..dbe1abb9 100644 --- a/api/src/controllers/projects.controller.ts +++ b/api/src/controllers/projects.controller.ts @@ -77,5 +77,5 @@ export const projectController = { updateDestinationStack, updateCurrentStep, deleteProject, - revertProject + revertProject, }; diff --git a/api/src/models/contentTypesMapper-lowdb.ts b/api/src/models/contentTypesMapper-lowdb.ts index f28d4ad4..77c3897a 100644 --- a/api/src/models/contentTypesMapper-lowdb.ts +++ b/api/src/models/contentTypesMapper-lowdb.ts @@ -9,7 +9,7 @@ export interface ContentTypesMapper { updateAt: Date; contentstackTitle: string; contentstackUid: string; - status:number; + status: number; fieldMapping: []; } diff --git a/api/src/models/project-lowdb.ts b/api/src/models/project-lowdb.ts index 683c60c5..2e5df806 100644 --- a/api/src/models/project-lowdb.ts +++ b/api/src/models/project-lowdb.ts @@ -18,7 +18,7 @@ interface LegacyCMS { awsRegion: string; bucketName: string; buketKey: string; -} + }; file_path: string; is_fileValid: boolean; } diff --git a/api/src/routes/contentMapper.routes.ts b/api/src/routes/contentMapper.routes.ts index 0fed1c01..a92401e6 100644 --- a/api/src/routes/contentMapper.routes.ts +++ b/api/src/routes/contentMapper.routes.ts @@ -47,5 +47,4 @@ router.get( asyncRouter(contentMapperController.removeContentMapper) ); - export default router; diff --git a/api/src/routes/projects.routes.ts b/api/src/routes/projects.routes.ts index 2cca80ab..5c7a5409 100644 --- a/api/src/routes/projects.routes.ts +++ b/api/src/routes/projects.routes.ts @@ -69,6 +69,6 @@ router.put( router.delete("/:projectId", asyncRouter(projectController.deleteProject)); //revert Project Route -router.patch("/:projectId", asyncRouter(projectController.revertProject)) +router.patch("/:projectId", asyncRouter(projectController.revertProject)); export default router; diff --git a/api/src/server.ts b/api/src/server.ts index 9e0a1a8a..678121af 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -16,7 +16,10 @@ import { unmatchedRoutesMiddleware } from "./middlewares/unmatched-routes.middle import logger from "./utils/logger.js"; import contentMapperRoutes from "./routes/contentMapper.routes.js"; import migrationRoutes from "./routes/migration.routes.js"; - +import chokidar from "chokidar"; +import { Server } from "socket.io"; +import http from "http"; +import fs from "fs"; try { const app = express(); app.use( @@ -48,9 +51,45 @@ try { // starting the server & DB connection. (async () => { await connectToDatabase(); - app.listen(config.PORT, () => + const server = app.listen(config.PORT, () => logger.info(`Server listening at port ${config.PORT}`) ); + // Chokidar - Watch for log file changes + const logFilePath = "/Users/sayali.joshi/Projects/migration-v2-node-server/api/combine.log"; // Replace with the actual path to your log file + const watcher = chokidar.watch(logFilePath); + // Socket.IO - Send logs to client + const io = new Server( + server, + (http, + { + cors: { + origin: "*", // This allows all origins. For production, specify exact origins for security. + methods: ["GET", "POST"], // Specify which HTTP methods are allowed. + allowedHeaders: ["my-custom-header"], // Specify which headers are allowed. + credentials: true, // If your client needs to send cookies or credentials with the requests. + }, + }) + ); + + watcher.on("change", (path) => { + // Read the updated log file + fs.readFile(path, "utf8", (err, data) => { + if (err) { + logger.error(`Error reading log file: ${err}`); + return; + } + // Get just the updated data + // const updatedData = data.slice(data.lastIndexOf("\n") + 1); + console.info("updates", data); + // Emit the updated data to all connected clients + try { + const parsedData = data; + io.emit("logUpdate", parsedData); + } catch (error) { + logger.error(`Error parsing data: ${error}`); + } + }); + }); })(); } catch (e) { logger.error("Error while starting the server!"); diff --git a/api/src/services/contentMapper.service.ts b/api/src/services/contentMapper.service.ts index e9d41e96..050e5580 100644 --- a/api/src/services/contentMapper.service.ts +++ b/api/src/services/contentMapper.service.ts @@ -10,7 +10,7 @@ import { STEPPER_STEPS, NEW_PROJECT_STATUS, CONTENT_TYPE_STATUS, - VALIDATION_ERRORS + VALIDATION_ERRORS, } from "../constants/index.js"; import logger from "../utils/logger.js"; import { config } from "../config/index.js"; @@ -37,7 +37,7 @@ const putTestData = async (req: Request) => { return { id, isDeleted: true, ...field }; }); FieldMapperModel.update((data: any) => { - data.field_mapper = [...data?.field_mapper ?? [], ...fields]; + data.field_mapper = [...(data?.field_mapper ?? []), ...fields]; }); contentTypes[index].fieldMapping = fieldIds; }); @@ -109,7 +109,7 @@ const getContentTypes = async (req: Request) => { .value(); content_mapper.push(contentMapperData); }); - + if (!isEmpty(content_mapper)) { if (search) { const filteredResult = content_mapper @@ -255,10 +255,7 @@ const updateContentType = async (req: Request) => { const project = ProjectModelLowdb.data.projects[projectIndex]; if ( - [ - NEW_PROJECT_STATUS[5], - NEW_PROJECT_STATUS[4], - ].includes(project.status) || + [NEW_PROJECT_STATUS[5], NEW_PROJECT_STATUS[4]].includes(project.status) || project.current_step < STEPPER_STEPS.CONTENT_MAPPING ) { logger.error( @@ -281,25 +278,36 @@ const updateContentType = async (req: Request) => { throw new BadRequestError(HTTP_TEXTS.INVALID_CONTENT_TYPE); } - try { await ContentTypesMapperModelLowdb.read(); if (fieldMapping) { - fieldMapping.forEach(async(field: any) => { - if (!field.ContentstackFieldType || field.ContentstackFieldType === '' || field.ContentstackFieldType === 'No matches found' || field.contentstackFieldUid === '') { + fieldMapping.forEach(async (field: any) => { + if ( + !field.ContentstackFieldType || + field.ContentstackFieldType === "" || + field.ContentstackFieldType === "No matches found" || + field.contentstackFieldUid === "" + ) { logger.error( getLogMessage( srcFun, - `${VALIDATION_ERRORS.STRING_REQUIRED.replace("$", "ContentstackFieldType or contentstackFieldUid")}` + `${VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "ContentstackFieldType or contentstackFieldUid" + )}` ) ); await ContentTypesMapperModelLowdb.read(); ContentTypesMapperModelLowdb.update((data: any) => { - data.ContentTypesMappers[updateIndex].status = CONTENT_TYPE_STATUS[3]; + data.ContentTypesMappers[updateIndex].status = + CONTENT_TYPE_STATUS[3]; }); throw new BadRequestError( - `${VALIDATION_ERRORS.STRING_REQUIRED.replace("$", "ContentstackFieldType or contentstackFieldUid")}` + `${VALIDATION_ERRORS.STRING_REQUIRED.replace( + "$", + "ContentstackFieldType or contentstackFieldUid" + )}` ); } }); @@ -324,7 +332,6 @@ const updateContentType = async (req: Request) => { data.ContentTypesMappers[updateIndex].contentstackUid = contentTypeData?.contentstackUid; } - }); if (updateIndex < 0) { @@ -343,7 +350,7 @@ const updateContentType = async (req: Request) => { const fieldIndex = FieldMapperModel.data.field_mapper.findIndex( (f: any) => f?.id === field?.id ); - if (fieldIndex > -1 && field?.ContentstackFieldType !== '') { + if (fieldIndex > -1 && field?.ContentstackFieldType !== "") { FieldMapperModel.update((data: any) => { data.field_mapper[fieldIndex] = field; data.field_mapper[fieldIndex].isDeleted = false; @@ -361,7 +368,7 @@ const updateContentType = async (req: Request) => { .get("ContentTypesMappers") .find({ id: contentTypeId }) .value(); - + return { updatedContentType }; } catch (error: any) { logger.error( @@ -807,5 +814,5 @@ export const contentMapperService = { resetAllContentTypesMapping, removeContentMapper, removeMapping, - getSingleContentTypes + getSingleContentTypes, }; diff --git a/api/src/services/org.service.ts b/api/src/services/org.service.ts index bc69192e..90324fb3 100644 --- a/api/src/services/org.service.ts +++ b/api/src/services/org.service.ts @@ -49,13 +49,14 @@ const getAllStacks = async (req: Request): Promise => { }; } let stacks = res?.data?.stacks; - if(search){ - stacks = stacks.filter((stack:{name: string, description:string})=>{ + if (search) { + stacks = stacks.filter((stack: { name: string; description: string }) => { const stackName = stack?.name?.toLowerCase(); const stackDescription = stack?.description?.toLowerCase(); - return stackName?.includes(search) || stackDescription?.includes(search); - - }) + return ( + stackName?.includes(search) || stackDescription?.includes(search) + ); + }); } const locale = await getStackLocal(token_payload, stacks); return { @@ -301,7 +302,7 @@ const getStackLocal = async (token_payload: any, data: any) => { token_payload?.region as keyof typeof config.CS_API ]!}/locales`, headers: { - api_key: stack.api_key, + api_key: stack.api_key, authtoken, }, }) diff --git a/api/src/services/projects.service.ts b/api/src/services/projects.service.ts index d80ce40d..b76e604f 100644 --- a/api/src/services/projects.service.ts +++ b/api/src/services/projects.service.ts @@ -1,8 +1,8 @@ import { Request } from "express"; import ProjectModelLowdb from "../models/project-lowdb.js"; -import ContentTypesMapperModelLowdb from "../models/contentTypesMapper-lowdb.js" +import ContentTypesMapperModelLowdb from "../models/contentTypesMapper-lowdb.js"; import FieldMapperModel from "../models/FieldMapper.js"; - + import { BadRequestError, ExceptionFunction, @@ -36,7 +36,7 @@ const getAllProjects = async (req: Request) => { org_id: orgId, region, owner: user_id, - isDeleted:false + isDeleted: false, }) .value(); @@ -85,18 +85,19 @@ const createProject = async (req: Request) => { test_stacks: [], current_test_stack_id: "", legacy_cms: { - "is_fileValid":false, + is_fileValid: false, awsDetails: { awsRegion: "", bucketName: "", - buketKey: "" - } }, + buketKey: "", + }, + }, content_mapper: [], execution_log: [], created_by: user_id, updated_at: new Date().toISOString(), created_at: new Date().toISOString(), - isDeleted:false + isDeleted: false, }; try { @@ -353,7 +354,8 @@ const affixConfirmation = async (req: Request) => { const updateFileFormat = async (req: Request) => { const { orgId, projectId } = req.params; - const { token_payload, file_format,file_path,is_fileValid,awsDetails } = req.body; + const { token_payload, file_format, file_path, is_fileValid, awsDetails } = + req.body; const srcFunc = "updateFileFormat"; const projectIndex = (await getProjectUtil( projectId, @@ -395,16 +397,19 @@ const updateFileFormat = async (req: Request) => { // } try { - ProjectModelLowdb.update((data: any) => { + ProjectModelLowdb.update((data: any) => { data.projects[projectIndex].legacy_cms.file_format = file_format; data.projects[projectIndex].legacy_cms.file_path = file_path; data.projects[projectIndex].legacy_cms.is_fileValid = is_fileValid; data.projects[projectIndex].current_step = STEPPER_STEPS.LEGACY_CMS; data.projects[projectIndex].status = NEW_PROJECT_STATUS[0]; data.projects[projectIndex].updated_at = new Date().toISOString(); - data.projects[projectIndex].legacy_cms.awsDetails.awsRegion = awsDetails.awsRegion; - data.projects[projectIndex].legacy_cms.awsDetails.bucketName = awsDetails.bucketName; - data.projects[projectIndex].legacy_cms.awsDetails.buketKey = awsDetails.buketKey; + data.projects[projectIndex].legacy_cms.awsDetails.awsRegion = + awsDetails.awsRegion; + data.projects[projectIndex].legacy_cms.awsDetails.bucketName = + awsDetails.bucketName; + data.projects[projectIndex].legacy_cms.awsDetails.buketKey = + awsDetails.buketKey; }); logger.info( @@ -644,7 +649,7 @@ const updateCurrentStep = async (req: Request) => { ProjectModelLowdb.update((data: any) => { data.projects[projectIndex].current_step = STEPPER_STEPS.CONTENT_MAPPING; - // data.projects[projectIndex].status = NEW_PROJECT_STATUS[3]; + // data.projects[projectIndex].status = NEW_PROJECT_STATUS[3]; data.projects[projectIndex].updated_at = new Date().toISOString(); }); break; @@ -779,9 +784,9 @@ const revertProject = async (req: Request) => { )) as number; const projects = ProjectModelLowdb.data.projects[projectIndex]; - if (!projects){ + if (!projects) { throw new NotFoundError(HTTP_TEXTS.PROJECT_NOT_FOUND); - } else{ + } else { ProjectModelLowdb.update((data: any) => { data.projects[projectIndex].isDeleted = false; }); @@ -796,11 +801,11 @@ const revertProject = async (req: Request) => { status: HTTP_CODES.OK, data: { message: HTTP_TEXTS.PROJECT_REVERT, - Project:projects + Project: projects, }, }; } -} +}; export const projectService = { getAllProjects, getProject, @@ -814,5 +819,5 @@ export const projectService = { updateDestinationStack, updateCurrentStep, deleteProject, - revertProject + revertProject, }; diff --git a/ui/package-lock.json b/ui/package-lock.json index d2cab51b..e78b26cd 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -19,6 +19,7 @@ "@types/react-redux": "^7.1.33", "axios": "^1.5.1", "bootstrap": "5.1.3", + "chokidar": "^3.6.0", "html-react-parser": "^4.2.9", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -29,6 +30,8 @@ "react-scripts": "5.0.1", "redux-persist": "^6.0.0", "sass": "^1.68.0", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, @@ -5024,6 +5027,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, "node_modules/@storybook/addons": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", @@ -5914,6 +5922,19 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/css-modules": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/css-modules/-/css-modules-1.0.5.tgz", @@ -7785,6 +7806,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -8156,15 +8185,9 @@ "integrity": "sha512-+67P1GkJRaxQD6PKK0Et9DhwQB+vGg3PM5+aavopCpZT1lj9jeqfvpgTLAWErNj8qApkkmXlu/Ug74kmhagkXg==" }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -8177,6 +8200,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -8535,6 +8561,18 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -9804,6 +9842,94 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-client": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.5.4.tgz", + "integrity": "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.0.0" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/enhanced-resolve": { "version": "5.15.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", @@ -21630,6 +21756,78 @@ "resolved": "https://registry.npmjs.org/sleep-promise/-/sleep-promise-8.0.1.tgz", "integrity": "sha512-nfwyX+G1dsx2R1DMMKWLpNxuHMOCL7JIRBUw0fl7Z4nZ1YZK0apZuGY8MDexn0HDZzgbERgj/CrNtsYpo/B7eA==" }, + "node_modules/socket.io": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", + "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/socket.io-client": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.7.5.tgz", + "integrity": "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", @@ -24153,6 +24351,14 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", + "integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/ui/package.json b/ui/package.json index e578593c..51f70482 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,6 +14,7 @@ "@types/react-redux": "^7.1.33", "axios": "^1.5.1", "bootstrap": "5.1.3", + "chokidar": "^3.6.0", "html-react-parser": "^4.2.9", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -24,6 +25,8 @@ "react-scripts": "5.0.1", "redux-persist": "^6.0.0", "sass": "^1.68.0", + "socket.io": "^4.7.5", + "socket.io-client": "^4.7.5", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, diff --git a/ui/public/index.html b/ui/public/index.html index 9eb09bff..b730eece 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -21,18 +21,18 @@ crossorigin="anonymous" /> + + - - - +