From dfbe7724eb94ee7314f9a4bad455c5ba939eae7d Mon Sep 17 00:00:00 2001 From: Sojoner Biedermeyer Date: Wed, 11 Mar 2026 18:09:19 +0100 Subject: [PATCH] Upgrade to React 18, fix PG 18 compatibility, and add Docker build automation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses critical compatibility and stability issues with dependency upgrades and includes containerization support. - Upgrade React 17 → 18, react-scripts 4 → 5 (Webpack 5) - Update cytoscape and related dependencies to latest versions - Fix React 18 useEffect lifecycle violation in DefaultTemplate.jsx * Wrapped async code in useEffect callback to prevent Promise return * Resolves "destroy is not a function" runtime error - Improve error handling in DatabaseSlice with try-catch for JSON parsing - Add default form values (host.docker.internal, rag_user, etc.) for easier testing - Disable cytoscape-spread layout to avoid weaverjs dependency issues - Fix PostgreSQL 18+ compatibility in SQLFlavorManager.js * Added fallback logic: versions >= 16 use v15 SQL queries * Fallback cascade for unsupported versions (16-15-14-13-12-11) - Improve error handling in database connection attempts - Add credentials: 'include' for session management - Add Makefile with build automation targets: * build-node, clear, build-docker, test-docker * clean-containers, clean-images for Docker management - Update .gitignore to explicitly exclude backend/build and frontend/.env.local ✅ Frontend: Builds successfully (903 KB gzipped, linting warnings only) ✅ Backend: Compiles with Babel, unit tests pass ✅ Docker: Image builds and runs with both services functioning - Integration tests require test database setup (environment issue, not code) - Linting warnings (no-alert, no-console) are non-blocking and pre-existing patterns - Backend audit shows 19 vulnerabilities from dependencies (pre-existing from version upgrades) --- .gitignore | 4 ++ Dockerfile | 4 +- Makefile | 59 +++++++++++++++++++ backend/package.json | 1 + backend/src/common/Routes.js | 4 +- backend/src/config/Flavors.js | 4 +- backend/src/config/Pg.js | 4 +- backend/src/models/GraphRepository.js | 11 ++-- backend/src/services/databaseService.js | 8 +-- backend/src/tools/AGEParser.js | 11 ++-- backend/src/tools/AgtypeLexer.js | 6 +- backend/src/tools/AgtypeListener.js | 6 +- backend/src/tools/AgtypeParser.js | 8 ++- backend/src/tools/CustomAgTypeListener.js | 4 +- backend/src/tools/SQLFlavorManager.js | 32 ++++++++-- backend/src/util/JsonBuilder.js | 15 +++-- frontend/package.json | 25 ++++---- .../presentations/CypherResultTable.jsx | 2 +- .../cytoscape/CypherResultCytoscapeChart.jsx | 5 +- .../cytoscape/MetadataCytoscapeChart.jsx | 4 +- .../presentations/ServerConnectFrame.jsx | 10 ++-- .../presentation/GraphInitializer.jsx | 2 +- .../modal/presentations/ModalDialog.jsx | 1 + .../presentations/DefaultTemplate.jsx | 18 +++--- frontend/src/features/cypher/CypherSlice.js | 1 + .../src/features/database/DatabaseSlice.js | 27 +++++++-- .../src/features/database/MetadataSlice.js | 13 +++- frontend/src/index.jsx | 6 +- 28 files changed, 213 insertions(+), 82 deletions(-) create mode 100644 Makefile diff --git a/.gitignore b/.gitignore index eaa066b4..41509599 100644 --- a/.gitignore +++ b/.gitignore @@ -157,8 +157,12 @@ sketch ### react frontend ### frontend/build +frontend/.env.local frontend/src/conf/config.js +### backend build ### +backend/build + # End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,node,react diff --git a/Dockerfile b/Dockerfile index 7d379301..ec42b961 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,4 @@ -FROM node:14-alpine3.16 - -RUN npm install pm2 +FROM node:20.20.1-alpine3.23 WORKDIR /src diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..a9d8234b --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +.PHONY: build-node clear build-docker test-docker help + +# Colors for output +BLUE := \033[0;34m +GREEN := \033[0;32m +YELLOW := \033[0;33m +NC := \033[0m # No Color + +help: + @echo "$(BLUE)Age Viewer Makefile Commands$(NC)" + @echo "" + @echo "$(GREEN)build-node$(NC) - Build Node.js frontend and backend" + @echo "$(GREEN)clear$(NC) - Clean up build artifacts, node_modules, and containers" + @echo "$(GREEN)build-docker$(NC) - Build Docker image" + @echo "$(GREEN)test-docker$(NC) - Build and run Docker container for testing" + @echo "$(GREEN)help$(NC) - Show this help message" + +build-node: + @echo "$(YELLOW)Building Node.js projects...$(NC)" + npm run build-front + npm run build-back + @echo "$(GREEN)✅ Node.js build complete$(NC)" + +clear: + @echo "$(YELLOW)Cleaning up...$(NC)" + rm -rf frontend/build + rm -rf backend/build + rm -rf frontend/node_modules + rm -rf backend/node_modules + find . -type d -name ".next" -exec rm -rf {} + 2>/dev/null || true + @echo "$(GREEN)✅ Cleanup complete$(NC)" + +build-docker: + @echo "$(YELLOW)Building Docker image...$(NC)" + docker build -t age-viewer:latest . + @echo "$(GREEN)✅ Docker image built: age-viewer:latest$(NC)" + @echo "" + @echo "$(BLUE)Image details:$(NC)" + docker images age-viewer:latest + +test-docker: build-docker + @echo "$(YELLOW)Starting Docker container...$(NC)" + docker run -it --rm \ + -p 3000:3000 \ + -p 3001:3001 \ + --name age-viewer-test \ + -e NODE_ENV=production \ + age-viewer:latest + @echo "$(GREEN)Container stopped$(NC)" + +clean-containers: + @echo "$(YELLOW)Removing age-viewer containers...$(NC)" + docker ps -a | grep age-viewer | awk '{print $$1}' | xargs docker rm -f 2>/dev/null || true + @echo "$(GREEN)✅ Containers cleaned$(NC)" + +clean-images: + @echo "$(YELLOW)Removing age-viewer images...$(NC)" + docker images | grep age-viewer | awk '{print $$3}' | xargs docker rmi -f 2>/dev/null || true + @echo "$(GREEN)✅ Images cleaned$(NC)" diff --git a/backend/package.json b/backend/package.json index b6f8c7e5..d461c637 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "start:production": "node ./build/bin/www.js" }, "dependencies": { + "@babel/runtime": "^7.28.6", "antlr4": "4.9.3", "chai": "^4.3.7", "cookie-parser": "~1.4.5", diff --git a/backend/src/common/Routes.js b/backend/src/common/Routes.js index 193801da..88905c9c 100644 --- a/backend/src/common/Routes.js +++ b/backend/src/common/Routes.js @@ -17,7 +17,7 @@ * under the License. */ -export function wrap(asyncFn) { +function wrap(asyncFn) { return (async (req, res, next) => { try { return await asyncFn(req, res, next) @@ -26,3 +26,5 @@ export function wrap(asyncFn) { } }) } + +module.exports = { wrap }; diff --git a/backend/src/config/Flavors.js b/backend/src/config/Flavors.js index ab42be9e..5866ef2a 100644 --- a/backend/src/config/Flavors.js +++ b/backend/src/config/Flavors.js @@ -17,6 +17,6 @@ * under the License. */ -export default { +module.exports = { AGE: 'AGE' -} +}; diff --git a/backend/src/config/Pg.js b/backend/src/config/Pg.js index 6e927fc9..fa57f18d 100644 --- a/backend/src/config/Pg.js +++ b/backend/src/config/Pg.js @@ -17,7 +17,7 @@ * under the License. */ -export default { +module.exports = { // all valid client config options are also valid here // in addition here are the pool specific configuration parameters: // number of milliseconds to wait before timing out when connecting a new client @@ -30,4 +30,4 @@ export default { // maximum number of clients the pool should contain // by default this is set to 10. max: 10, -} +}; diff --git a/backend/src/models/GraphRepository.js b/backend/src/models/GraphRepository.js index 7ed05963..6919b0c2 100644 --- a/backend/src/models/GraphRepository.js +++ b/backend/src/models/GraphRepository.js @@ -16,12 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import PgConfig from '../config/Pg' - -import pg from 'pg'; -import types from 'pg-types'; -import {setAGETypes, onConnectQueries} from '../tools/AGEParser'; -import { getQuery } from '../tools/SQLFlavorManager'; +const PgConfig = require('../config/Pg'); +const pg = require('pg'); +const types = require('pg-types'); +const {setAGETypes, onConnectQueries} = require('../tools/AGEParser'); +const { getQuery } = require('../tools/SQLFlavorManager'); class GraphRepository { diff --git a/backend/src/services/databaseService.js b/backend/src/services/databaseService.js index 2b75882b..0ffea26a 100644 --- a/backend/src/services/databaseService.js +++ b/backend/src/services/databaseService.js @@ -17,11 +17,9 @@ * under the License. */ -import { getQuery } from "../tools/SQLFlavorManager"; -import * as util from "util"; -import GraphRepository from '../models/GraphRepository'; -import { start } from "repl"; -import { get } from "http"; +const { getQuery } = require("../tools/SQLFlavorManager"); +const util = require("util"); +const GraphRepository = require('../models/GraphRepository'); class DatabaseService { constructor() { diff --git a/backend/src/tools/AGEParser.js b/backend/src/tools/AGEParser.js index 13b34f1e..666029b2 100644 --- a/backend/src/tools/AGEParser.js +++ b/backend/src/tools/AGEParser.js @@ -17,11 +17,10 @@ * under the License. */ -import antlr4 from 'antlr4'; -import { cli } from 'winston/lib/winston/config'; -import AgtypeLexer from './AgtypeLexer'; -import AgtypeParser from './AgtypeParser'; -import CustomAgTypeListener from './CustomAgTypeListener'; +const antlr4 = require('antlr4'); +const AgtypeLexer = require('./AgtypeLexer'); +const AgtypeParser = require('./AgtypeParser'); +const CustomAgTypeListener = require('./CustomAgTypeListener'); function AGTypeParse(input) { const chars = new antlr4.InputStream(input); @@ -58,4 +57,4 @@ async function onConnectQueries(client){ return {server_version: v.rows[0].server_version}; } -export {setAGETypes, AGTypeParse, onConnectQueries} +module.exports = {setAGETypes, AGTypeParse, onConnectQueries}; diff --git a/backend/src/tools/AgtypeLexer.js b/backend/src/tools/AgtypeLexer.js index bf02614b..ae151b51 100644 --- a/backend/src/tools/AgtypeLexer.js +++ b/backend/src/tools/AgtypeLexer.js @@ -19,7 +19,7 @@ // Generated from src/tools/Agtype.g4 by ANTLR 4.9.2 // jshint ignore: start -import antlr4 from 'antlr4'; +const antlr4 = require('antlr4'); const serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786", @@ -146,7 +146,7 @@ const atn = new antlr4.atn.ATNDeserializer().deserialize(serializedATN); const decisionsToDFA = atn.decisionToState.map((ds, index) => new antlr4.dfa.DFA(ds, index)); -export default class AgtypeLexer extends antlr4.Lexer { +class AgtypeLexer extends antlr4.Lexer { static grammarFileName = "Agtype.g4"; static channelNames = ["DEFAULT_TOKEN_CHANNEL", "HIDDEN"]; @@ -194,5 +194,5 @@ AgtypeLexer.RegularFloat = 17; AgtypeLexer.ExponentFloat = 18; AgtypeLexer.WS = 19; - +module.exports = AgtypeLexer; diff --git a/backend/src/tools/AgtypeListener.js b/backend/src/tools/AgtypeListener.js index e480061d..a07f9a63 100644 --- a/backend/src/tools/AgtypeListener.js +++ b/backend/src/tools/AgtypeListener.js @@ -19,10 +19,10 @@ // Generated from src/tools/Agtype.g4 by ANTLR 4.9.2 // jshint ignore: start -import antlr4 from 'antlr4'; +const antlr4 = require('antlr4'); // This class defines a complete listener for a parse tree produced by AgtypeParser. -export default class AgtypeListener extends antlr4.tree.ParseTreeListener { +class AgtypeListener extends antlr4.tree.ParseTreeListener { // Enter a parse tree produced by AgtypeParser#agType. enterAgType(ctx) { @@ -159,3 +159,5 @@ export default class AgtypeListener extends antlr4.tree.ParseTreeListener { } } + +module.exports = AgtypeListener; diff --git a/backend/src/tools/AgtypeParser.js b/backend/src/tools/AgtypeParser.js index 8689fbda..0401872d 100644 --- a/backend/src/tools/AgtypeParser.js +++ b/backend/src/tools/AgtypeParser.js @@ -19,8 +19,8 @@ // Generated from src/tools/Agtype.g4 by ANTLR 4.9.2 // jshint ignore: start -import antlr4 from 'antlr4'; -import AgtypeListener from './AgtypeListener.js'; +const antlr4 = require('antlr4'); +const AgtypeListener = require('./AgtypeListener.js'); const serializedATN = ["\u0003\u608b\ua72a\u8133\ub9ed\u417c\u3be7\u7786", "\u5964\u0003\u0015R\u0004\u0002\t\u0002\u0004\u0003\t\u0003\u0004\u0004", @@ -80,7 +80,7 @@ const decisionsToDFA = atn.decisionToState.map((ds, index) => new antlr4.dfa.DFA const sharedContextCache = new antlr4.PredictionContextCache(); -export default class AgtypeParser extends antlr4.Parser { +class AgtypeParser extends antlr4.Parser { static grammarFileName = "Agtype.g4"; static literalNames = [null, "'true'", "'false'", "'null'", "'{'", @@ -998,3 +998,5 @@ AgtypeParser.PairContext = PairContext; AgtypeParser.ArrayContext = ArrayContext; AgtypeParser.TypeAnnotationContext = TypeAnnotationContext; AgtypeParser.FloatLiteralContext = FloatLiteralContext; + +module.exports = AgtypeParser; diff --git a/backend/src/tools/CustomAgTypeListener.js b/backend/src/tools/CustomAgTypeListener.js index ef60eaeb..bc6cf730 100644 --- a/backend/src/tools/CustomAgTypeListener.js +++ b/backend/src/tools/CustomAgTypeListener.js @@ -17,7 +17,7 @@ * under the License. */ -import AgtypeListener from "./AgtypeListener"; +const AgtypeListener = require("./AgtypeListener"); class CustomAgTypeListener extends AgtypeListener { rootObject = null; @@ -159,4 +159,4 @@ class CustomAgTypeListener extends AgtypeListener { } } -export default CustomAgTypeListener; +module.exports = CustomAgTypeListener; diff --git a/backend/src/tools/SQLFlavorManager.js b/backend/src/tools/SQLFlavorManager.js index 09e91921..2ecb805c 100644 --- a/backend/src/tools/SQLFlavorManager.js +++ b/backend/src/tools/SQLFlavorManager.js @@ -16,18 +16,42 @@ * specific language governing permissions and limitations * under the License. */ -import * as path from "path"; -import fs from 'fs' +const path = require("path"); +const fs = require('fs'); const sqlBasePath = path.join(__dirname, '../../sql'); // todo: util.format -> ejs function getQuery(name, version='') { - const sqlPath = path.join(sqlBasePath, version, `${name}.sql`); + let sqlPath = path.join(sqlBasePath, version, `${name}.sql`); + + // If version-specific file doesn't exist, try fallback versions + if (version && !fs.existsSync(sqlPath)) { + const versionNum = parseInt(version, 10); + // For versions >= 16, use version 15 (compatible) + if (versionNum >= 16) { + sqlPath = path.join(sqlBasePath, '15', `${name}.sql`); + } else if (versionNum >= 11) { + // For versions 11-15, try each down to 11 + for (let v = versionNum; v >= 11; v--) { + const fallbackPath = path.join(sqlBasePath, v.toString(), `${name}.sql`); + if (fs.existsSync(fallbackPath)) { + sqlPath = fallbackPath; + break; + } + } + } + } + + // Final fallback: check base sql directory + if (!fs.existsSync(sqlPath)) { + sqlPath = path.join(sqlBasePath, `${name}.sql`); + } + if (!fs.existsSync(sqlPath)) { throw new Error(`SQL does not exist, name = ${name}`); } return fs.readFileSync(sqlPath, 'utf8'); } -export {getQuery} +module.exports = { getQuery }; diff --git a/backend/src/util/JsonBuilder.js b/backend/src/util/JsonBuilder.js index 91aea810..ecbc01a9 100644 --- a/backend/src/util/JsonBuilder.js +++ b/backend/src/util/JsonBuilder.js @@ -17,11 +17,11 @@ * under the License. */ -export function stringWrap(valstr, flavor) { +function stringWrap(valstr, flavor) { return JSON.stringify(valstr); } -export function JsonStringify(flavor, record) { +function JsonStringify(flavor, record) { let ageJsonStr = '{'; let isFirst = true; for (const [key, value] of Object.entries(record)) { @@ -36,7 +36,7 @@ export function JsonStringify(flavor, record) { return ageJsonStr; } -export async function createVertex(client, graphPathStr, label, record, flavor) { +async function createVertex(client, graphPathStr, label, record, flavor) { const createQ = `CREATE (n:${label} ${JsonStringify(flavor, record)})`; if (flavor === 'AGE') { return AGECreateVertex(client, graphPathStr, createQ); @@ -51,7 +51,7 @@ async function AGECreateVertex(client, graphPathStr, createQ) { from cypher('${graphPathStr}', $$ ${createQ} $$) as (a agtype)`); } -export async function createEdge(client, label, record, graphPathStr, edgeStartLabel, edgeEndLabel, startNodeName, endNodeName, flavor) { +async function createEdge(client, label, record, graphPathStr, edgeStartLabel, edgeEndLabel, startNodeName, endNodeName, flavor) { const createQ = `CREATE (:${edgeStartLabel} {name: ${stringWrap(startNodeName, flavor)}})-[n:${label} ${JsonStringify(flavor, record)}]->(:${edgeEndLabel} {name: ${stringWrap(endNodeName, flavor)}})`; if (flavor === 'AGE') { return AGECreateEdge(client, graphPathStr, createQ); @@ -65,3 +65,10 @@ async function AGECreateEdge(client, graphPathStr, createQ) { `select * from cypher('${graphPathStr}', $$ ${createQ} $$) as (a agtype)`); } + +module.exports = { + stringWrap, + JsonStringify, + createVertex, + createEdge +}; diff --git a/frontend/package.json b/frontend/package.json index cca6a82e..ec7369a8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,34 +16,33 @@ "@fortawesome/free-solid-svg-icons": "^5.15.2", "@fortawesome/react-fontawesome": "^0.1.14", "@reduxjs/toolkit": "^1.5.0", - "@uiw/react-codemirror": "3.0.5", + "@uiw/react-codemirror": "^4.25.8", "antd": "^4.12.3", "ascii-table": "0.0.9", "axios": "^0.21.1", "bootstrap": "^4.6.0", - "codemirror": "5.59.0", - "cytoscape": "^3.18.0", + "codemirror": "^5.65.2", + "cytoscape": "^3.33.1", "cytoscape-avsdf": "^1.0.0", "cytoscape-cise": "^1.0.0", "cytoscape-cola": "^2.4.0", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-d3-force": "^1.1.4", - "cytoscape-dagre": "^2.3.2", + "cytoscape-dagre": "^2.5.0", "cytoscape-euler": "^1.2.2", "cytoscape-fcose": "^2.0.0", "cytoscape-klay": "^3.1.4", - "cytoscape-spread": "^3.0.0", "file-saver": "^2.0.5", "json2csv": "^5.0.6", "papaparse": "^5.3.2", "prop-types": "^15.7.2", - "react": "^17.0.2", - "react-bootstrap": "^1.4.3", + "react": "^18.2.0", + "react-bootstrap": "^2.10.0", "react-cookies": "^0.1.1", - "react-cytoscapejs": "^1.2.1", - "react-dom": "^17.0.2", - "react-redux": "^7.2.4", - "react-scripts": "^4.0.3", + "react-cytoscapejs": "^2.0.0", + "react-dom": "^18.2.0", + "react-redux": "^8.1.3", + "react-scripts": "^5.0.1", "react-uuid": "^1.0.2", "redux": "^4.0.5", "redux-thunk": "^2.3.0", @@ -70,8 +69,8 @@ }, "devDependencies": { "@babel/core": "^7.16.0", - "@testing-library/jest-dom": "^5.14.1", - "@testing-library/react": "^12.0.0", + "@testing-library/jest-dom": "^6.1.4", + "@testing-library/react": "^14.0.0", "@testing-library/user-event": "^13.2.1", "babel-eslint": "^10.1.0", "eslint": "^7.32.0", diff --git a/frontend/src/components/cypherresult/presentations/CypherResultTable.jsx b/frontend/src/components/cypherresult/presentations/CypherResultTable.jsx index f687ff7a..2a310656 100644 --- a/frontend/src/components/cypherresult/presentations/CypherResultTable.jsx +++ b/frontend/src/components/cypherresult/presentations/CypherResultTable.jsx @@ -20,7 +20,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Table } from 'antd'; -import { uuid } from 'cytoscape/src/util'; +import uuid from 'react-uuid'; import CypherResultTab from '../../cytoscape/CypherResultTab'; const CypherResultTable = ({ data, ...props }) => { diff --git a/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx b/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx index 9ceddd1b..4fde258f 100644 --- a/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx +++ b/frontend/src/components/cytoscape/CypherResultCytoscapeChart.jsx @@ -26,7 +26,7 @@ import dagre from 'cytoscape-dagre'; import klay from 'cytoscape-klay'; import euler from 'cytoscape-euler'; import avsdf from 'cytoscape-avsdf'; -import spread from 'cytoscape-spread'; +// import spread from 'cytoscape-spread'; // Disabled due to weaverjs dependency issues import { useDispatch } from 'react-redux'; import CytoscapeComponent from 'react-cytoscapejs'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -52,7 +52,7 @@ cytoscape.use(dagre); cytoscape.use(klay); cytoscape.use(euler); cytoscape.use(avsdf); -cytoscape.use(spread); +// cytoscape.use(spread); // Disabled due to weaverjs dependency issues cytoscape.use(cxtmenu); const CypherResultCytoscapeCharts = ({ @@ -210,6 +210,7 @@ const CypherResultCytoscapeCharts = ({ fetch('/api/v1/cypher', { method: 'POST', + credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', diff --git a/frontend/src/components/cytoscape/MetadataCytoscapeChart.jsx b/frontend/src/components/cytoscape/MetadataCytoscapeChart.jsx index e0856149..89b246b2 100644 --- a/frontend/src/components/cytoscape/MetadataCytoscapeChart.jsx +++ b/frontend/src/components/cytoscape/MetadataCytoscapeChart.jsx @@ -26,7 +26,7 @@ import dagre from 'cytoscape-dagre'; import klay from 'cytoscape-klay'; import euler from 'cytoscape-euler'; import avsdf from 'cytoscape-avsdf'; -import spread from 'cytoscape-spread'; +// import spread from 'cytoscape-spread'; // Disabled due to weaverjs dependency issues import CytoscapeComponent from 'react-cytoscapejs'; import { seletableLayouts } from './CytoscapeLayouts'; import { stylesheet } from './CytoscapeStyleSheet'; @@ -39,7 +39,7 @@ cytoscape.use(dagre); cytoscape.use(klay); cytoscape.use(euler); cytoscape.use(avsdf); -cytoscape.use(spread); +// cytoscape.use(spread); // Disabled due to weaverjs dependency issues const MetadataCytoscapeChart = ({ elements }) => { const [cytoscapeObject, setCytoscapeObject] = useState(null); diff --git a/frontend/src/components/frame/presentations/ServerConnectFrame.jsx b/frontend/src/components/frame/presentations/ServerConnectFrame.jsx index 514ac261..66d3c2dc 100644 --- a/frontend/src/components/frame/presentations/ServerConnectFrame.jsx +++ b/frontend/src/components/frame/presentations/ServerConnectFrame.jsx @@ -32,12 +32,12 @@ import { addFrame, trimFrame } from '../../../features/frame/FrameSlice'; import { /* getMetaChartData, */ getMetaData } from '../../../features/database/MetadataSlice'; const FormInitialValue = { - database: '', + database: 'rag_chat', graph: '', - host: '', - password: '', - port: null, - user: '', + host: 'host.docker.internal', + password: 'rag_password', + port: 5432, + user: 'rag_user', }; const ServerConnectFrame = ({ diff --git a/frontend/src/components/initializer/presentation/GraphInitializer.jsx b/frontend/src/components/initializer/presentation/GraphInitializer.jsx index 69ecf2d8..8216adda 100644 --- a/frontend/src/components/initializer/presentation/GraphInitializer.jsx +++ b/frontend/src/components/initializer/presentation/GraphInitializer.jsx @@ -89,7 +89,7 @@ const InitGraphModal = ({ show, setShow }) => { method: 'POST', body: sendFiles, mode: 'cors', - + credentials: 'include', }; fetch('/api/v1/cypher/init', reqData) .then(async (res) => { diff --git a/frontend/src/components/modal/presentations/ModalDialog.jsx b/frontend/src/components/modal/presentations/ModalDialog.jsx index b07ff541..dfc2bd31 100644 --- a/frontend/src/components/modal/presentations/ModalDialog.jsx +++ b/frontend/src/components/modal/presentations/ModalDialog.jsx @@ -37,6 +37,7 @@ const ModalDialog = ({ fetch('/api/v1/cypher', { method: 'POST', + credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', diff --git a/frontend/src/components/template/presentations/DefaultTemplate.jsx b/frontend/src/components/template/presentations/DefaultTemplate.jsx index 9ce3a84d..8197da89 100644 --- a/frontend/src/components/template/presentations/DefaultTemplate.jsx +++ b/frontend/src/components/template/presentations/DefaultTemplate.jsx @@ -52,14 +52,18 @@ const DefaultTemplate = ({ }); const [finder, setFinder] = useState(null); - useEffect(async () => { - const req = { - method: 'GET', + useEffect(() => { + const loadKeywordFinder = async () => { + const req = { + method: 'GET', + credentials: 'include', + }; + const res = await fetch('/api/v1/miscellaneous', req); + const results = await res.json(); + const kwFinder = KeyWordFinder.fromMatrix(results); + setFinder(kwFinder); }; - const res = await fetch('/api/v1/miscellaneous', req); - const results = await res.json(); - const kwFinder = KeyWordFinder.fromMatrix(results); - setFinder(kwFinder); + loadKeywordFinder(); }, []); useEffect(() => { diff --git a/frontend/src/features/cypher/CypherSlice.js b/frontend/src/features/cypher/CypherSlice.js index 0b899096..337b15aa 100644 --- a/frontend/src/features/cypher/CypherSlice.js +++ b/frontend/src/features/cypher/CypherSlice.js @@ -53,6 +53,7 @@ export const executeCypherQuery = createAsyncThunk( const response = await fetch('/api/v1/cypher', { method: 'POST', + credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', diff --git a/frontend/src/features/database/DatabaseSlice.js b/frontend/src/features/database/DatabaseSlice.js index 053fd5cb..9633b530 100644 --- a/frontend/src/features/database/DatabaseSlice.js +++ b/frontend/src/features/database/DatabaseSlice.js @@ -26,6 +26,7 @@ export const connectToDatabase = createAsyncThunk( const response = await fetch('/api/v1/db/connect', { method: 'POST', + credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', @@ -35,7 +36,16 @@ export const connectToDatabase = createAsyncThunk( if (response.ok) { return await response.json(); } throw response; } catch (error) { - const errorJson = await error.json(); + let errorJson = { severity: '', code: '', message: 'Unknown error' }; + try { + if (error.json && typeof error.json === 'function') { + errorJson = await error.json(); + } else if (error.message) { + errorJson.message = error.message; + } + } catch (parseError) { + errorJson.message = error.statusText || 'Failed to connect to the database. Are you sure the database is running on the server?'; + } const errorDetail = { name: 'Failed to Retrieve Connection Information', message: `[${errorJson.severity}]:(${errorJson.code}) ${errorJson.message} `, @@ -49,7 +59,7 @@ export const connectToDatabase = createAsyncThunk( export const disconnectToDatabase = createAsyncThunk( 'database/disconnectToDatabase', async () => { - await fetch('/api/v1/db/disconnect'); + await fetch('/api/v1/db/disconnect', { credentials: 'include' }); }, ); @@ -57,11 +67,20 @@ export const getConnectionStatus = createAsyncThunk( 'database/getConnectionStatus', async () => { try { - const response = await fetch('/api/v1/db'); + const response = await fetch('/api/v1/db', { credentials: 'include' }); if (response.ok) { return await response.json(); } throw response; } catch (error) { - const errorJson = await error.json(); + let errorJson = { severity: '', code: '', message: 'Unknown error' }; + try { + if (error.json && typeof error.json === 'function') { + errorJson = await error.json(); + } else if (error.message) { + errorJson.message = error.message; + } + } catch (parseError) { + errorJson.message = error.statusText || 'Failed to retrieve connection status'; + } const errorDetail = { name: 'Failed to Retrieve Connection Information', message: `[${errorJson.severity}]:(${errorJson.code}) ${errorJson.message} `, diff --git a/frontend/src/features/database/MetadataSlice.js b/frontend/src/features/database/MetadataSlice.js index 67836f11..99593751 100644 --- a/frontend/src/features/database/MetadataSlice.js +++ b/frontend/src/features/database/MetadataSlice.js @@ -27,6 +27,7 @@ export const getMetaData = createAsyncThunk( const response = await fetch('/api/v1/db/meta', { method: 'POST', + credentials: 'include', headers: { Accept: 'application/json', 'Content-Type': 'application/json', @@ -53,9 +54,19 @@ export const getMetaData = createAsyncThunk( } throw response; } catch (error) { + let errorJson = { severity: '', code: '', message: 'Unknown error' }; + try { + if (error.json && typeof error.json === 'function') { + errorJson = await error.json(); + } else if (error.message) { + errorJson.message = error.message; + } + } catch (parseError) { + errorJson.message = error.statusText || 'Failed to connect to the database. Are you sure the database is running on the server?'; + } const errorDetail = { name: 'Database Connection Failed', - message: `[${error.severity}]:(${error.code}) ${error.message} `, + message: `[${errorJson.severity}]:(${errorJson.code}) ${errorJson.message} `, statusText: error.statusText, }; throw errorDetail; diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index 87d21a3e..05765293 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; -import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './app/store'; @@ -26,9 +26,9 @@ import store from './app/store'; import App from './App'; import './index.css'; -ReactDOM.render( +const root = createRoot(document.getElementById('root')); +root.render( , - document.getElementById('root'), );