From 6e372edb6731840601d5b44d48f37ab1f2e2349b Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 17:16:33 +0000 Subject: [PATCH 01/12] latest --- .../log-viewer-webui/client/package-lock.json | 201 +++++++++++++++++- .../log-viewer-webui/client/package.json | 2 + .../client/tsconfig/tsconfig.app.json | 7 +- .../log-viewer-webui/client/vite.config.ts | 20 +- components/log-viewer-webui/common/index.ts | 3 +- .../src/plugins/MongoSocketIoServer/index.ts | 65 ++++-- 6 files changed, 259 insertions(+), 39 deletions(-) diff --git a/components/log-viewer-webui/client/package-lock.json b/components/log-viewer-webui/client/package-lock.json index fc1a071310..b17ec28b61 100644 --- a/components/log-viewer-webui/client/package-lock.json +++ b/components/log-viewer-webui/client/package-lock.json @@ -18,13 +18,15 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.4.1", + "socket.io-client": "^4.8.1", + "vite-tsconfig-paths": "^5.1.4", "zustand": "^5.0.3" }, "devDependencies": { "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", - "eslint-config-yscope": "*", + "eslint-config-yscope": "latest", "typescript": "~5.6.2", "vite": "^6.2.6" } @@ -2162,6 +2164,12 @@ "integrity": "sha512-gu+tdy9WZIRulrR4CAcGXZAAixwakKszkUXudMJ4EhtNflBEify5Pm5vnVEVqdmMkxnT4tcdfJps5XYqaNeF9Q==", "license": "MIT" }, + "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==", + "license": "MIT" + }, "node_modules/@stylistic/eslint-plugin": { "version": "2.13.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-2.13.0.tgz", @@ -2238,7 +2246,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -3681,6 +3689,45 @@ "dev": true, "license": "ISC" }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "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.1.1" + } + }, + "node_modules/engine.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", @@ -3891,7 +3938,7 @@ "version": "0.25.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz", "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4913,6 +4960,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -5856,7 +5909,7 @@ "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -6203,7 +6256,7 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, + "devOptional": true, "funding": [ { "type": "opencollective", @@ -7082,7 +7135,7 @@ "version": "4.34.8", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.34.8.tgz", "integrity": "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -7400,6 +7453,68 @@ "license": "ISC", "peer": true }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-client/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7413,7 +7528,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, + "devOptional": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7711,6 +7826,26 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsconfck": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", + "integrity": "sha512-CLDfGgUp7XPswWnezWwsCRxNmgQjhYq3VXHM0/XIRxhVrKw0M1if9agzryh1QS3nxjCROvV+xWxoJO1YctzzWg==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -7853,7 +7988,7 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -8073,7 +8208,7 @@ "version": "6.2.6", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -8141,6 +8276,25 @@ } } }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8261,6 +8415,35 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "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/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/components/log-viewer-webui/client/package.json b/components/log-viewer-webui/client/package.json index 6397be684f..c2f22c0e3b 100644 --- a/components/log-viewer-webui/client/package.json +++ b/components/log-viewer-webui/client/package.json @@ -23,6 +23,8 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.4.1", + "socket.io-client": "^4.8.1", + "vite-tsconfig-paths": "^5.1.4", "zustand": "^5.0.3" }, "devDependencies": { diff --git a/components/log-viewer-webui/client/tsconfig/tsconfig.app.json b/components/log-viewer-webui/client/tsconfig/tsconfig.app.json index 059a6f365a..6cc691d2cd 100644 --- a/components/log-viewer-webui/client/tsconfig/tsconfig.app.json +++ b/components/log-viewer-webui/client/tsconfig/tsconfig.app.json @@ -1,7 +1,8 @@ { "extends": "./tsconfig.base.json", "include": [ - "../src" + "../src", + "../common/**/*" ], "compilerOptions": { @@ -15,6 +16,10 @@ "target": "ES2022", "tsBuildInfoFile": "../node_modules/.tmp/tsconfig.app.tsbuildinfo", "useDefineForClassFields": true, + "baseUrl": "../", + "paths": { + "@common/*": ["../common/*"], // Map imports from "@common/*" to the shared folder + }, /* Bundler mode */ "isolatedModules": true, diff --git a/components/log-viewer-webui/client/vite.config.ts b/components/log-viewer-webui/client/vite.config.ts index 9b2428dc87..b6c92f412e 100644 --- a/components/log-viewer-webui/client/vite.config.ts +++ b/components/log-viewer-webui/client/vite.config.ts @@ -1,21 +1,29 @@ import react from "@vitejs/plugin-react"; import {defineConfig} from "vite"; - +import tsconfigPaths from 'vite-tsconfig-paths'; // https://vite.dev/config/ export default defineConfig({ - base: "./", - plugins: [react()], - publicDir: "public", + plugins: [react(), tsconfigPaths()], server: { + host: "0.0.0.0", port: 8080, proxy: { "/query/": { // Below target should match the server's configuration in // `components/log-viewer-webui/server/.env` (or `.env.local` if overridden) - target: "http://localhost:3000/", + target: "http://localhost:3001/", changeOrigin: true, }, + "/socket.io/": { + target: "ws://localhost:3001/", + changeOrigin: true, + ws: true, + }, }, + fs: { + allow: ['..'] // allow serving files from one level up (common folder) + } }, -}); + publicDir: "public", +}); \ No newline at end of file diff --git a/components/log-viewer-webui/common/index.ts b/components/log-viewer-webui/common/index.ts index 0983fdb6e2..33e2957206 100644 --- a/components/log-viewer-webui/common/index.ts +++ b/components/log-viewer-webui/common/index.ts @@ -35,7 +35,6 @@ type ClientToServerEvents = { requestArgs: { collectionName: string; }, - callback: (res: Response) => void ) => void; "collection::find::subscribe": ( requestArgs: { @@ -73,7 +72,7 @@ interface SocketData { collectionName?: string; } -export { +export type { ClientToServerEvents, Err, InterServerEvents, diff --git a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts index 5d9a3bc259..522127f169 100644 --- a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -152,28 +152,22 @@ class MongoSocketIoServer { * @param socket * @param requestArgs * @param requestArgs.collectionName - * @param callback */ async #collectionInitListener ( socket: MongoCustomSocket, requestArgs: {collectionName: string}, - callback:(res: Response) => void ): Promise { const {collectionName} = requestArgs; this.#fastify.log.info( `Socket:${socket.id} requested init of collection:${collectionName}` ); - - const hasCollection = await this.#hasCollection(collectionName); - if (false === hasCollection) { - this.#fastify.log.error(`Collection ${collectionName} does not exist in MongoDB`); - callback({ - error: `Collection ${collectionName} does not exist in MongoDB`, - }); - - return; - } - + /* eslint-disable no-warning-comments */ + // TODO: The init socket event could race with the subscription event (i.e. the subscription + // event could run concurrently with the init event) leading to errors due to an unitialized + // collection. Consider removing this event entirely and using the subscription event to + // initialize the collection. If this event remains, do not run any async/await code + // in this function. With no aysnc/await, a race is unlikely since the init and + // subscription events should be serialized by Socket.IO. socket.data = {...socket.data, collectionName}; } @@ -222,6 +216,26 @@ class MongoSocketIoServer { return queryId; } + /** + * Gets an existing watcher collection or creates a new one if it doesn't exist. + * + * @param collectionName + * @return The watcher collection instance. + */ + #getOrCreateWatcherCollection ( + collectionName: string + ) + : MongoWatcherCollection { + let watcherCollection = this.#collections.get(collectionName); + if ("undefined" === typeof watcherCollection) { + watcherCollection = new MongoWatcherCollection(collectionName, this.#mongoDb); + this.#fastify.log.info(`Created MongoDb collection:${collectionName}.`); + this.#collections.set(collectionName, watcherCollection); + } + + return watcherCollection; + } + /** * Listener for subscribing to a find query. The client will receive updates whenever * the query results change. @@ -240,23 +254,32 @@ class MongoSocketIoServer { const {query, options} = requestArgs; const {collectionName} = socket.data; + this.#fastify.log.info( + `Socket:${socket.id} requested query:${JSON.stringify(query)} ` + + `with options:${JSON.stringify(options)} to collection:${collectionName}` + ); + if ("undefined" === typeof collectionName) { this.#fastify.log.error(`Collection name:${collectionName} is undefined`); + callback({ + error: "Collection was not initialized on server prior to query request.", + }); return; } - this.#fastify.log.info( - `Socket:${socket.id} requested query:${JSON.stringify(query)} ` + - `with options:${JSON.stringify(options)} to collection:${collectionName}` - ); + const hasCollection = await this.#hasCollection(collectionName); + if (false === hasCollection) { + this.#fastify.log.error(`Collection ${collectionName} does not exist in MongoDB`); + callback({ + error: `Collection ${collectionName} does not exist in MongoDB on server`, + }); - let watcherCollection = this.#collections.get(collectionName); - if ("undefined" === typeof watcherCollection) { - watcherCollection = new MongoWatcherCollection(collectionName, this.#mongoDb); - this.#collections.set(collectionName, watcherCollection); + return; } + const watcherCollection = this.#getOrCreateWatcherCollection(collectionName); + const queryParameters: QueryParameters = {collectionName, query, options}; const queryId = this.#getQueryId(queryParameters); From 712bd99d42d400f1c31ae7b5f1f62f764c10cd73 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 17:23:49 +0000 Subject: [PATCH 02/12] latest --- .../client/tsconfig/tsconfig.app.json | 2 +- components/log-viewer-webui/client/vite.config.ts | 8 +++++--- .../server/src/plugins/MongoSocketIoServer/index.ts | 12 ++++++------ 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/components/log-viewer-webui/client/tsconfig/tsconfig.app.json b/components/log-viewer-webui/client/tsconfig/tsconfig.app.json index 6cc691d2cd..3f2fa4631b 100644 --- a/components/log-viewer-webui/client/tsconfig/tsconfig.app.json +++ b/components/log-viewer-webui/client/tsconfig/tsconfig.app.json @@ -18,7 +18,7 @@ "useDefineForClassFields": true, "baseUrl": "../", "paths": { - "@common/*": ["../common/*"], // Map imports from "@common/*" to the shared folder + "@common/*": ["../common/*"], // Map imports from "@common/*" to the shared folder }, /* Bundler mode */ diff --git a/components/log-viewer-webui/client/vite.config.ts b/components/log-viewer-webui/client/vite.config.ts index b6c92f412e..49d22e974d 100644 --- a/components/log-viewer-webui/client/vite.config.ts +++ b/components/log-viewer-webui/client/vite.config.ts @@ -2,9 +2,12 @@ import react from "@vitejs/plugin-react"; import {defineConfig} from "vite"; import tsconfigPaths from 'vite-tsconfig-paths'; + // https://vite.dev/config/ export default defineConfig({ + base: "./", plugins: [react(), tsconfigPaths()], + publicDir: "public", server: { host: "0.0.0.0", port: 8080, @@ -12,11 +15,11 @@ export default defineConfig({ "/query/": { // Below target should match the server's configuration in // `components/log-viewer-webui/server/.env` (or `.env.local` if overridden) - target: "http://localhost:3001/", + target: "http://localhost:3000/", changeOrigin: true, }, "/socket.io/": { - target: "ws://localhost:3001/", + target: "ws://localhost:3000/", changeOrigin: true, ws: true, }, @@ -25,5 +28,4 @@ export default defineConfig({ allow: ['..'] // allow serving files from one level up (common folder) } }, - publicDir: "public", }); \ No newline at end of file diff --git a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts index 522127f169..23c3bee5e6 100644 --- a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -162,12 +162,12 @@ class MongoSocketIoServer { `Socket:${socket.id} requested init of collection:${collectionName}` ); /* eslint-disable no-warning-comments */ - // TODO: The init socket event could race with the subscription event (i.e. the subscription - // event could run concurrently with the init event) leading to errors due to an unitialized - // collection. Consider removing this event entirely and using the subscription event to - // initialize the collection. If this event remains, do not run any async/await code - // in this function. With no aysnc/await, a race is unlikely since the init and - // subscription events should be serialized by Socket.IO. + // TODO: The init socket event could race with the subscription event (i.e. the + // subscription event could run concurrently with the init event) leading to errors due + // to an uninitialized collection. Consider removing this event entirely and using the + // subscription event to initialize the collection. If this event remains, do not run any + // async/await code in this function. With no aysnc/await, a race is unlikely since the + // init and subscription events should be serialized by Socket.IO. socket.data = {...socket.data, collectionName}; } From f5dd06cee8e085db387d1606f9cc673434dbc41c Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 17:40:03 +0000 Subject: [PATCH 03/12] latest --- .../src/api/socket/MongoCollectionSocket.ts | 63 +++++++++++ .../src/api/socket/MongoCursorSocket.ts | 107 ++++++++++++++++++ .../client/src/api/socket/useCursor.tsx | 68 +++++++++++ .../log-viewer-webui/client/vite.config.ts | 12 +- 4 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts create mode 100644 components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts create mode 100644 components/log-viewer-webui/client/src/api/socket/useCursor.tsx diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts new file mode 100644 index 0000000000..f4b69c0a1e --- /dev/null +++ b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts @@ -0,0 +1,63 @@ +import { + ClientToServerEvents, + ServerToClientEvents, +} from "@common/index.js"; +import { + io, + Socket, +} from "socket.io-client"; + +import {MongoCursorSocket} from "./MongoCursorSocket.js"; + + +/** + * Socket connection to a MongoDB collection residing on a server. Class provides methods to + * query the collection. + */ +class MongoCollectionSocket { + private socket: Socket; + + /** + * Initalizes socket connection to a MongoDB collection on the server. + * + * @param collectionName + */ + constructor (collectionName: string) { + // eslint-disable-next-line no-warning-comments + // TODO: Add support for user provided domain name (i.e. io("https://server-domain.com")). + // Implementation could involve parsing server .env file and moving server .env to a + // common folder. + + // eslint-disable-next-line no-warning-comments + // TODO: Current implementation creates a new socket connection for each collection. This + // could be problematic if the number of collections is large since browsers limit the + // number of web sockets per domain. An simple change is to use Socket.IO namespace + // feature, and having Socket.IO multiplex the socket per collection namespace. Another + // more involved option is to implement a shared socket. + // Namespace reference: https://socket.io/docs/v4/namespaces/ + this.socket = io(); + this.socket.emit("collection::init", { + collectionName: collectionName, + }); + console.log(`MongoDB collection:${collectionName} initialized.`); + } + + /** + * Selects documents in collection and returns a cursor-like object. + * + * @param query + * @param options + * @return a `MongoCursorSocket`. + */ + + find (query: object, options: object) { + return new MongoCursorSocket( + this.socket, + query, + options, + ); + } +} + + +export default MongoCollectionSocket; diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts new file mode 100644 index 0000000000..852dc9f019 --- /dev/null +++ b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts @@ -0,0 +1,107 @@ +import { + ClientToServerEvents, + QueryId, + Response, + ServerToClientEvents, +} from "@common/index.js"; +import {Socket} from "socket.io-client"; + +import {Nullable} from "../../typings/common"; + + +/** + * A cursor-like object receiving MongoDB documents over a socket connection. + */ +class MongoCursorSocket { + #socket: Socket; + + #query: object; + + #options: object; + + #queryId: Nullable = null; + + + // Listener for data updates from the server. + #updateListener: Nullable<(respArgs: {queryId: number; data: object[]}) => void> = null; + + /** + * @param socket + * @param query + * @param options + */ + constructor ( + socket: Socket, + query: object, + options: object + ) { + this.#socket = socket; + this.#query = query; + this.#options = options; + } + + /** + * Subscribes to query watcher for real-time updates. + * + * @param onDataUpdate Handler which sets data updates from the server in react ui component. + * @throws {Error} if subscription fails. + */ + async subscribe (onDataUpdate: (data: object[]) => void): Promise { + console.log("Attepting to subscribe to query:", this.#query); + + this.#updateListener = (respArgs: {queryId: number; data: object[]}) => { + // Server sends updates for multiple queryIDs using the same event name. + if (this.#queryId === respArgs.queryId) { + onDataUpdate(respArgs.data); + } + }; + + this.#socket.on("collection::find::update", this.#updateListener); + + const response: Response<{queryId: number; initialDocuments: object[]}> = + await this.#socket.emitWithAck( + "collection::find::subscribe", + { + query: this.#query, + options: this.#options, + } + ); + + if ("error" in response) { + throw new Error(`Subscription failed: ${response.error}`); + } + + // Set the initial documents received from the server. + onDataUpdate(response.data.initialDocuments); + + this.#queryId = response.data.queryId; + console.log(`Subscribed to queryID:${this.#queryId}.`); + } + + /** + * Unsubscribe from the query. + */ + unsubscribe (): void { + if (null === this.#queryId) { + console.error("Attempted to unsubscribe, but no active subscription exists."); + + return; + } + + + this.#socket.emit("collection::find::unsubscribe", { + queryId: this.#queryId, + }); + + if (this.#updateListener) { + this.#socket.off("collection::find::update", this.#updateListener); + this.#updateListener = null; + } + + console.log(`Unsubscribed to queryID:${this.#queryId}.`); + + this.#queryId = null; + } +} + +export {MongoCursorSocket}; diff --git a/components/log-viewer-webui/client/src/api/socket/useCursor.tsx b/components/log-viewer-webui/client/src/api/socket/useCursor.tsx new file mode 100644 index 0000000000..4242072831 --- /dev/null +++ b/components/log-viewer-webui/client/src/api/socket/useCursor.tsx @@ -0,0 +1,68 @@ +import { + DependencyList, + useEffect, + useState, +} from "react"; + +import {MongoCursorSocket} from "./MongoCursorSocket.js"; + + +/** + * Custom hook which returns a real-time reactive array of documents from a `MongoCursorSocket`. + * + * @param query Function which returns a `MongoCursorSocket` instance. + * @param dependencies Array of dependencies for the query. + * @return Reactive array. + */ +const useCursor = ( + query: () => MongoCursorSocket, + dependencies: DependencyList = [] +): object[] => { + const [data, setData] = useState([]); + + + useEffect(() => { + const cursor = query(); + + // Flag to ignore updates after unmounting. + let ignore = false; + console.log("Subscribing to cursor"); + + // Handler to set data updates from the server. + const onDataUpdate = (dataUpdate: object[]) => { + if (false === ignore) { + setData(dataUpdate); + } + }; + + const subscribed = cursor.subscribe(onDataUpdate); + + subscribed.catch((error: unknown) => { + console.error("Error during subscription:", error); + }); + + return () => { + ignore = true; + + // For a shortly lived cursor (ex. strict mode), the subscription may have not yet + // recieved the queryID from the server, making it impossible to unsubscribe + // immediately (there is no queryID). The subscribed promise allows unsubcription + // to happen when the subscription actually completes. + subscribed + .then(() => { + // Unsubscribe will not run if the subscription failed since the promise was + // rejected. + console.log("Unsubscribing from cursor"); + cursor.unsubscribe(); + }) + .catch((error: unknown) => { + console.error("Error during unsubscription:", error); + }); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, dependencies); + + return data; +}; + +export {useCursor}; diff --git a/components/log-viewer-webui/client/vite.config.ts b/components/log-viewer-webui/client/vite.config.ts index 49d22e974d..8dbb6586bb 100644 --- a/components/log-viewer-webui/client/vite.config.ts +++ b/components/log-viewer-webui/client/vite.config.ts @@ -1,12 +1,13 @@ import react from "@vitejs/plugin-react"; import {defineConfig} from "vite"; -import tsconfigPaths from 'vite-tsconfig-paths'; +import tsconfigPaths from "vite-tsconfig-paths"; // https://vite.dev/config/ export default defineConfig({ base: "./", - plugins: [react(), tsconfigPaths()], + plugins: [react(), + tsconfigPaths()], publicDir: "public", server: { host: "0.0.0.0", @@ -25,7 +26,8 @@ export default defineConfig({ }, }, fs: { - allow: ['..'] // allow serving files from one level up (common folder) - } + // allow serving files from one level up (common folder) + allow: [".."], + }, }, -}); \ No newline at end of file +}); From ee3ffb7a846d215f02acc69b3185bc2953a970ca Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 18:07:45 +0000 Subject: [PATCH 04/12] latest --- .../log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts index 852dc9f019..0df6cc62e6 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts @@ -68,6 +68,7 @@ class MongoCursorSocket { ); if ("error" in response) { + this.#socket.off("collection::find::update", this.#updateListener); throw new Error(`Subscription failed: ${response.error}`); } From 39e44cfa1c75aee59679c495344e43c13debc361 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 18:15:03 +0000 Subject: [PATCH 05/12] code rabbit --- .../log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts | 2 +- .../server/src/plugins/MongoSocketIoServer/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts index 0df6cc62e6..c6276231c2 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts @@ -47,7 +47,7 @@ class MongoCursorSocket { * @throws {Error} if subscription fails. */ async subscribe (onDataUpdate: (data: object[]) => void): Promise { - console.log("Attepting to subscribe to query:", this.#query); + console.log("Attempting to subscribe to query:", JSON.stringify(this.#query)); this.#updateListener = (respArgs: {queryId: number; data: object[]}) => { // Server sends updates for multiple queryIDs using the same event name. diff --git a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts index 23c3bee5e6..532f851e3e 100644 --- a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -167,7 +167,7 @@ class MongoSocketIoServer { // to an uninitialized collection. Consider removing this event entirely and using the // subscription event to initialize the collection. If this event remains, do not run any // async/await code in this function. With no aysnc/await, a race is unlikely since the - // init and subscription events should be serialized by Socket.IO. + // init and subscription events should be serialised by Socket.IO. socket.data = {...socket.data, collectionName}; } From e3c3ce7b2b2f1fb8ed788a3561d37ce4ce0dd9cd Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 19:08:18 +0000 Subject: [PATCH 06/12] latest --- .../src/api/socket/MongoCollectionSocket.ts | 23 +++------- .../src/api/socket/MongoCursorSocket.ts | 6 +++ components/log-viewer-webui/common/index.ts | 9 ++-- .../src/plugins/MongoSocketIoServer/index.ts | 42 ++----------------- 4 files changed, 19 insertions(+), 61 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts index f4b69c0a1e..8fab73259c 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts @@ -3,10 +3,11 @@ import { ServerToClientEvents, } from "@common/index.js"; import { - io, Socket, } from "socket.io-client"; +import {getSharedSocket} from "./SocketSingleton.js"; + import {MongoCursorSocket} from "./MongoCursorSocket.js"; @@ -15,6 +16,7 @@ import {MongoCursorSocket} from "./MongoCursorSocket.js"; * query the collection. */ class MongoCollectionSocket { + collectionName: string; private socket: Socket; /** @@ -23,22 +25,8 @@ class MongoCollectionSocket { * @param collectionName */ constructor (collectionName: string) { - // eslint-disable-next-line no-warning-comments - // TODO: Add support for user provided domain name (i.e. io("https://server-domain.com")). - // Implementation could involve parsing server .env file and moving server .env to a - // common folder. - - // eslint-disable-next-line no-warning-comments - // TODO: Current implementation creates a new socket connection for each collection. This - // could be problematic if the number of collections is large since browsers limit the - // number of web sockets per domain. An simple change is to use Socket.IO namespace - // feature, and having Socket.IO multiplex the socket per collection namespace. Another - // more involved option is to implement a shared socket. - // Namespace reference: https://socket.io/docs/v4/namespaces/ - this.socket = io(); - this.socket.emit("collection::init", { - collectionName: collectionName, - }); + this.socket = getSharedSocket(); + this.collectionName = collectionName; console.log(`MongoDB collection:${collectionName} initialized.`); } @@ -53,6 +41,7 @@ class MongoCollectionSocket { find (query: object, options: object) { return new MongoCursorSocket( this.socket, + this.collectionName, query, options, ); diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts index c6276231c2..d869d83745 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCursorSocket.ts @@ -15,6 +15,8 @@ import {Nullable} from "../../typings/common"; class MongoCursorSocket { #socket: Socket; + #collectionName: string; + #query: object; #options: object; @@ -27,15 +29,18 @@ class MongoCursorSocket { /** * @param socket + * @param collectionName * @param query * @param options */ constructor ( socket: Socket, + collectionName: string, query: object, options: object ) { this.#socket = socket; + this.#collectionName = collectionName; this.#query = query; this.#options = options; } @@ -62,6 +67,7 @@ class MongoCursorSocket { await this.#socket.emitWithAck( "collection::find::subscribe", { + collectionName: this.#collectionName, query: this.#query, options: this.#options, } diff --git a/components/log-viewer-webui/common/index.ts b/components/log-viewer-webui/common/index.ts index 33e2957206..2e4c07f846 100644 --- a/components/log-viewer-webui/common/index.ts +++ b/components/log-viewer-webui/common/index.ts @@ -31,13 +31,9 @@ type Response = Err | Success; */ type ClientToServerEvents = { "disconnect": () => void; - "collection::init": ( - requestArgs: { - collectionName: string; - }, - ) => void; "collection::find::subscribe": ( requestArgs: { + collectionName: string; query: object; options: object; }, @@ -53,6 +49,9 @@ type ClientToServerEvents = { * Events that the server can emit to the client. */ interface ServerToClientEvents { + // eslint-disable-next-line no-warning-comments + // TODO: Consider replace this with `collection::find::update${number}`, which will + // limit callbacks being triggered in the client to their respective query IDs. "collection::find::update": (respArgs: { queryId: QueryId; data: object[]; diff --git a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts index 532f851e3e..87c3d7d52c 100644 --- a/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts +++ b/components/log-viewer-webui/server/src/plugins/MongoSocketIoServer/index.ts @@ -99,7 +99,6 @@ class MongoSocketIoServer { this.#io.on("connection", (socket) => { this.#fastify.log.info(`New socket connected with ID:${socket.id}`); socket.on("disconnect", this.#disconnectListener.bind(this, socket)); - socket.on("collection::init", this.#collectionInitListener.bind(this, socket)); socket.on( "collection::find::subscribe", this.#collectionFindSubscribeListener.bind(this, socket) @@ -146,32 +145,6 @@ class MongoSocketIoServer { return collections.some((collection) => collection.name === collectionName); } - /** - * Listener for initializing a connection to a collection. - * - * @param socket - * @param requestArgs - * @param requestArgs.collectionName - */ - async #collectionInitListener ( - socket: MongoCustomSocket, - requestArgs: {collectionName: string}, - ): Promise { - const {collectionName} = requestArgs; - this.#fastify.log.info( - `Socket:${socket.id} requested init of collection:${collectionName}` - ); - /* eslint-disable no-warning-comments */ - // TODO: The init socket event could race with the subscription event (i.e. the - // subscription event could run concurrently with the init event) leading to errors due - // to an uninitialized collection. Consider removing this event entirely and using the - // subscription event to initialize the collection. If this event remains, do not run any - // async/await code in this function. With no aysnc/await, a race is unlikely since the - // init and subscription events should be serialised by Socket.IO. - socket.data = {...socket.data, collectionName}; - } - - /** * Adds the query ID to the connection's subscribed query IDs. * @@ -244,30 +217,21 @@ class MongoSocketIoServer { * @param requestArgs * @param requestArgs.query * @param requestArgs.options + * @param requestArgs.collectionName * @param callback */ async #collectionFindSubscribeListener ( socket: MongoCustomSocket, - requestArgs: {query: object; options: object}, + requestArgs: {collectionName: string; query: object; options: object}, callback: (res: Response<{queryId: number; initialDocuments: object[]}>) => void ): Promise { - const {query, options} = requestArgs; - const {collectionName} = socket.data; + const {collectionName, query, options} = requestArgs; this.#fastify.log.info( `Socket:${socket.id} requested query:${JSON.stringify(query)} ` + `with options:${JSON.stringify(options)} to collection:${collectionName}` ); - if ("undefined" === typeof collectionName) { - this.#fastify.log.error(`Collection name:${collectionName} is undefined`); - callback({ - error: "Collection was not initialized on server prior to query request.", - }); - - return; - } - const hasCollection = await this.#hasCollection(collectionName); if (false === hasCollection) { this.#fastify.log.error(`Collection ${collectionName} does not exist in MongoDB`); From 11a1288b3e1dbc047563cabac0a276e866346486 Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 19:11:24 +0000 Subject: [PATCH 07/12] latest --- .../client/src/api/socket/SocketSingleton.ts | 15 +++++++++++++++ components/log-viewer-webui/common/index.ts | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts diff --git a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts new file mode 100644 index 0000000000..f4f785cd97 --- /dev/null +++ b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts @@ -0,0 +1,15 @@ +import { io, Socket } from "socket.io-client"; + +let sharedSocket: Socket | null = null; + +// Singleton pattern to create a single socket connection for the entire application.. +export function getSharedSocket(): Socket { + if (!sharedSocket) { + // eslint-disable-next-line no-warning-comments + // TODO: Add support for user provided domain name (i.e. io("https://server-domain.com")). + // Implementation could involve parsing server .env file and moving server .env to a + // common folder. + sharedSocket = io(); + } + return sharedSocket; +} \ No newline at end of file diff --git a/components/log-viewer-webui/common/index.ts b/components/log-viewer-webui/common/index.ts index 2e4c07f846..73d4923469 100644 --- a/components/log-viewer-webui/common/index.ts +++ b/components/log-viewer-webui/common/index.ts @@ -50,7 +50,7 @@ type ClientToServerEvents = { */ interface ServerToClientEvents { // eslint-disable-next-line no-warning-comments - // TODO: Consider replace this with `collection::find::update${number}`, which will + // TODO: Consider replacing this with `collection::find::update${number}`, which will // limit callbacks being triggered in the client to their respective query IDs. "collection::find::update": (respArgs: { queryId: QueryId; From d83db22d0c59ca581ac53e6d1b437f813cb73faf Mon Sep 17 00:00:00 2001 From: Marco Date: Fri, 9 May 2025 19:18:59 +0000 Subject: [PATCH 08/12] latest --- .../src/api/socket/MongoCollectionSocket.ts | 8 +++--- .../client/src/api/socket/SocketSingleton.ts | 26 +++++++++++++++---- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts index 8fab73259c..1a7ba5b1b1 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts @@ -2,13 +2,10 @@ import { ClientToServerEvents, ServerToClientEvents, } from "@common/index.js"; -import { - Socket, -} from "socket.io-client"; - -import {getSharedSocket} from "./SocketSingleton.js"; +import {Socket} from "socket.io-client"; import {MongoCursorSocket} from "./MongoCursorSocket.js"; +import {getSharedSocket} from "./SocketSingleton.js"; /** @@ -17,6 +14,7 @@ import {MongoCursorSocket} from "./MongoCursorSocket.js"; */ class MongoCollectionSocket { collectionName: string; + private socket: Socket; /** diff --git a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts index f4f785cd97..9bdccd88ed 100644 --- a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts +++ b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts @@ -1,9 +1,22 @@ -import { io, Socket } from "socket.io-client"; +import { + ClientToServerEvents, + ServerToClientEvents, +} from "@common/index.js"; +import { + io, + Socket, +} from "socket.io-client"; -let sharedSocket: Socket | null = null; -// Singleton pattern to create a single socket connection for the entire application.. -export function getSharedSocket(): Socket { +let sharedSocket: Socket | null = null; + +/** + * Returns the shared Socket.io instance for the application. + * Creates a new connection if one doesn't exist yet. + * + * @return The shared Socket.io instance + */ +const getSharedSocket = (): Socket => { if (!sharedSocket) { // eslint-disable-next-line no-warning-comments // TODO: Add support for user provided domain name (i.e. io("https://server-domain.com")). @@ -11,5 +24,8 @@ export function getSharedSocket(): Socket { // common folder. sharedSocket = io(); } + return sharedSocket; -} \ No newline at end of file +}; + +export {getSharedSocket}; From 8e3c0340b7d160e27adee240e1e92938f8e2a18c Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 12 May 2025 21:32:47 +0000 Subject: [PATCH 09/12] latest --- .../client/src/api/socket/MongoCollectionSocket.ts | 6 +++--- .../client/src/api/socket/SocketSingleton.ts | 12 +++++++----- components/log-viewer-webui/client/vite.config.ts | 7 ++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts index 1a7ba5b1b1..34943c35b2 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts @@ -15,7 +15,7 @@ import {getSharedSocket} from "./SocketSingleton.js"; class MongoCollectionSocket { collectionName: string; - private socket: Socket; + #socket: Socket; /** * Initalizes socket connection to a MongoDB collection on the server. @@ -23,7 +23,7 @@ class MongoCollectionSocket { * @param collectionName */ constructor (collectionName: string) { - this.socket = getSharedSocket(); + this.#socket = getSharedSocket(); this.collectionName = collectionName; console.log(`MongoDB collection:${collectionName} initialized.`); } @@ -38,7 +38,7 @@ class MongoCollectionSocket { find (query: object, options: object) { return new MongoCursorSocket( - this.socket, + this.#socket, this.collectionName, query, options, diff --git a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts index 9bdccd88ed..8a5df3ea38 100644 --- a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts +++ b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts @@ -6,15 +6,17 @@ import { io, Socket, } from "socket.io-client"; +import { + Nullable, +} from "../../typings/common"; - -let sharedSocket: Socket | null = null; +let sharedSocket: Nullable> = null; /** - * Returns the shared Socket.io instance for the application. - * Creates a new connection if one doesn't exist yet. + * Returns the shared Socket.io instance for the application. Creates a new connection if one + * doesn't exist yet. * - * @return The shared Socket.io instance + * @return The shared Socket.io instance. */ const getSharedSocket = (): Socket => { if (!sharedSocket) { diff --git a/components/log-viewer-webui/client/vite.config.ts b/components/log-viewer-webui/client/vite.config.ts index 8dbb6586bb..f3bdc9cd56 100644 --- a/components/log-viewer-webui/client/vite.config.ts +++ b/components/log-viewer-webui/client/vite.config.ts @@ -6,11 +6,12 @@ import tsconfigPaths from "vite-tsconfig-paths"; // https://vite.dev/config/ export default defineConfig({ base: "./", - plugins: [react(), - tsconfigPaths()], + plugins: [ + react(), + tsconfigPaths() + ], publicDir: "public", server: { - host: "0.0.0.0", port: 8080, proxy: { "/query/": { From 3fa5132c160f5f1aade73e60d90ba60f75f315cd Mon Sep 17 00:00:00 2001 From: Marco Date: Mon, 12 May 2025 21:56:51 +0000 Subject: [PATCH 10/12] latest --- .../log-viewer-webui/client/package-lock.json | 41 +++++++++++++++++++ .../log-viewer-webui/client/package.json | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/components/log-viewer-webui/client/package-lock.json b/components/log-viewer-webui/client/package-lock.json index 78fdccfaa8..b1408ded38 100644 --- a/components/log-viewer-webui/client/package-lock.json +++ b/components/log-viewer-webui/client/package-lock.json @@ -19,6 +19,9 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.4.1", + "react-syntax-highlighter": "^15.6.1", + "socket.io-client": "^4.8.1", + "vite-tsconfig-paths": "^5.1.4", "zustand": "^5.0.3" }, "devDependencies": { @@ -8710,6 +8713,44 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "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/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/components/log-viewer-webui/client/package.json b/components/log-viewer-webui/client/package.json index 0d91ddb3d4..2f2488cb69 100644 --- a/components/log-viewer-webui/client/package.json +++ b/components/log-viewer-webui/client/package.json @@ -24,9 +24,9 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "^7.4.1", + "react-syntax-highlighter": "^15.6.1", "socket.io-client": "^4.8.1", "vite-tsconfig-paths": "^5.1.4", - "react-syntax-highlighter": "^15.6.1", "zustand": "^5.0.3" }, "devDependencies": { From 1e35e7f880bd6ef973e31924ad8c096b9e00583a Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 13 May 2025 14:23:07 +0000 Subject: [PATCH 11/12] fix lint --- .../client/src/api/socket/SocketSingleton.ts | 6 +++--- components/log-viewer-webui/client/vite.config.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts index 8a5df3ea38..77aa07be60 100644 --- a/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts +++ b/components/log-viewer-webui/client/src/api/socket/SocketSingleton.ts @@ -6,9 +6,9 @@ import { io, Socket, } from "socket.io-client"; -import { - Nullable, -} from "../../typings/common"; + +import {Nullable} from "../../typings/common"; + let sharedSocket: Nullable> = null; diff --git a/components/log-viewer-webui/client/vite.config.ts b/components/log-viewer-webui/client/vite.config.ts index f3bdc9cd56..f3e6c5d260 100644 --- a/components/log-viewer-webui/client/vite.config.ts +++ b/components/log-viewer-webui/client/vite.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ base: "./", plugins: [ react(), - tsconfigPaths() + tsconfigPaths(), ], publicDir: "public", server: { From 6834f5f4fcb3c09026a9a878e31b261d31a5ec3c Mon Sep 17 00:00:00 2001 From: Marco Date: Tue, 13 May 2025 19:41:50 +0000 Subject: [PATCH 12/12] make private --- .../client/src/api/socket/MongoCollectionSocket.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts index 34943c35b2..ef95a9fb2a 100644 --- a/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts +++ b/components/log-viewer-webui/client/src/api/socket/MongoCollectionSocket.ts @@ -13,7 +13,7 @@ import {getSharedSocket} from "./SocketSingleton.js"; * query the collection. */ class MongoCollectionSocket { - collectionName: string; + #collectionName: string; #socket: Socket; @@ -24,7 +24,7 @@ class MongoCollectionSocket { */ constructor (collectionName: string) { this.#socket = getSharedSocket(); - this.collectionName = collectionName; + this.#collectionName = collectionName; console.log(`MongoDB collection:${collectionName} initialized.`); } @@ -39,7 +39,7 @@ class MongoCollectionSocket { find (query: object, options: object) { return new MongoCursorSocket( this.#socket, - this.collectionName, + this.#collectionName, query, options, );