From b511f3ee8122bfe8240fa3f425acf2346e8ac239 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 11 Nov 2025 16:23:08 +0000 Subject: [PATCH 01/27] dev: added .user folder to gitignore Developers should use this folder for their personal scripts/files that should not be added to the repository --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dd665b0e..be4ea37c 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ yarn.lock package-lock.json *.log apps/listwebserver/version.yml +.user From 8107dd6f90a15e79b8db9993f998a9a4bccf7e01 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Wed, 12 Nov 2025 13:45:40 +0000 Subject: [PATCH 02/27] dev: added a DevContainer for development purposes --- .devcontainer/Dockerfile | 37 +++++++++++++++++++++++++++++++++ .devcontainer/devcontainer.json | 36 ++++++++++++++++++++++++++++++++ .github/dependabot.yml | 12 +++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .github/dependabot.yml diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..7688537c --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,37 @@ +FROM ubuntu:20.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update -qq && apt-get install -y --no-install-recommends \ + software-properties-common wget apt-utils file zip \ + openssh-client gpg-agent socat rsync \ + make ninja-build git \ + python3 python3-pip python3-venv \ + curl libpcap-dev libssl-dev uuid-dev ffmpeg bzip2 + +RUN apt-get install -y vim nano tmux tmuxinator +RUN git config --global core.editor vim + +# Install conan into a virtualenv to avoid PEP 668 "externally-managed" errors +# Install conan (pin to 1.x series). Use `conan<2` to ensure the legacy 1.x client is installed +RUN python3 -m venv /opt/venv && \ + /opt/venv/bin/python -m pip install --upgrade pip setuptools wheel && \ + /opt/venv/bin/python -m pip install "conan<2" && \ + ln -s /opt/venv/bin/conan /usr/local/bin/conan + +# By default, anything you run in Docker is done as superuser. +# Conan runs some install commands as superuser, and will prepend `sudo` to +# these commands, unless `CONAN_SYSREQUIRES_SUDO=0` is in your env variables. +ENV CONAN_SYSREQUIRES_SUDO=0 +# Some packages request that Conan use the system package manager to install +# a few dependencies. This flag allows Conan to proceed with these installations; +# leaving this flag undefined can cause some installation failures. +ENV CONAN_SYSREQUIRES_MODE=enabled + +# Install CMake 3.19.8 (supports CMP0110 policy) from the official binary installer +ARG CMAKE_VERSION=3.19.8 +RUN wget -O /tmp/cmake-${CMAKE_VERSION}-Linux-x86_64.sh \ + https://cmake.org/files/v3.19/cmake-${CMAKE_VERSION}-Linux-x86_64.sh && \ + chmod +x /tmp/cmake-${CMAKE_VERSION}-Linux-x86_64.sh && \ + /tmp/cmake-${CMAKE_VERSION}-Linux-x86_64.sh --skip-license --prefix=/usr/local && \ + rm -f /tmp/cmake-${CMAKE_VERSION}-Linux-x86_64.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..cdd367de --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,36 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "build": { + "dockerfile": "Dockerfile" + }, + "features": { + "ghcr.io/meaningful-ooo/devcontainer-features/fish": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "version": "latest", + "enableNonRootDocker": "true", + "moby": "true" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "20" + }, + "git": "latest" + }, + "customizations": { + "vscode": { + "settings": { + "cmake.configureOnOpen": true, + "editor.formatOnSave": true + }, + "extensions": [ + "eamodio.gitlens" + ] + } + }, + "workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/${localWorkspaceFolderBasename},type=bind,consistency=delegated", + "runArgs": [ + "--network=host" + ] +} diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..f33a02cd --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for more information: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates +# https://containers.dev/guide/dependabot + +version: 2 +updates: + - package-ecosystem: "devcontainers" + directory: "/" + schedule: + interval: weekly From 72313ef0c0e98769f682660810a07a95ea56e454 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Wed, 12 Nov 2025 15:18:54 +0000 Subject: [PATCH 03/27] Upgraded lerna version to ^9.0.0 Removed useWorkspaces option from lerna.json The "useWorkspaces" option has been removed. By default lerna will resolve your packages using your package manager's workspaces configuration. Alternatively, you can manually provide a list of package globs to be used instead via the "packages" option in lerna.json. Added workspaces to package.json Remove lerna bootstrap, add verbose logs --- apps/gui-v2/package.json | 2 +- lerna.json | 1 - package.json | 19 +++++++++++++++++-- scripts/build_node.sh | 3 +-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json index 7de25910..cca3888a 100644 --- a/apps/gui-v2/package.json +++ b/apps/gui-v2/package.json @@ -22,7 +22,7 @@ "eslint-plugin-prettier": "^3.1.1", "eslint-plugin-react": "^7.14.3", "eslint-plugin-react-hooks": "^1.7.0", - "lerna": "^3.18.1", + "lerna": "^9.0.0", "prettier": "^1.18.2", "react-app-rewired": "^2.1.3", "sass": "1.50.0", diff --git a/lerna.json b/lerna.json index e5567db4..c1ab9eef 100644 --- a/lerna.json +++ b/lerna.json @@ -11,7 +11,6 @@ "apps/gui-v2/packages/*", "apps/gui-v2" ], - "useWorkspaces": false, "npmClient": "yarn", "version": "0.0.0" } diff --git a/package.json b/package.json index eb2de2b9..78c6db80 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,22 @@ "name": "root", "private": true, "devDependencies": { - "lerna": "^4.0.0" + "lerna": "^9.0.0" }, - "version": "0.0.0" + "version": "0.0.0", + "workspaces": [ + "apps/listwebserver", + "apps/capture_probe", + "js/tests", + "js/user-read-only", + "third_party/bisect-core-ts", + "third_party/bisect-core-ts-be", + "third_party/ebu-list-sdk/lib", + "third_party/ebu-list-sdk/demos", + "apps/gui-v2/packages/*", + "apps/gui-v2" + ], + "resolutions": { + "@types/minimatch": "5.1.2" + } } diff --git a/scripts/build_node.sh b/scripts/build_node.sh index f31d8536..6dff95a2 100755 --- a/scripts/build_node.sh +++ b/scripts/build_node.sh @@ -9,7 +9,6 @@ cd $TOP_DIR echo "Bootstrapping..." yarn install -npx lerna bootstrap echo "Building..." npx lerna run build @@ -17,7 +16,7 @@ npx lerna run build echo "Building GUI..." cd $TOP_DIR/apps/gui-v2/ -yarn run build:production +yarn run build:production --verbose echo "Done" From 871024f72ac6f749e938237457041d5d0c8fbf05 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 13:35:50 +0000 Subject: [PATCH 04/27] Use react 18 in gui-v2 --- apps/gui-v2/packages/react-app/package.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apps/gui-v2/packages/react-app/package.json b/apps/gui-v2/packages/react-app/package.json index b3c42e8e..462f0f36 100644 --- a/apps/gui-v2/packages/react-app/package.json +++ b/apps/gui-v2/packages/react-app/package.json @@ -5,6 +5,8 @@ "dependencies": { "@bisect/ebu-list-sdk": "^0.1.0", "bulma": "^0.9.1", + "react": "^18.0.0", + "react-dom": "^18.0.0", "react-circular-progressbar": "^2.0.3", "@ebu-list/translations": "^0.1.0", "fs": "^0.0.1-security", @@ -24,6 +26,10 @@ "test": "react-app-rewired test", "eject": "react-app-rewired eject" }, + "devDependencies": { + "@types/react": "^18.0.1", + "@types/react-dom": "^18.0.0" + }, "peerDependencies": { "@types/query-string": "^6.3.0", "node-polyglot": "^2.4.0", @@ -57,4 +63,4 @@ "luxon": "^1.27.0", "@types/luxon": "^1.26.5" } -} \ No newline at end of file +} From f1495f07eca3e6d67ce02e7575309bcf6b20c35f Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 14:21:17 +0000 Subject: [PATCH 05/27] GUI: Update deps, align react, TS fixes --- apps/gui-v2/package.json | 143 ++++++++++-------- apps/gui-v2/packages/components/package.json | 14 +- .../packages/react-app/config-overrides.js | 14 +- apps/gui-v2/packages/react-app/package.json | 12 +- .../ImagesGallery/ImagesGallery.tsx | 6 +- .../src/pages/Capture/AddSourceModal.tsx | 4 +- .../src/pages/Capture/CaptureContent.tsx | 4 +- .../src/pages/Capture/ConfirmModal.tsx | 5 +- .../src/pages/Capture/EditSourceModal.tsx | 5 +- .../react-app/src/pages/Common/MainPage.tsx | 2 +- .../Dashboard/UploadPcap/UploadModal.tsx | 5 +- .../StreamExplorerPage/Audio/AudioPlayer.tsx | 59 ++++---- .../Audio/AudioPlayerDisplay.tsx | 2 +- .../Video/VideoStreamExplorerDisplay.tsx | 2 +- .../liveSource/useRecoilLiveSourceHandler.tsx | 2 +- .../store/gui/pcaps/useRecoilPcapsHandler.tsx | 2 +- .../useRecoilStreamComparisonHandler.tsx | 2 +- .../store/gui/user/useRecoilUserHandler.tsx | 2 +- .../graphs/dataTransformationLineGraphs.ts | 2 +- apps/gui-v2/tsconfig.json | 6 +- apps/listwebserver/package.json | 10 +- apps/listwebserver/src/analyzers/rtp.ts | 21 +-- package.json | 7 +- 23 files changed, 181 insertions(+), 150 deletions(-) diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json index cca3888a..285c5c0a 100644 --- a/apps/gui-v2/package.json +++ b/apps/gui-v2/package.json @@ -6,87 +6,90 @@ "live": "lerna run live --stream --scope '@ebu-list/gui-v2'", "build:production": "lerna run build:production --stream --scope '@ebu-list/gui-v2'" }, + "version": "1.0.0", "devDependencies": { - "@types/form-data": "^2.5.0", - "@types/node-polyglot": "^2.4.1", - "@types/react-custom-scrollbars": "^4.0.7", - "@types/react-image-gallery": "^1.0.1", - "@types/react-select": "^4.0.15", - "@types/wavesurfer.js": "^3.3.2", - "css-loader": "^5.0.1", - "enzyme-to-json": "^3.4.3", - "eslint-config-airbnb": "^18.0.1", - "eslint-config-prettier": "^6.4.0", - "eslint-plugin-import": "^2.18.2", - "eslint-plugin-jsx-a11y": "^6.2.3", - "eslint-plugin-prettier": "^3.1.1", - "eslint-plugin-react": "^7.14.3", - "eslint-plugin-react-hooks": "^1.7.0", - "lerna": "^9.0.0", - "prettier": "^1.18.2", - "react-app-rewired": "^2.1.3", - "sass": "1.50.0", - "sass-loader": "^10.1.0", - "style-loader": "^2.0.0", - "ts-jest": "^24.1.0" - }, - "dependencies": { - "@tippyjs/react": "^4.2.5", - "@types/enzyme-adapter-react-16": "^1.0.5", - "@types/jest": "24.0.19", - "@types/lodash": "^4.14.176", - "@types/luxon": "^1.26.5", - "@types/node": "12.11.1", + "@tippyjs/react": "^4.2.6", + "@types/enzyme-adapter-react-16": "^1.0.9", + "@types/form-data": "^2.5.2", + "@types/jest": "^30.0.0", + "@types/lodash": "^4.17.20", + "@types/luxon": "^3.7.1", + "@types/node": "^24.10.1", + "@types/node-polyglot": "^2.5.0", "@types/query-string": "^6.3.0", - "@types/react": "^18.0.1", - "@types/react-dom": "18.0.0", - "@types/react-modal": "^3.13.1", + "@types/react": "^18.2.21", + "@types/react-custom-scrollbars": "^4.0.13", + "@types/react-dom": "^18.2.7", + "@types/react-image-gallery": "^1.2.4", + "@types/react-modal": "^3.16.3", "@types/react-router-dom": "^5.3.3", - "@types/recharts": "^1.8.21", - "@types/smpte-timecode": "^1.2.1", - "@types/uuid": "^8.3.1", - "assert": "^2.0.0", + "@types/react-select": "^5.0.1", + "@types/recharts": "^2.0.1", + "@types/smpte-timecode": "^1.2.5", + "@types/uuid": "^11.0.0", + "@types/wavesurfer.js": "^6.0.12", + "css-loader": "^7.1.2", + "enzyme-to-json": "^3.6.2", + "eslint-config-airbnb": "^19.0.4", + "eslint-config-prettier": "^10.1.8", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-prettier": "^5.5.4", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^7.0.1", + "jest": "^30.2.0", + "lerna": "^9.0.1", + "prettier": "^3.6.2", + "react-app-rewired": "^2.2.1", + "sass": "^1.94.0", + "sass-loader": "^16.0.6", + "style-loader": "^4.0.0", + "ts-jest": "^29.4.5" + }, + "dependencies": { + "@wavesurfer/react": "^1.0.11", + "assert": "^2.1.0", "buffer": "^6.0.3", - "csv-parse": "^4.16.0", - "customize-cra": "^0.8.0", - "enzyme": "^3.10.0", - "enzyme-adapter-react-16": "^1.15.1", + "csv-parse": "^6.1.0", + "customize-cra": "^1.0.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.8", "file-loader": "^6.2.0", "fs": "^0.0.1-security", - "history": "^5.0.0", + "history": "^5.3.0", "https-browserify": "^1.0.0", "lodash": "^4.17.21", - "luxon": "^1.27.0", - "node-polyglot": "^2.4.0", + "luxon": "^3.7.2", + "node-polyglot": "^2.6.0", "npm-license-crawler": "^0.2.1", "os": "^0.1.2", "os-browserify": "^0.3.0", "process": "^0.11.10", - "rc-scrollbars": "^1.1.2", - "react": "^18.0.0", - "react-custom-scrollbars-2": "^4.4.0", - "react-dom": "^18.0.0", - "react-dropzone": "^10.2.2", - "react-ga": "^3.3.0", - "react-image-gallery": "^1.0.8", - "react-modal": "^3.14.3", - "react-router-dom": "6.3.0", - "react-scripts": "5.0.0", - "react-select": "^4.3.0", - "react-toastify": "8.2.0", - "recharts": "^2.0.3", - "recoil": "0.7.1", - "rollup": "^2.38.5", - "simplebar-react": "^2.3.6", - "smpte-timecode": "^1.2.3", + "rc-scrollbars": "^1.1.6", + "react": "^18.3.1", + "react-custom-scrollbars-2": "^4.5.0", + "react-dom": "^18.3.1", + "react-dropzone": "^14.3.8", + "react-ga": "^3.3.1", + "react-image-gallery": "^1.4.0", + "react-modal": "^3.16.3", + "react-router-dom": "^6.26.0", + "react-scripts": "^5.0.1", + "react-select": "^5.10.2", + "react-toastify": "^9.1.3", + "recharts": "^3.4.1", + "recoil": "^0.7.7", + "rollup": "^4.53.2", + "simplebar-react": "^3.3.2", + "smpte-timecode": "^1.3.6", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", - "svg-url-loader": "^7.1.1", - "tippy.js": "^6.2.7", - "typescript": "^4.0.3", - "url": "^0.11.0", - "uuid": "^8.3.2", - "wavesurfer.js": "^4.2.0" + "svg-url-loader": "^8.0.0", + "tippy.js": "^6.3.7", + "typescript": "^5.9.3", + "url": "^0.11.4", + "uuid": "^13.0.0", + "wavesurfer.js": "^7.12.1" }, "jest": { "transform": { @@ -102,6 +105,12 @@ "packages/*/src/**/*.{js,jsx,ts,tsx}" ] }, + "resolutions": { + "nth-check": "^2.0.1", + "postcss": "^8.4.31", + "webpack-dev-server": "^5.2.1", + "semver": "^5.7.2" + }, "browserslist": { "production": [ ">0.2%", diff --git a/apps/gui-v2/packages/components/package.json b/apps/gui-v2/packages/components/package.json index f3bcf592..77ae9eea 100644 --- a/apps/gui-v2/packages/components/package.json +++ b/apps/gui-v2/packages/components/package.json @@ -7,22 +7,22 @@ "@types/jest": "24.0.19", "@types/node": "12.11.1", "@types/react": "^18.0.1", - "@types/react-dom": "16.9.2", + "@types/react-dom": "^18.0.0", "customize-cra": "^0.8.0", "enzyme": "^3.10.0", - "react": "^16.10.2", - "react-dom": "^16.10.2", - "react-scripts": "5.0.0", - "react-dropzone": "^10.2.2", + "react": "^18.0.2", + "react-dom": "^18.2.7", + "react-scripts": "^5.0.1", + "react-dropzone": "^14.3.8", "typescript": "^4.0.3", - "recharts": "^2.0.3", + "recharts": ">=2.0.3", "tippy.js": "^6.2.7", "react-image-gallery": "^1.0.8", "@types/react-image-gallery": "^1.0.1", "react-custom-scrollbars": "^4.2.1", "@types/react-custom-scrollbars": "^4.0.7", "file-loader": "^6.2.0", - "svg-url-loader": "^7.1.1", + "svg-url-loader": ">=7.1.1", "rc-scrollbars": "^1.1.2", "react-toastify": "^7.0.4" }, diff --git a/apps/gui-v2/packages/react-app/config-overrides.js b/apps/gui-v2/packages/react-app/config-overrides.js index aff51a7e..d28bb20d 100644 --- a/apps/gui-v2/packages/react-app/config-overrides.js +++ b/apps/gui-v2/packages/react-app/config-overrides.js @@ -2,18 +2,20 @@ const webpack = require('webpack'); module.exports = function override(config, env) { config.resolve.fallback = { - url: require.resolve('url'), + url: require.resolve('url/'), fs: require.resolve('fs'), assert: require.resolve('assert'), http: require.resolve('stream-http'), https: require.resolve('https-browserify'), os: require.resolve('os-browserify/browser'), - buffer: require.resolve('buffer'), + buffer: require.resolve('buffer/'), stream: require.resolve('stream-browserify'), - buffer: require.resolve("buffer/"), - url: require.resolve("url/"), - process: require.resolve("process/browser") + process: require.resolve('process/browser.js') }; + // Some packages import 'process/browser' directly (ESM), add an explicit alias + // so requests like 'process/browser' resolve to the JS file with extension. + config.resolve.alias = config.resolve.alias || {}; + config.resolve.alias['process/browser'] = require.resolve('process/browser.js'); config.plugins.push( new webpack.ProvidePlugin({ process: 'process/browser', @@ -22,4 +24,4 @@ module.exports = function override(config, env) { ); return config; -} \ No newline at end of file +} diff --git a/apps/gui-v2/packages/react-app/package.json b/apps/gui-v2/packages/react-app/package.json index 462f0f36..541bdccf 100644 --- a/apps/gui-v2/packages/react-app/package.json +++ b/apps/gui-v2/packages/react-app/package.json @@ -41,6 +41,8 @@ "@types/react": "^18.0.1", "@types/react-dom": "18.0.0", "@types/react-router-dom": "^5.3.3", + "@types/smpte-timecode": "^1.2.1", + "@types/wavesurfer.js": "^6.0.12", "customize-cra": "^0.8.0", "file-loader": "^6.2.0", "history": "^5.0.0", @@ -50,17 +52,17 @@ "react-router-dom": "^6.3.0", "react-scripts": "4.0.0", "svg-url-loader": "^7.1.1", - "typescript": "^4.0.3", "smpte-timecode": "^1.2.3", - "@types/smpte-timecode": "^1.2.1", "recoil": "0.7.1", "rc-scrollbars": "^1.1.2", "react-custom-scrollbars-2": "^4.4.0", - "@types/wavesurfer.js": "^3.3.2", "simplebar-react": "^2.3.6", - "wavesurfer.js": "^4.2.0", "npm-license-crawler": "^0.2.1", "luxon": "^1.27.0", - "@types/luxon": "^1.26.5" + "@types/luxon": "^1.26.5", + "typescript": "^5.9.3", + "wavesurfer.js": "^7.12.1", + "webpack": "^5.2.1", + "webpack-dev-server": "5.2.1" } } diff --git a/apps/gui-v2/packages/react-app/src/components/ImagesGallery/ImagesGallery.tsx b/apps/gui-v2/packages/react-app/src/components/ImagesGallery/ImagesGallery.tsx index d79d921f..3ff3ac33 100644 --- a/apps/gui-v2/packages/react-app/src/components/ImagesGallery/ImagesGallery.tsx +++ b/apps/gui-v2/packages/react-app/src/components/ImagesGallery/ImagesGallery.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import ImageGallery from 'react-image-gallery'; -import 'react-image-gallery/styles/scss/image-gallery.scss'; +import _ImageGallery from 'react-image-gallery'; +import 'react-image-gallery/styles/css/image-gallery.css'; import './styles.scss'; +const ImageGallery: any = _ImageGallery; + interface IImage { original: string; thumbnail: string; diff --git a/apps/gui-v2/packages/react-app/src/pages/Capture/AddSourceModal.tsx b/apps/gui-v2/packages/react-app/src/pages/Capture/AddSourceModal.tsx index 6839467b..37d22180 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Capture/AddSourceModal.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Capture/AddSourceModal.tsx @@ -1,8 +1,10 @@ import React from 'react'; -import Modal from 'react-modal'; +import _Modal from 'react-modal'; import SourceInfo from './SourceInfo'; import './styles.scss'; +const Modal: any = _Modal; + interface IComponentProps { isOpen: boolean; onAdd: (source: { label: string, multicast: string, port: string }) => void; diff --git a/apps/gui-v2/packages/react-app/src/pages/Capture/CaptureContent.tsx b/apps/gui-v2/packages/react-app/src/pages/Capture/CaptureContent.tsx index b59916fb..b000709e 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Capture/CaptureContent.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Capture/CaptureContent.tsx @@ -54,7 +54,7 @@ function CaptureContent({ message: (

Could not capture pcap {name}

-

{captureResult}

+

{JSON.stringify(captureResult)}

), }); @@ -69,7 +69,7 @@ function CaptureContent({ message: (

Could not analyze pcap {name}

-

{awaiterResult}

+

{JSON.stringify(awaiterResult)}

), }); diff --git a/apps/gui-v2/packages/react-app/src/pages/Capture/ConfirmModal.tsx b/apps/gui-v2/packages/react-app/src/pages/Capture/ConfirmModal.tsx index ae506942..e4dea04c 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Capture/ConfirmModal.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Capture/ConfirmModal.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import Modal from 'react-modal'; +import _Modal from 'react-modal'; import SourceInfo from './SourceInfo'; import './styles.scss'; +const Modal: any = _Modal; + + interface IComponentProps { message: string; isOpen: boolean; diff --git a/apps/gui-v2/packages/react-app/src/pages/Capture/EditSourceModal.tsx b/apps/gui-v2/packages/react-app/src/pages/Capture/EditSourceModal.tsx index bce33b54..f60949e0 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Capture/EditSourceModal.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Capture/EditSourceModal.tsx @@ -1,8 +1,11 @@ import React from 'react'; -import Modal from 'react-modal'; +import _Modal from 'react-modal'; import SourceInfo from './SourceInfo'; import './styles.scss'; +const Modal: any = _Modal; + + interface IComponentProps { source: any | undefined, isOpen: boolean; diff --git a/apps/gui-v2/packages/react-app/src/pages/Common/MainPage.tsx b/apps/gui-v2/packages/react-app/src/pages/Common/MainPage.tsx index 2a6e1267..1c028d95 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Common/MainPage.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Common/MainPage.tsx @@ -57,7 +57,7 @@ const MainPage = () => { draggable={false} pauseOnHover progressClassName="toastProgress" - bodyClassName="toastBody" + toastClassName="toastBody" /> ); diff --git a/apps/gui-v2/packages/react-app/src/pages/Dashboard/UploadPcap/UploadModal.tsx b/apps/gui-v2/packages/react-app/src/pages/Dashboard/UploadPcap/UploadModal.tsx index 4d83615a..2872daa7 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Dashboard/UploadPcap/UploadModal.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Dashboard/UploadPcap/UploadModal.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import Modal from 'react-modal'; +import _Modal from 'react-modal'; import './styles.scss'; import _ from 'lodash'; import list from '../../../utils/api'; @@ -7,6 +7,9 @@ import PcapUploadInformation from './PcapUploadInformation'; import { CancelIcon } from 'components/icons'; import { CustomScrollbar } from 'components'; +const Modal: any = _Modal; + + interface IComponentProps { isOpen: boolean; onUploadDone: () => void; diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx index a0aedfc1..b75a38de 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx @@ -1,8 +1,9 @@ import React from 'react'; -import WaveSurfer, { WaveSurferPlugin } from 'wavesurfer.js'; +import { useWavesurfer } from '@wavesurfer/react' +import Timeline from 'wavesurfer.js/dist/plugins/timeline.esm.js' + import './styles.scss'; -const TimelinePlugin = require('wavesurfer.js/dist/plugin/wavesurfer.timeline.min.js'); -const CursorPlugin = require('wavesurfer.js/dist/plugin/wavesurfer.cursor.js'); + import { Slider, ButtonAudioPlayer, CustomScrollbar } from 'components/index'; import { translate } from '../../../../utils/translation'; @@ -29,46 +30,46 @@ function timeInterval(pxPerSec: number) { } const createWaveSurfer = (waveform: any) => { - const wavesurfer = WaveSurfer.create({ + const wavesurfer = useWavesurfer({ container: waveform, waveColor: '#80c1ff', progressColor: '#0083ff', - splitChannels: true, + // splitChannels: true, autoCenter: true, - scrollParent: true, + // scrollParent: true, fillParent: true, barWidth: 1, - responsive: true, + // responsive: true, normalize: true, hideScrollbar: false, height: 120, cursorWidth: 3, cursorColor: 'rgba(255, 71, 71, 0.5)', - xhr: { withCredentials: true }, + // xhr: { withCredentials: true }, plugins: [ - TimelinePlugin.create({ + Timeline.create({ container: '.wave-timeline', - timeInterval: timeInterval, - primaryColor: '#39415a', - secondaryColor: 'white', - primaryFontColor: '#39415a', - secondaryFontColor: 'white', - }), - CursorPlugin.create({ - showTime: true, - opacity: 1, - - customShowTimeStyle: { - 'background-color': '#fff', - color: '#000', - padding: '2px', - 'font-size': '10px', - }, - customStyle: { - 'border-color': 'white', - }, + // timeInterval: timeInterval, + // primaryColor: '#39415a', + // secondaryColor: 'white', + // primaryFontColor: '#39415a', + // secondaryFontColor: 'white', }), + // CursorPlugin.create({ + // showTime: true, + // opacity: 1, + + // customShowTimeStyle: { + // 'background-color': '#fff', + // color: '#000', + // padding: '2px', + // 'font-size': '10px', + // }, + // customStyle: { + // 'border-color': 'white', + // }, + // }), ], }); @@ -120,7 +121,7 @@ function AudioPlayer({ React.useEffect(() => { const waveform = waveformRef?.current?.querySelector('.wave'); - const wavesurfer = createWaveSurfer(waveform); + const {wavesurfer, isPlaying, currentTime} = createWaveSurfer(waveform); waveSurferRef.current = wavesurfer; if (mp3Url === '') { return; diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx index 9a7667d5..d58e0468 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx @@ -48,7 +48,7 @@ function AudioPlayerDisplay({ const wsClient = list.wsClient; React.useEffect(() => { - if (wsClient === (null || undefined)) { + if (wsClient == null) { return; } const handleMessage = (msg: any) => { diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Video/VideoStreamExplorerDisplay.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Video/VideoStreamExplorerDisplay.tsx index f0720db1..0126c0ed 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Video/VideoStreamExplorerDisplay.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Video/VideoStreamExplorerDisplay.tsx @@ -14,7 +14,7 @@ function useWaitForFrames(pcapId: string, streamId: string): WaitForFramesStates const [framesAreReady, setFramesAreReady] = React.useState(WaitForFramesStates.waiting); const wsClient = list.wsClient; React.useEffect(() => { - if (wsClient === (null || undefined)) { + if (wsClient == null) { return; } diff --git a/apps/gui-v2/packages/react-app/src/store/gui/liveSource/useRecoilLiveSourceHandler.tsx b/apps/gui-v2/packages/react-app/src/store/gui/liveSource/useRecoilLiveSourceHandler.tsx index e0beeacc..9f221296 100644 --- a/apps/gui-v2/packages/react-app/src/store/gui/liveSource/useRecoilLiveSourceHandler.tsx +++ b/apps/gui-v2/packages/react-app/src/store/gui/liveSource/useRecoilLiveSourceHandler.tsx @@ -45,7 +45,7 @@ export default () => { ); React.useEffect(() => { - if (wsClient === (null || undefined)) { + if (wsClient == null) { return; } diff --git a/apps/gui-v2/packages/react-app/src/store/gui/pcaps/useRecoilPcapsHandler.tsx b/apps/gui-v2/packages/react-app/src/store/gui/pcaps/useRecoilPcapsHandler.tsx index f47853fd..586dc138 100644 --- a/apps/gui-v2/packages/react-app/src/store/gui/pcaps/useRecoilPcapsHandler.tsx +++ b/apps/gui-v2/packages/react-app/src/store/gui/pcaps/useRecoilPcapsHandler.tsx @@ -146,7 +146,7 @@ export default () => { ); React.useEffect(() => { - if (wsClient === (null || undefined)) { + if (wsClient == null) { return; } diff --git a/apps/gui-v2/packages/react-app/src/store/gui/streamComparison/useRecoilStreamComparisonHandler.tsx b/apps/gui-v2/packages/react-app/src/store/gui/streamComparison/useRecoilStreamComparisonHandler.tsx index d82a4ff0..0f391f07 100644 --- a/apps/gui-v2/packages/react-app/src/store/gui/streamComparison/useRecoilStreamComparisonHandler.tsx +++ b/apps/gui-v2/packages/react-app/src/store/gui/streamComparison/useRecoilStreamComparisonHandler.tsx @@ -71,7 +71,7 @@ export default () => { ); React.useEffect(() => { - if (wsClient === (null || undefined)) { + if (wsClient == null) { return; } diff --git a/apps/gui-v2/packages/react-app/src/store/gui/user/useRecoilUserHandler.tsx b/apps/gui-v2/packages/react-app/src/store/gui/user/useRecoilUserHandler.tsx index 8a729797..66f91317 100644 --- a/apps/gui-v2/packages/react-app/src/store/gui/user/useRecoilUserHandler.tsx +++ b/apps/gui-v2/packages/react-app/src/store/gui/user/useRecoilUserHandler.tsx @@ -16,7 +16,7 @@ export default () => { //Reconnect websocket after refreshing browser const wsClient = list.wsClient; - if (wsClient === (null || undefined) && userInfo) { + if (wsClient == null && userInfo) { list.reconnectWsClient(userInfo?.id); } }; diff --git a/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts b/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts index 351f2c34..ff2479e0 100644 --- a/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts +++ b/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts @@ -5,7 +5,7 @@ const isIGraphicTimeMaxData = ( data: IGraphicTimeMaxData[] | IGraphicTimeValueData[] ): data is IGraphicTimeMaxData[] => { if (data.length === 0) return false; - return (data as IGraphicTimeMaxData[])[0].max !== (undefined || null); + return (data as IGraphicTimeMaxData[])[0].max != null; }; export const getFinalData = (data: IGraphicTimeMaxData[] | IGraphicTimeValueData[]) => { diff --git a/apps/gui-v2/tsconfig.json b/apps/gui-v2/tsconfig.json index 333c5431..e23ae849 100644 --- a/apps/gui-v2/tsconfig.json +++ b/apps/gui-v2/tsconfig.json @@ -6,10 +6,10 @@ "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, - "strict": true, + "strict": false, "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", + "module": "nodenext", + "moduleResolution": "nodenext", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, diff --git a/apps/listwebserver/package.json b/apps/listwebserver/package.json index 5d2f0cad..d7a7df4e 100644 --- a/apps/listwebserver/package.json +++ b/apps/listwebserver/package.json @@ -32,12 +32,12 @@ "fs-jetpack": "^2.2.3", "helmet": "^3.21.2", "influx": "^5.5.1", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.3", "lodash": "^4.17.21", "moment": "^2.22.2", - "mongoose": "^5.7.12", + "mongoose": "^6.13.6", "morgan": "^1.9.1", - "multer": "^1.4.2", + "multer": "^2.0.2", "node-cron": "^2.0.3", "node-fetch": "^2.6.0", "path": "^0.12.7", @@ -46,10 +46,10 @@ "sdp-transform": "^2.14.1", "sdpoker": "AMWA-TV/sdpoker#076999c144e105beb1bae428793b75589537c5d6", "socket.io": "^2.3.0", - "tmp": "^0.1.0", + "tmp": "^0.2.5", "url": "^0.11.0", "uuid": "^8.3.2", - "validator": "^12.0.0", + "validator": "^13.15.23", "websocket": "^1.0.30", "winston": "^3.2.1" }, diff --git a/apps/listwebserver/src/analyzers/rtp.ts b/apps/listwebserver/src/analyzers/rtp.ts index 653c909c..9cfb04ff 100644 --- a/apps/listwebserver/src/analyzers/rtp.ts +++ b/apps/listwebserver/src/analyzers/rtp.ts @@ -217,19 +217,20 @@ export async function doInterFrameRtpTsDeltaAnalysis( } function getInterFrameRtpTsDeltaLimit(stream: api.pcap.IStreamInfo): api.pcap.IMinMax | null { - var rate; - if (_.get(stream, 'statistics.rate', null) !== null) { - rate = _.get(stream, 'statistics.rate', null); - } else if (_.get(stream, 'media_specific.rate', null) !== null) { - rate = _.get(stream, 'media_specific.rate', null); - } else { - return null; - } + const mediaSpecific = stream.media_specific as unknown as { rate?: unknown } | undefined; + const rate = stream.statistics?.rate ?? (mediaSpecific && 'rate' in (mediaSpecific as object) ? mediaSpecific.rate : null); - const rtpClockRate = 90000; - const interTicks = rtpClockRate / eval(rate); + if (rate == null) return null; + + const rateValue = Number(rate); + if (isNaN(rateValue) || rateValue === 0) return null; + + + const rtpClockRate = 90000; + const interTicks = rtpClockRate / rateValue; + return { min: Math.floor(interTicks), max: Math.ceil(interTicks), diff --git a/package.json b/package.json index 78c6db80..1d9ce89e 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "apps/gui-v2" ], "resolutions": { - "@types/minimatch": "5.1.2" - } + "@types/minimatch": "5.1.2", + "semver": "^7.3.5", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7" + } } From 6f07e5130931a48673d0ce11e25862b34a96143e Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 10:53:27 +0000 Subject: [PATCH 06/27] GUI: Fixed WaveSurfer usage --- .../StreamExplorerPage/Audio/AudioPlayer.tsx | 158 +++++++----------- .../Audio/AudioPlayerDisplay.tsx | 9 +- 2 files changed, 65 insertions(+), 102 deletions(-) diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx index b75a38de..b096c36a 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx @@ -29,126 +29,92 @@ function timeInterval(pxPerSec: number) { return retval; } -const createWaveSurfer = (waveform: any) => { - const wavesurfer = useWavesurfer({ - container: waveform, - waveColor: '#80c1ff', +function AudioPlayer({ mp3Url }: { mp3Url: string }) { + const [isLoading, setisLoading] = React.useState(true); + const [isPlaying, setIsPlaying] = React.useState(false); + const [hasError, setHasError] = React.useState(false); + const waveContainerRef = React.useRef(null); + + const timelinePlugin = React.useMemo(() => ( + Timeline.create({ container: '.wave-timeline' }) + ), []); + + const wsOptions = React.useMemo(() => ({ + container: waveContainerRef, progressColor: '#0083ff', - // splitChannels: true, autoCenter: true, - // scrollParent: true, fillParent: true, barWidth: 1, - // responsive: true, normalize: true, hideScrollbar: false, height: 120, cursorWidth: 3, cursorColor: 'rgba(255, 71, 71, 0.5)', + plugins: [timelinePlugin], + }), [timelinePlugin]); - // xhr: { withCredentials: true }, - plugins: [ - Timeline.create({ - container: '.wave-timeline', - // timeInterval: timeInterval, - // primaryColor: '#39415a', - // secondaryColor: 'white', - // primaryFontColor: '#39415a', - // secondaryFontColor: 'white', - }), - // CursorPlugin.create({ - // showTime: true, - // opacity: 1, - - // customShowTimeStyle: { - // 'background-color': '#fff', - // color: '#000', - // padding: '2px', - // 'font-size': '10px', - // }, - // customStyle: { - // 'border-color': 'white', - // }, - // }), - ], - }); - - return wavesurfer; -}; - -function AudioPlayer({ - mp3Url, - cursorInitPos, - onCursorChanged, -}: { - mp3Url: string; - cursorInitPos: number; - onCursorChanged: ((d: number, c: number) => void) | undefined; -}) { - const [isLoading, setisLoading] = React.useState(true); - const [isPlaying, setIsPlaying] = React.useState(false); - const [hasError, setHasError] = React.useState(false); - const waveSurferRef = React.useRef(null); - const waveformRef = React.useRef(null); - - const onPlayerReady = () => { - setisLoading(false); - setHasError(false); - waveSurferRef.current.seekTo(cursorInitPos); + const { wavesurfer, isReady } = useWavesurfer(wsOptions); - if (onCursorChanged) { - waveSurferRef.current.on('seek', onSeek); - waveSurferRef.current.on('pause', onSeek); - onSeek(); - } - }; + const onFinishPlay = () => setIsPlaying(false); - const onFinishPlay = () => { - setIsPlaying(false); - }; + // Load audio and attach handlers when url changes + React.useEffect(() => { + if (!wavesurfer || !mp3Url) return; - const onPlayerError = () => { - setHasError(true); - }; + setisLoading(true); + setHasError(false); - const onSeek = () => { - if (onCursorChanged) { - const duration = waveSurferRef.current.getDuration(); - const currenttime = waveSurferRef.current.getCurrentTime(); - onCursorChanged(duration, currenttime); - } - }; + const handleReady = () => { + setisLoading(false); + }; + const handleError = () => { + setHasError(true); + setisLoading(false); + }; + const handleFinish = onFinishPlay; + + wavesurfer.on('ready', handleReady); + wavesurfer.on('error', handleError); + wavesurfer.on('finish', handleFinish); + + // Check content-type before loading to avoid WaveSurfer crashes + // when the first requests don't have the correct content-type + const loadWithContentTypeCheck = async () => { + try { + const response = await fetch(mp3Url, { method: 'HEAD' }); + const contentType = response.headers.get('content-type') || ''; + + if (contentType.startsWith('audio/') || contentType.includes('mpeg')) { + wavesurfer.load(mp3Url); + } else { + // Not ready yet, trigger retry logic + handleError(); + } + } catch (error) { + handleError(); + } + }; - React.useEffect(() => { - const waveform = waveformRef?.current?.querySelector('.wave'); - const {wavesurfer, isPlaying, currentTime} = createWaveSurfer(waveform); - waveSurferRef.current = wavesurfer; - if (mp3Url === '') { - return; - } - wavesurfer.load(mp3Url); - - wavesurfer.on('ready', onPlayerReady); - wavesurfer.on('finish', onFinishPlay); - wavesurfer.on('error', onPlayerError); + loadWithContentTypeCheck(); return () => { - wavesurfer.unAll(); - wavesurfer.destroy(); + wavesurfer.un('ready', handleReady); + wavesurfer.un('error', handleError); + wavesurfer.un('finish', handleFinish); }; - }, [mp3Url]); + }, [wavesurfer, mp3Url]); const play = () => { - waveSurferRef?.current?.playPause(); - setIsPlaying(prevIsPlaying => !prevIsPlaying); + wavesurfer?.playPause(); + setIsPlaying(prev => !prev); }; const onZoom = (value: number) => { - waveSurferRef.current.zoom(value); + wavesurfer?.zoom(value); }; const onVolumeChange = (value: number) => { - waveSurferRef.current.setVolume(value); + wavesurfer?.setVolume(value); }; const buttonLabel = isPlaying ? translate('audio_player.pause') : translate('audio_player.play'); @@ -157,10 +123,10 @@ function AudioPlayer({ const buttonDisabled = hasError; return ( -
+
{/* {isLoading && } */} -
+
{!isLoading && ( diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx index d58e0468..e489f5ce 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayerDisplay.tsx @@ -38,11 +38,8 @@ function AudioPlayerDisplay({ const [mp3Url, setMp3Url] = React.useState(); React.useEffect(() => { - const loadMp3Url = async (): Promise => { - const newMp3Url = await list.stream.downloadMp3Url(pcapID, currentStream?.id); - setMp3Url(newMp3Url); - }; - loadMp3Url(); + const newMp3Url = list.stream.downloadMp3Url(pcapID, currentStream?.id); + setMp3Url(newMp3Url); }, [currentStream?.id]); const wsClient = list.wsClient; @@ -92,7 +89,7 @@ function AudioPlayerDisplay({
{mp3Url !== '' ? ( - + ) : (
ERROR: MP3_FILE_FAILED
)} From 17384a60858c69e3947f6d74dd0f4882aa53f325 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 10:55:02 +0000 Subject: [PATCH 07/27] GUI: Error handling in stream selector --- .../StreamComparison/StreamSelectorPanel.tsx | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/apps/gui-v2/packages/react-app/src/pages/StreamComparison/StreamSelectorPanel.tsx b/apps/gui-v2/packages/react-app/src/pages/StreamComparison/StreamSelectorPanel.tsx index cbb8cdcc..3f1e1f1f 100644 --- a/apps/gui-v2/packages/react-app/src/pages/StreamComparison/StreamSelectorPanel.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/StreamComparison/StreamSelectorPanel.tsx @@ -17,6 +17,16 @@ function StreamSelectorPanel({ pcaps, onChange, enableAudioChannelSelector, isAu const [streams, setStreams] = React.useState([]); const [audioChannels, setAudioChannels] = React.useState([]); + const extractErrorMessage = (err: any): string => { + if (err?.response?.data?.message) return err.response.data.message; + if (err?.message) return err.message; + try { + return JSON.stringify(err); + } catch { + return String(err); + } + }; + React.useEffect(() => { if (!selectedPcapId) { return; @@ -29,17 +39,17 @@ function StreamSelectorPanel({ pcaps, onChange, enableAudioChannelSelector, isAu setSelectedStreamId(s[0].id); onChange({ pcap: selectedPcapId, stream: s[0].id, audioChannel: null }); }) - .catch(e => { + .catch((e: any) => { Notification({ typeMessage: 'error', message: (

Could not get stream from Pcap

-

{e}

+

{extractErrorMessage(e)}

), }); - console.error(`Error getting streams: ${e}`); + console.error('Error getting streams:', e); }); }, [selectedPcapId]); @@ -67,17 +77,17 @@ function StreamSelectorPanel({ pcaps, onChange, enableAudioChannelSelector, isAu onChange({ pcap: selectedPcapId, stream: selectedStreamId, audioChannel: null }); } }) - .catch((e: React.ReactNode) => { + .catch((e: any) => { Notification({ typeMessage: 'error', message: (

Could not get streaminfo from id:

-

{e}

+

{extractErrorMessage(e)}

), }); - console.error(`Error getting streams: ${e}`); + console.error('Error getting stream info:', e); }); }, [selectedStreamId]); From d59f0a28c0538a4b5ef5c306e1f18a29e2b85721 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 13:58:54 +0000 Subject: [PATCH 08/27] GUI: Fix percentage histograms' bounds Histogram charts were going out of bounds, specifying a yDomain range of [0,100] and removing stackOffset=true fixes this --- .../src/components/BarGraphic/BarGraphic.tsx | 5 ++-- .../GraphsPage/Ancillary/RtpLineCharts.tsx | 11 +++++-- .../GraphsPage/Video/CbufferAnalysis.tsx | 2 ++ .../GraphsPage/Video/VrxAnalysis.tsx | 2 ++ .../graphs/dataTransformationLineGraphs.ts | 30 ++++++++++++------- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/apps/gui-v2/packages/react-app/src/components/BarGraphic/BarGraphic.tsx b/apps/gui-v2/packages/react-app/src/components/BarGraphic/BarGraphic.tsx index 85ce3272..48374453 100644 --- a/apps/gui-v2/packages/react-app/src/components/BarGraphic/BarGraphic.tsx +++ b/apps/gui-v2/packages/react-app/src/components/BarGraphic/BarGraphic.tsx @@ -28,6 +28,7 @@ interface IComponentProps { datakeyX: string; datakeyY: string; leftMargin?: number; + yDomain?: [number | string, number | string]; } function BarGraphic({ barGraphicData }: { barGraphicData: IComponentProps }) { @@ -97,7 +98,6 @@ function BarGraphic({ barGraphicData }: { barGraphicData: IComponentProps }) { right: 30, left: barGraphicData.leftMargin === undefined ? 0 : barGraphicData.leftMargin, }} - stackOffset={'expand'} > - ) } /> {barGraphicData.referenceLines && barGraphicData.referenceLines.map((item, index) => ( diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Ancillary/RtpLineCharts.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Ancillary/RtpLineCharts.tsx index f2f1273d..5523336c 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Ancillary/RtpLineCharts.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Ancillary/RtpLineCharts.tsx @@ -32,7 +32,8 @@ function RtpLineCharts({ setpacketsFrameData([]); const loadPacketsFrameData = async (): Promise => { const all = await list.stream.getPacketsPerFrame(pcapID, streamID, first_packet_ts, last_packet_ts); - const packetsFrameFinalData = getFinalData(all); + const normalized = Array.isArray(all) ? all : (Array.isArray((all as any)?.data) ? (all as any).data : []); + const packetsFrameFinalData = getFinalData(normalized); setpacketsFrameData(packetsFrameFinalData as IGraphicTimeValueData[]); }; loadPacketsFrameData(); @@ -47,7 +48,8 @@ function RtpLineCharts({ first_packet_ts, last_packet_ts ); - const latencyFinalData = getFinalData(getDeltaFPTvsRTP(all)); + const normalized = Array.isArray(all) ? all : (Array.isArray((all as any)?.data) ? (all as any).data : []); + const latencyFinalData = getFinalData(getDeltaFPTvsRTP(normalized)); setlatencyData(latencyFinalData as IGraphicTimeValueData[]); }; loadLatencyData(); @@ -57,7 +59,8 @@ function RtpLineCharts({ setRtpTimeStepData([]); const loadRtpTimeStepData = async (): Promise => { const all = await list.stream.getDeltaToPreviousRtpTsRaw(pcapID, streamID, first_packet_ts, last_packet_ts); - const rtpTimeStepFinalData = getFinalData(all); + const normalized = Array.isArray(all) ? all : (Array.isArray((all as any)?.data) ? (all as any).data : []); + const rtpTimeStepFinalData = getFinalData(normalized); setRtpTimeStepData(rtpTimeStepFinalData as IGraphicTimeValueData[]); }; loadRtpTimeStepData(); @@ -133,6 +136,7 @@ function RtpLineCharts({ const packetsFrameHistFinalData = getFinalHistData(packetsFrameHistPercData); const leftMarginPacketsFrameHist = getLeftMarginBarGraphic(packetsFrameHistFinalData); const compliancePacketsFrameHist = getCompliance(currentStream?.analyses.pkts_per_frame.result || undefined); + const yDomainPercent: [number | string, number | string] = [0, 100]; const packetsFrameHistGraphData = { barGraphic: packetsFrameHistFinalData, title: mediaInfoVideoPacketPerFrame, @@ -142,6 +146,7 @@ function RtpLineCharts({ datakeyY: 'value', datakeyX: 'label', leftMargin: leftMarginPacketsFrameHist, + yDomain: yDomainPercent, }; return ( diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/CbufferAnalysis.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/CbufferAnalysis.tsx index 00980b89..62640043 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/CbufferAnalysis.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/CbufferAnalysis.tsx @@ -94,6 +94,7 @@ function CbufferAnalysis({ const cHistPercData: number[][] = getPercHistData(cHistData); const cHistFinalData = getFinalHistData(cHistPercData); const leftMarginCHist = getLeftMarginBarGraphic(cHistFinalData); + const yDomain: [number | string, number | string] = [0, 100]; const cHistGraphData = { barGraphic: cHistFinalData, title: 'Cinst histogram', @@ -103,6 +104,7 @@ function CbufferAnalysis({ datakeyY: 'value', datakeyX: 'label', leftMargin: leftMarginCHist, + yDomain, }; return ( diff --git a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/VrxAnalysis.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/VrxAnalysis.tsx index 101c902c..385942ea 100644 --- a/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/VrxAnalysis.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/GraphsPage/Video/VrxAnalysis.tsx @@ -80,6 +80,7 @@ function VrxAnalysis({ currentStream, pcapID }: { currentStream: SDK.types.IStre const vrxHistFinalData = getFinalHistData(vrxHistPercData); const leftMarginVrxHist = getLeftMarginBarGraphic(vrxHistFinalData); const complianceVrxHist = getCompliance(currentStream?.global_video_analysis?.vrx?.compliance); + const yDomainPercent: [number | string, number | string] = [0, 100]; const vrxHistGraphData = { barGraphic: vrxHistFinalData, title: mediaInfoHistogram, @@ -89,6 +90,7 @@ function VrxAnalysis({ currentStream, pcapID }: { currentStream: SDK.types.IStre datakeyY: 'value', datakeyX: 'label', leftMargin: leftMarginVrxHist, + yDomain: yDomainPercent, }; return ( diff --git a/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts b/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts index ff2479e0..cebecd45 100644 --- a/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts +++ b/apps/gui-v2/packages/react-app/src/utils/graphs/dataTransformationLineGraphs.ts @@ -1,25 +1,33 @@ import { IGraphicTimeMaxData, IGraphicTimeValueData } from 'components/index'; import _ from 'lodash'; -const isIGraphicTimeMaxData = ( - data: IGraphicTimeMaxData[] | IGraphicTimeValueData[] -): data is IGraphicTimeMaxData[] => { - if (data.length === 0) return false; - return (data as IGraphicTimeMaxData[])[0].max != null; -}; +export function isIGraphicTimeMaxData( + data: IGraphicTimeMaxData[] | IGraphicTimeValueData[] | Array +): data is IGraphicTimeMaxData[] { + if (!Array.isArray(data) || data.length === 0) return false as any; + const first = (data as Array).find((d) => d != null); + if (!first) return false as any; + return (first as IGraphicTimeMaxData).max != null; +} export const getFinalData = (data: IGraphicTimeMaxData[] | IGraphicTimeValueData[]) => { - if (isIGraphicTimeMaxData(data)) { - const result: IGraphicTimeMaxData[] = data.reduce((acc, curr) => { - if ((!_.isNil(curr.time)) || (!_.isNil(curr.max))) { + const arr: Array = Array.isArray(data) + ? data + : ((data as any)?.data && Array.isArray((data as any).data) ? (data as any).data : []); + + if (arr.length === 0) return []; + + if (isIGraphicTimeMaxData(arr)) { + const result: IGraphicTimeMaxData[] = (arr as IGraphicTimeMaxData[]).reduce((acc, curr) => { + if (!_.isNil((curr as IGraphicTimeMaxData).time) && !_.isNil((curr as IGraphicTimeMaxData).max)) { acc.push(curr); } return acc; }, [] as IGraphicTimeMaxData[]); return result; } else { - const result: IGraphicTimeValueData[] = data.reduce((acc, curr) => { - if ((!_.isNil(curr.time)) && (_.isNil(curr.value))) { + const result: IGraphicTimeValueData[] = (arr as IGraphicTimeValueData[]).reduce((acc, curr) => { + if (!_.isNil((curr as IGraphicTimeValueData).time) && !_.isNil((curr as IGraphicTimeValueData).value)) { acc.push(curr); } return acc; From b8366f023d0c51a539ae3dcfffd737bdef2277be Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 15:36:11 +0000 Subject: [PATCH 09/27] GUI: Fixed help sidebar button --- .../packages/react-app/src/pages/Common/SidebarHOC.tsx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/gui-v2/packages/react-app/src/pages/Common/SidebarHOC.tsx b/apps/gui-v2/packages/react-app/src/pages/Common/SidebarHOC.tsx index 5194998e..21bbe295 100644 --- a/apps/gui-v2/packages/react-app/src/pages/Common/SidebarHOC.tsx +++ b/apps/gui-v2/packages/react-app/src/pages/Common/SidebarHOC.tsx @@ -148,10 +148,8 @@ function SidebarHOC() { case sidebarButtonsKeys.settings: path = routeNames.SETTINGS; break; - case helpString: - const win = window.open('https://github.com/ebu/pi-list/issues', '_blank'); - win?.focus(); - routeBasePath ? (path = routeBasePath) : (path = '/'); + case sidebarButtonsKeys.help: + window.open('https://github.com/ebu/pi-list/issues', '_blank'); break; case sidebarButtonsKeys.version: path = location.pathname; From b089ec6839c8bbf8284eaa93888a9719c33490c6 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 13:35:05 +0000 Subject: [PATCH 10/27] Commented obsolete conan commands --- CMakeLists.txt | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eaaa5507..4c4de711 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,19 +14,26 @@ configure_file( "${PROJECT_SOURCE_DIR}/apps/listwebserver/version.yml" ) -execute_process(COMMAND bash "${PROJECT_SOURCE_DIR}/scripts/reset_libssl_permissions.sh" - WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") +#execute_process(COMMAND bash "${PROJECT_SOURCE_DIR}/scripts/reset_libssl_permissions.sh" +# WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}") # ------------------------------------------------------------------------- # conan conan_check() # Remove repositories used in older versions -conan_remove_remote(NAME conan-center) -conan_remove_remote(NAME bincrafters) -conan_remove_remote(NAME bisect) + +# Remote 'conan-center' can't be found or is disabled +# conan_remove_remote(NAME conan-center) + +# Remote 'bincrafters' can't be found or is disabled +# conan_remove_remote(NAME bincrafters) + +# Remote 'bisect' can't be found or is disabled +# conan_remove_remote(NAME bisect) + # Add conan center -conan_add_remote(NAME conancenter URL https://center.conan.io) +# conan_add_remote(NAME conancenter URL https://center.conan.io) conan_cmake_run(CONANFILE conanfile.txt BASIC_SETUP CMAKE_TARGETS From c39e837725da698a97bed0fe954477a86dd1a293 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 13:36:17 +0000 Subject: [PATCH 11/27] Copy root node_modules in deploy script --- scripts/deploy/deploy.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/deploy/deploy.sh b/scripts/deploy/deploy.sh index e2f5e506..f102c580 100755 --- a/scripts/deploy/deploy.sh +++ b/scripts/deploy/deploy.sh @@ -38,6 +38,7 @@ cp -R $TOP_DIR/apps/listwebserver/dist $RELEASE_DIR/server/app/listwebserver/ cp -R $TOP_DIR/apps/listwebserver/version.yml $RELEASE_DIR/server/app/listwebserver cp -R $TOP_DIR/apps/listwebserver/package.json $RELEASE_DIR/server/app/listwebserver cp -L -R $TOP_DIR/apps/listwebserver/node_modules $RELEASE_DIR/server/app/listwebserver +cp -L -R $TOP_DIR/node_modules $RELEASE_DIR/server/app/listwebserver cp -R $TOP_DIR/apps/gui-v2/packages/react-app/build/* $RELEASE_DIR/server/app/gui cp -R $DEPLOY_SCRIPT_DIR/artifacts/listwebserver/gen_static_config.sh $RELEASE_DIR/server/app/ cp -R $DEPLOY_SCRIPT_DIR/artifacts/listwebserver/static.config.json $RELEASE_DIR/server/app/listwebserver From e113849a04b9b9e18a69e87d8e5d86ca87c40e9f Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 14:36:35 +0000 Subject: [PATCH 12/27] Use archived apt sources and node20 in listwebserver dockerfile --- scripts/deploy/artifacts/listwebserver/Dockerfile | 9 ++++++++- scripts/deploy/artifacts/server.sh | 14 +++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/scripts/deploy/artifacts/listwebserver/Dockerfile b/scripts/deploy/artifacts/listwebserver/Dockerfile index 20ab11c2..ab287e53 100644 --- a/scripts/deploy/artifacts/listwebserver/Dockerfile +++ b/scripts/deploy/artifacts/listwebserver/Dockerfile @@ -3,13 +3,20 @@ FROM gcc:10.2 ENV DEBIAN_FRONTEND=noninteractive + +# Debian Buster archives were moved to archive.debian.org; +# The lines below switch apt sources to archive.debian.org and disable the +# 'Valid-Until' check so `apt-get update` succeeds for EOL releases. +RUN sed -i 's|deb.debian.org|archive.debian.org|g; s|security.debian.org|archive.debian.org|g' /etc/apt/sources.list || true +RUN printf 'Acquire::Check-Valid-Until "false";\n' > /etc/apt/apt.conf.d/99no-check-valid-until + RUN apt-get update RUN apt-get install -yq \ wireshark-common \ nginx # Install node -RUN curl -sL https://deb.nodesource.com/setup_12.x -o nodesource_setup.sh +RUN curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh RUN bash nodesource_setup.sh RUN apt-get install -y \ ffmpeg \ diff --git a/scripts/deploy/artifacts/server.sh b/scripts/deploy/artifacts/server.sh index ebcd4586..f65af6a2 100755 --- a/scripts/deploy/artifacts/server.sh +++ b/scripts/deploy/artifacts/server.sh @@ -32,9 +32,9 @@ start() echo -e "Starting containers..." '\n' if [ "$1" = '-m' ] || [ "$2" = '-m' ] then - docker-compose up --build + docker compose up --build else - docker-compose up --build -d + docker compose up --build -d fi echo -e "Started!" '\n' exit @@ -43,7 +43,7 @@ start() stop() { echo -e "Stopping containers..." '\n' - docker-compose stop + docker compose stop echo -e "Stopped!" '\n' exit } @@ -59,7 +59,7 @@ restart() remove() { echo -e "Removing..." '\n' - docker-compose down -v + docker compose down -v echo -e "Removed!" '\n' exit } @@ -67,7 +67,7 @@ remove() prune() { echo -e "Pruning..." '\n' - docker-compose rm -f -s -v + docker compose rm -f -s -v echo -e "Pruned!" '\n' exit } @@ -75,7 +75,7 @@ prune() monitor() { echo -e "Monitoring containers..." '\n' - docker-compose logs -f --tail=500 + docker compose logs -f --tail=500 echo -e "No longer monitoring containers!" '\n' exit } @@ -110,4 +110,4 @@ then monitor else display_help "Can't find any suitable argument." -fi \ No newline at end of file +fi From 804c2d2c598275440bb291afe3dd147f4017921a Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Mon, 17 Nov 2025 15:01:40 +0000 Subject: [PATCH 13/27] Set package resolutions to use patched dependencies Security fixes: - Critical (6->0): mongoose (6.13.6), form-data (2.5.4), url-parse (1.5.10) - High (18->0): axios (1.8.2), multer (2.0.2), jsonwebtoken (9.0.0), follow-redirects (1.15.6), dicer (0.3.0) - Moderate (54->13): nth-check (2.1.1), debug (4.3.4), webpack-dev-server (5.2.1), postcss (8.4.31), js-yaml (4.1.1), tmp (0.2.4) Audit results: 91 vulnerabilities -> 16 (83% reduction) --- package.json | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d9ce89e..cae8b8e5 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,24 @@ "@types/minimatch": "5.1.2", "semver": "^7.3.5", "@types/react": "^18.2.21", - "@types/react-dom": "^18.2.7" + "@types/react-dom": "^18.2.7", + "mongoose": "^6.13.6", + "form-data": "^2.5.4", + "url-parse": "^1.5.10", + "axios": "^1.8.2", + "multer": "^2.0.2", + "jsonwebtoken": "^9.0.0", + "follow-redirects": "^1.15.6", + "dicer": "^0.3.0", + "nth-check": "^2.1.1", + "debug": "^4.3.4", + "webpack-dev-server": "^5.2.1", + "postcss": "^8.4.31", + "js-yaml": "^4.1.1", + "tmp": "^0.2.4", + "cookie": "^0.7.0", + "validator": "^13.15.20", + "fast-xml-parser": "^4.1.2", + "tough-cookie": "^4.1.3" } } From 12cf7e2f6cd95f5887b2efee0c9fa8a336a29887 Mon Sep 17 00:00:00 2001 From: "guillaume.domingues" Date: Tue, 18 Nov 2025 14:09:51 +0000 Subject: [PATCH 14/27] Updated db calls to use promises --- apps/listwebserver/src/managers/database.ts | 6 +++--- apps/listwebserver/src/managers/database2.ts | 5 ++++- apps/listwebserver/src/managers/databaseCommon.ts | 9 +++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/listwebserver/src/managers/database.ts b/apps/listwebserver/src/managers/database.ts index f2097e34..c1d672eb 100644 --- a/apps/listwebserver/src/managers/database.ts +++ b/apps/listwebserver/src/managers/database.ts @@ -10,10 +10,10 @@ function createNewConnection(databaseName: string) { const conn = mongoose.createConnection(mongoDatabaseUrl, options); - conn.then( - (connection) => { + // Handle mongoose 6.x connection via promise + conn.asPromise().then( + () => { logger('database-manager').info('Connected to DB.'); - return connection; }, (err: any) => { logger('database-manager').error(`Failed to create a connection to DB: ${err.toString()}`); diff --git a/apps/listwebserver/src/managers/database2.ts b/apps/listwebserver/src/managers/database2.ts index c966e1b2..5181b561 100644 --- a/apps/listwebserver/src/managers/database2.ts +++ b/apps/listwebserver/src/managers/database2.ts @@ -4,7 +4,10 @@ import { hostname, options, port } from './databaseCommon'; async function doTestConnection(mongoDatabaseUrl: string): Promise { try { - await mongoose.createConnection(mongoDatabaseUrl, options); + const conn = mongoose.createConnection(mongoDatabaseUrl, options); + // Wait for connection to be established + await conn.asPromise(); + await conn.close(); return true; } catch (err) { return false; diff --git a/apps/listwebserver/src/managers/databaseCommon.ts b/apps/listwebserver/src/managers/databaseCommon.ts index 7476c18e..ea019501 100644 --- a/apps/listwebserver/src/managers/databaseCommon.ts +++ b/apps/listwebserver/src/managers/databaseCommon.ts @@ -1,10 +1,11 @@ import programArguments from '../util/programArguments'; +// Mongoose 6.x compatible options export const options = { - reconnectTries: Number.MAX_VALUE, // Never stop trying to reconnect - reconnectInterval: 500, // Reconnect every 500ms - useFindAndModify: false, - useNewUrlParser: true, + retryWrites: true, + retryReads: true, + maxPoolSize: 10, + serverSelectionTimeoutMS: 5000, }; export const { hostname, port } = programArguments.database; From 128532f4504cf01b720bba0d422567aa184f11ab Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 2 Dec 2025 17:14:38 +0000 Subject: [PATCH 15/27] Use gcc:11 for LIST server runtime --- scripts/deploy/artifacts/listwebserver/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/deploy/artifacts/listwebserver/Dockerfile b/scripts/deploy/artifacts/listwebserver/Dockerfile index ab287e53..2701f3e7 100644 --- a/scripts/deploy/artifacts/listwebserver/Dockerfile +++ b/scripts/deploy/artifacts/listwebserver/Dockerfile @@ -1,14 +1,14 @@ # This is the LIST server's runtime Dockerfile -FROM gcc:10.2 +FROM gcc:11 ENV DEBIAN_FRONTEND=noninteractive # Debian Buster archives were moved to archive.debian.org; # The lines below switch apt sources to archive.debian.org and disable the # 'Valid-Until' check so `apt-get update` succeeds for EOL releases. -RUN sed -i 's|deb.debian.org|archive.debian.org|g; s|security.debian.org|archive.debian.org|g' /etc/apt/sources.list || true -RUN printf 'Acquire::Check-Valid-Until "false";\n' > /etc/apt/apt.conf.d/99no-check-valid-until +# RUN sed -i 's|deb.debian.org|archive.debian.org|g; s|security.debian.org|archive.debian.org|g' /etc/apt/sources.list || true +# RUN printf 'Acquire::Check-Valid-Until "false";\n' > /etc/apt/apt.conf.d/99no-check-valid-until RUN apt-get update RUN apt-get install -yq \ From 8444c81248974f7762d7caeda36c716ef144cc65 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 2 Dec 2025 17:16:47 +0000 Subject: [PATCH 16/27] Create LIST db if nonexistent --- apps/listwebserver/src/managers/influx-db.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/listwebserver/src/managers/influx-db.js b/apps/listwebserver/src/managers/influx-db.js index d6a6c5d2..1b295984 100644 --- a/apps/listwebserver/src/managers/influx-db.js +++ b/apps/listwebserver/src/managers/influx-db.js @@ -23,6 +23,21 @@ class InfluxDbManager { }); log.info(`Influx DB Manager connect to ${program.influxURL}`); + + // Ensure the 'LIST' database exists. InfluxDB doesn't always auto-create + // databases; explicitly create it if missing to avoid write errors later. + this.influx.getDatabaseNames() + .then((names) => { + if (!names.includes('LIST')) { + log.info("'LIST' database not found — creating it."); + return this.influx.createDatabase('LIST'); + } + log.info("'LIST' database already exists."); + return null; + }) + .catch((err) => { + log.error(`Error while checking/creating 'LIST' database: ${err && err.message ? err.message : err}`); + }); } fromPcapIdWhereStreamIs(pcapID, streamID) { @@ -472,4 +487,4 @@ class InfluxDbManager { } } -module.exports = new InfluxDbManager(); \ No newline at end of file +module.exports = new InfluxDbManager(); From 9bc1bf69008ce2cca5f93f5e6ac908df2a3907e1 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 2 Dec 2025 17:20:26 +0000 Subject: [PATCH 17/27] Backend: Use join function for paths --- apps/listwebserver/src/api/pcap.js | 45 ++++++++++++++----- apps/listwebserver/src/app.js | 6 ++- apps/listwebserver/src/util/analysis/utils.ts | 9 ++-- package.json | 4 +- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/apps/listwebserver/src/api/pcap.js b/apps/listwebserver/src/api/pcap.js index d500d9b1..405e2dde 100644 --- a/apps/listwebserver/src/api/pcap.js +++ b/apps/listwebserver/src/api/pcap.js @@ -851,9 +851,16 @@ function renderMp3(req, res) { }) .exec() .then((data) => { - const folderPath = `${getUserFolder(req)}/${pcapID}/${streamID}/`; - const rawFilePath = `${folderPath}/raw`; - const mp3FilePath = `${folderPath}/audio-${channels}.mp3`; + + const folderPath = path.join(getUserFolder(req), pcapID, streamID); + const rawFilePath = path.join(folderPath, 'raw'); + const mp3FilePath = path.join(folderPath, `audio-${channels}.mp3`); + + try { + const rawExists = fs.fileExists(rawFilePath); + } catch (e) { + logger('render-mp3').error(`Error checking raw file existence: ${e}`); + } const encodingBits = data.media_specific.encoding == 'L24' ? 24 : 16; const sampling = parseInt(data.media_specific.sampling) / 1000; const channelNumber = data.media_specific.number_channels; @@ -883,6 +890,7 @@ function renderMp3(req, res) { .catch((output) => { logger('render-mp3').error(output.stdout); logger('render-mp3').error(output.stderr); + logger('render-mp3').error(`ffmpeg failed for mp3: ${mp3FilePath}`); const userId = getUserId(req); websocketManager.instance().sendEventToUser(userId, { event: api.wsEvents.Mp3.failed, @@ -904,14 +912,27 @@ router.get('/:pcapID/stream/:streamID/downloadmp3', (req, res) => { if (channels === undefined || channels === '') { channels = '0'; // keep first channel by default } - const folderPath = `${getUserFolder(req)}/${pcapID}/${streamID}`; - const filePath = `${folderPath}/audio-${channels}.mp3`; - - if (fs.fileExists(filePath)) { - fs.sendFileAsResponse(filePath, res); - logger('download-mp3').info(`Mp3 file ${filePath} already exist`); - } else { - logger('download-mp3').info(`Render mp3 file ${filePath}`); + const folderPath = path.join(getUserFolder(req), pcapID, streamID); + const filePath = path.join(folderPath, `audio-${channels}.mp3`); + + logger('download-mp3').info( + `downloadmp3 request: pcapID=${pcapID} streamID=${streamID} channels=${channels} file=${filePath}` + ); + + try { + const exists = fs.fileExists(filePath); + logger('download-mp3').info(`fileExists=${exists} for ${filePath}`); + if (exists) { + fs.sendFileAsResponse(filePath, res); + logger('download-mp3').info(`Mp3 file served: ${filePath}`); + } else { + logger('download-mp3').info(`Mp3 file not found, will render: ${filePath}`); + renderMp3(req, res); + } + } catch (e) { + logger('download-mp3').error(`Error checking/serving mp3 file: ${e}`); + // Fallback to attempt render + logger('download-mp3').info(`Attempting to render mp3 due to error for: ${filePath}`); renderMp3(req, res); } }); @@ -937,4 +958,4 @@ router.get('/:pcapID/stream/:streamID/ancillary/:filename', (req, res) => { } }); -module.exports = router; \ No newline at end of file +module.exports = router; diff --git a/apps/listwebserver/src/app.js b/apps/listwebserver/src/app.js index 4b963aa8..555517c3 100644 --- a/apps/listwebserver/src/app.js +++ b/apps/listwebserver/src/app.js @@ -1,4 +1,5 @@ const express = require('express'); +const path = require('path') const cookieParser = require('cookie-parser'); const session = require('express-session'); const morgan = require('morgan'); @@ -75,7 +76,8 @@ app.use(resourceNotFoundHandler); app.use(apiErrorHandler); // Generate static config data when the LIST web server is executed. -const generateStaticConfigCommand = `"${programArguments.cpp}/static_generator" "${programArguments.folder}"`; +const static_gen_path = path.join(programArguments.cpp, 'static_generator') +const generateStaticConfigCommand = `"${static_gen_path}" "${programArguments.folder}"`; logger('static-generator').profile('Static configurations generated'); @@ -92,4 +94,4 @@ exec(generateStaticConfigCommand) logger('static-generator').profile('Static configurations generated'); }); -module.exports = app; \ No newline at end of file +module.exports = app; diff --git a/apps/listwebserver/src/util/analysis/utils.ts b/apps/listwebserver/src/util/analysis/utils.ts index 9c8aac8e..2c2e9538 100644 --- a/apps/listwebserver/src/util/analysis/utils.ts +++ b/apps/listwebserver/src/util/analysis/utils.ts @@ -2,9 +2,12 @@ import { IPcapDefinition } from './index'; import { getUserId } from '../../auth/middleware'; import program from '../programArguments'; import { v4 as uuid } from 'uuid'; +import path from 'path'; export function getUserFolderFromUserId(userId: string): string { - return `${program.folder}/${userId}`; + // Use path.join to avoid accidental duplicate slashes when program.folder already + // ends with a trailing slash (sanitizeDirectoryPath currently appends one). + return path.join(program.folder, userId); } export function getUserFolder(req: unknown): string { @@ -23,7 +26,7 @@ export function generateRandomPcapFilename(file: { originalname: string }) { export function generatePcapDefinitionFromId(userId: string, pcapId: string): IPcapDefinition { return { uuid: pcapId, - folder: `${getUserFolderFromUserId(userId)}/${pcapId}`, + folder: path.join(getUserFolderFromUserId(userId), pcapId), }; } @@ -37,5 +40,5 @@ export function generateRandomPcapDefinition( } export function getPcapFolder(userId: string, pcapId: string): string { - return `${getUserFolderFromUserId(userId)}/${pcapId}`; + return path.join(getUserFolderFromUserId(userId), pcapId); } diff --git a/package.json b/package.json index cae8b8e5..292bf967 100644 --- a/package.json +++ b/package.json @@ -32,9 +32,9 @@ "dicer": "^0.3.0", "nth-check": "^2.1.1", "debug": "^4.3.4", - "webpack-dev-server": "^5.2.1", + "webpack-dev-server": "^4.15.0", "postcss": "^8.4.31", - "js-yaml": "^4.1.1", + "js-yaml": "^3.14.1", "tmp": "^0.2.4", "cookie": "^0.7.0", "validator": "^13.15.20", From a4eef6da5cf76aac85bb38e83632687b41a7c3c9 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 10:57:53 +0000 Subject: [PATCH 18/27] backend: Export rtp analysis function, log errors --- apps/listwebserver/src/analyzers/rtp.ts | 2 +- apps/listwebserver/src/controllers/streams.ts | 7 +++++-- apps/listwebserver/src/util/serverUtils.js | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/listwebserver/src/analyzers/rtp.ts b/apps/listwebserver/src/analyzers/rtp.ts index 9cfb04ff..a7c46762 100644 --- a/apps/listwebserver/src/analyzers/rtp.ts +++ b/apps/listwebserver/src/analyzers/rtp.ts @@ -125,7 +125,7 @@ function getResult(dropped_packets: number | undefined): api.pcap.Compliance { return 'not_compliant'; } -function addRtpSequenceAnalysisToStream(stream: api.pcap.IStreamInfo) { +export function addRtpSequenceAnalysisToStream(stream: api.pcap.IStreamInfo) { const dropped_packets_count = stream.statistics?.dropped_packet_count; const dropped_packets_samples = stream.statistics?.dropped_packet_samples; const packet_count = stream.statistics?.packet_count; diff --git a/apps/listwebserver/src/controllers/streams.ts b/apps/listwebserver/src/controllers/streams.ts index 687a3fe3..8a2e7c24 100644 --- a/apps/listwebserver/src/controllers/streams.ts +++ b/apps/listwebserver/src/controllers/streams.ts @@ -109,8 +109,11 @@ function upgradeStreamInfo(stream: any) { if (stream.media_type == 'audio') { stream = upgradeTsdfAnalysis(stream); } - stream = addRtpSequenceAnalysisToStream(stream); - + try { + stream = addRtpSequenceAnalysisToStream(stream); + } catch (err: any) { + logger.error(`upgradeStreamInfo: addRtpSequenceAnalysisToStream failed err=${err?.toString?.() ?? err}`); + } return stream; } diff --git a/apps/listwebserver/src/util/serverUtils.js b/apps/listwebserver/src/util/serverUtils.js index 1d9ab6d3..be2dbf14 100644 --- a/apps/listwebserver/src/util/serverUtils.js +++ b/apps/listwebserver/src/util/serverUtils.js @@ -16,7 +16,7 @@ function onError(error) { // handle specific listen errors with friendly messages switch (error.code) { case 'EACCES': - Logger('server').error(`${bind} requires elevated privileges`); + logger('server').error(`${bind} requires elevated privileges`); process.exit(1); break; case 'EADDRINUSE': From 83ad634d973aed1b665b3f2f1fe6d95e3f8e5fad Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 14:34:07 +0000 Subject: [PATCH 19/27] gui: Upgrade webpack-dev-server to 5.2.1 Migrated to CRACO because react-app-rewired doesn't work with this version of webpack-dev-server. --- .../gui-v2/packages/react-app/craco.config.js | 93 +++++++++++++++++++ apps/gui-v2/packages/react-app/package.json | 40 ++++---- package.json | 2 +- 3 files changed, 116 insertions(+), 19 deletions(-) create mode 100644 apps/gui-v2/packages/react-app/craco.config.js diff --git a/apps/gui-v2/packages/react-app/craco.config.js b/apps/gui-v2/packages/react-app/craco.config.js new file mode 100644 index 00000000..f2c6f51c --- /dev/null +++ b/apps/gui-v2/packages/react-app/craco.config.js @@ -0,0 +1,93 @@ +/* eslint-disable */ +const webpack = require('webpack'); + +module.exports = { + webpack: { + configure: (config) => { + // Preserve existing resolve and add polyfill fallbacks + config.resolve = config.resolve || {}; + config.resolve.fallback = { + ...(config.resolve.fallback || {}), + url: require.resolve('url/'), + fs: false, + assert: require.resolve('assert'), + http: require.resolve('stream-http'), + https: require.resolve('https-browserify'), + os: require.resolve('os-browserify/browser'), + buffer: require.resolve('buffer/'), + stream: require.resolve('stream-browserify'), + process: require.resolve('process/browser.js'), + }; + + // Alias to ensure ESM imports for process/browser resolve correctly + config.resolve.alias = config.resolve.alias || {}; + config.resolve.alias['process/browser'] = require.resolve('process/browser.js'); + + // Inject global providers + config.plugins = config.plugins || []; + config.plugins.push( + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'], + }) + ); + + return config; + }, + }, + devServer: (devServerConfig) => { + // Translate deprecated https boolean to server option for WDS v5 + if (typeof devServerConfig.https !== 'undefined') { + if (devServerConfig.https === true) { + devServerConfig.server = 'https'; + } else { + devServerConfig.server = 'http'; + } + delete devServerConfig.https; + } + // Normalize deprecated hooks to setupMiddlewares for WDS >=4/5 + const originalSetupMiddlewares = devServerConfig.setupMiddlewares; + + // Translate onBeforeSetupMiddleware to setupMiddlewares + if (typeof devServerConfig.onBeforeSetupMiddleware === 'function') { + const before = devServerConfig.onBeforeSetupMiddleware; + devServerConfig.setupMiddlewares = (middlewares, devServer) => { + before(devServer); + if (typeof originalSetupMiddlewares === 'function') { + middlewares = originalSetupMiddlewares(middlewares, devServer) || middlewares; + } + return middlewares; + }; + delete devServerConfig.onBeforeSetupMiddleware; + } + + // Translate onAfterSetupMiddleware to setupMiddlewares + if (typeof devServerConfig.onAfterSetupMiddleware === 'function') { + const after = devServerConfig.onAfterSetupMiddleware; + const prev = devServerConfig.setupMiddlewares; + devServerConfig.setupMiddlewares = (middlewares, devServer) => { + if (typeof prev === 'function') { + middlewares = prev(middlewares, devServer) || middlewares; + } + after(devServer); + return middlewares; + }; + delete devServerConfig.onAfterSetupMiddleware; + } + + // Ensure setupMiddlewares exists even if none of the above matched + if (typeof devServerConfig.setupMiddlewares !== 'function') { + devServerConfig.setupMiddlewares = (middlewares) => middlewares; + } + + // Add CRA compatibility shim: react-scripts expects devServer.close() + const prevSetup = devServerConfig.setupMiddlewares; + devServerConfig.setupMiddlewares = (middlewares, devServer) => { + if (devServer && typeof devServer.stop === 'function' && typeof devServer.close !== 'function') { + devServer.close = devServer.stop.bind(devServer); + } + return typeof prevSetup === 'function' ? (prevSetup(middlewares, devServer) || middlewares) : middlewares; + }; + return devServerConfig; + }, +}; diff --git a/apps/gui-v2/packages/react-app/package.json b/apps/gui-v2/packages/react-app/package.json index 541bdccf..2f50ca3a 100644 --- a/apps/gui-v2/packages/react-app/package.json +++ b/apps/gui-v2/packages/react-app/package.json @@ -4,40 +4,42 @@ "private": true, "dependencies": { "@bisect/ebu-list-sdk": "^0.1.0", - "bulma": "^0.9.1", - "react": "^18.0.0", - "react-dom": "^18.0.0", - "react-circular-progressbar": "^2.0.3", "@ebu-list/translations": "^0.1.0", - "fs": "^0.0.1-security", "assert": "^2.0.0", + "buffer": "^6.0.3", + "bulma": "^0.9.1", + "fs": "^0.0.1-security", "https-browserify": "^1.0.0", "os": "^0.1.2", "os-browserify": "^0.3.0", + "process": "^0.11.10", + "react": "^18.0.0", + "react-circular-progressbar": "^2.0.3", + "react-dom": "^18.0.0", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", - "buffer": "^6.0.3", - "url": "^0.11.0", - "process": "^0.11.10" + "url": "^0.11.0" }, "scripts": { - "start": "REACT_APP_LIVE_MODE=${EBU_LIST_LIVE_MODE:-false} react-app-rewired start", - "build:production": "react-app-rewired build", - "test": "react-app-rewired test", - "eject": "react-app-rewired eject" + "start": "REACT_APP_LIVE_MODE=${EBU_LIST_LIVE_MODE:-false} craco start", + "build:production": "craco build", + "test": "craco test", + "eject": "react-scripts eject" }, "devDependencies": { + "@craco/craco": "^7.1.0", "@types/react": "^18.0.1", - "@types/react-dom": "^18.0.0" + "@types/react-dom": "^18.0.0", + "react-scripts": "^5.0.1", + "webpack": "^5.2.1", + "webpack-dev-server": "5.2.1" }, "peerDependencies": { - "@types/query-string": "^6.3.0", - "node-polyglot": "^2.4.0", + "@craco/craco": "^7.1.0", "@types/jest": "24.0.19", - "@types/react-select": "^4.0.15", - "react-select": "^4.3.0", "@types/lodash": "^4.14.176", "@types/node": "12.11.1", + "@types/query-string": "^6.3.0", "@types/react": "^18.0.1", "@types/react-dom": "18.0.0", "@types/react-router-dom": "^5.3.3", @@ -47,10 +49,12 @@ "file-loader": "^6.2.0", "history": "^5.0.0", "lodash": "^4.17.21", + "node-polyglot": "^2.4.0", "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.3.0", - "react-scripts": "4.0.0", + "react-scripts": "^5.0.1", + "react-select": "^4.3.0", "svg-url-loader": "^7.1.1", "smpte-timecode": "^1.2.3", "recoil": "0.7.1", diff --git a/package.json b/package.json index 292bf967..039766b7 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "dicer": "^0.3.0", "nth-check": "^2.1.1", "debug": "^4.3.4", - "webpack-dev-server": "^4.15.0", + "webpack-dev-server": "^5.2.1", "postcss": "^8.4.31", "js-yaml": "^3.14.1", "tmp": "^0.2.4", From a505b624a37298d669dbd6cf7a390f635207bff7 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 9 Dec 2025 15:39:44 +0000 Subject: [PATCH 20/27] Removed lerna After the change to use yarn workspaces we no longer need lerna. Documentation was also updated to reflect this. This change could be improved upon by adding scripts to run tests, etc from the project root --- apps/gui-v2/package.json | 8 +++----- docs/development_guide.md | 9 +++++---- docs/read_only_user.md | 7 ++++--- docs/time_tests.md | 5 +++-- docs/validation_tests.md | 5 +++-- package.json | 8 +++++--- scripts/build_node.sh | 4 ++-- scripts/run_all.sh | 35 +++-------------------------------- scripts/setup_build_env.sh | 1 - 9 files changed, 28 insertions(+), 54 deletions(-) diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json index 285c5c0a..bc633022 100644 --- a/apps/gui-v2/package.json +++ b/apps/gui-v2/package.json @@ -2,9 +2,9 @@ "name": "list-ebu-gui-v2", "private": true, "scripts": { - "start": "lerna run start --stream --scope '@ebu-list/gui-v2'", - "live": "lerna run live --stream --scope '@ebu-list/gui-v2'", - "build:production": "lerna run build:production --stream --scope '@ebu-list/gui-v2'" + "start": "cd packages/react-app && yarn start", + "live": "cd packages/react-app && yarn live", + "build:production": "cd packages/react-app && yarn build:production" }, "version": "1.0.0", "devDependencies": { @@ -38,9 +38,7 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", "jest": "^30.2.0", - "lerna": "^9.0.1", "prettier": "^3.6.2", - "react-app-rewired": "^2.2.1", "sass": "^1.94.0", "sass-loader": "^16.0.6", "style-loader": "^4.0.0", diff --git a/docs/development_guide.md b/docs/development_guide.md index 17bef3a6..5df2d11e 100644 --- a/docs/development_guide.md +++ b/docs/development_guide.md @@ -22,7 +22,7 @@ LIST is mostly composed of: - **Ninja** >= v1.10 - **Docker** >= v15 - **Docker-compose** >= v1.20 -- **NodeJS** >= v12 + npm packages: lerna & yarn +- **NodeJS** >= v12 + yarn - **C++17 compatible compiler** We use CMake as the meta build system and require most of our third-party dependencies using conan. @@ -91,7 +91,7 @@ To use as an external library, just use cmake's `add_subdirectory()` and it will ## Build node packages: -Packages are listed in `lerna.json` and mostly includes: +Node packages are under the `apps/` and `js/` workspace folders and mostly include: - backend server - reacjs GUI @@ -102,10 +102,11 @@ Packages are listed in `lerna.json` and mostly includes: ./scripts/build_node.sh ``` -You can still compile an individual package. +You can still compile an individual package, for example the validation tests: ``` -npx lerna run build --scope="@list/validation-tests" +cd js/tests +yarn build ``` ## Contribute diff --git a/docs/read_only_user.md b/docs/read_only_user.md index fc2e0a7d..9546106f 100644 --- a/docs/read_only_user.md +++ b/docs/read_only_user.md @@ -5,12 +5,13 @@ ## Build ```sh -npx lerna run build --scope="@list/user-read-only-script" +cd js/user-read-only +yarn build ``` ```sh -cd packages/user-read-only -yarn run user-read-only -b http:// -u -p +cd js/user-read-only +yarn run read-only-user -b http:// -u -p ``` - u - The username from the demo user that you want to create. diff --git a/docs/time_tests.md b/docs/time_tests.md index 12ca2df1..e2597731 100644 --- a/docs/time_tests.md +++ b/docs/time_tests.md @@ -5,12 +5,13 @@ Pcaps upload duration time tests for EBU-LIST based on [ebu-list-sdk](https://gi ## Build ```sh -npx lerna run build --scope="@list/validation-tests" +cd js/tests +yarn build ``` ## Tests -from the root_directory: +From the repository root: ```sh cd js/tests diff --git a/docs/validation_tests.md b/docs/validation_tests.md index 14d1b621..018ac3ad 100644 --- a/docs/validation_tests.md +++ b/docs/validation_tests.md @@ -8,13 +8,14 @@ Automated validation tests for EBU-LIST based on [ebu-list-sdk](https://github.c ## Build ```sh -npx lerna run build --scope="@list/validation-tests" +cd js/tests +yarn build ``` ## Tests ```sh -cd packages/tests +cd js/tests yarn run validation-tests-basics -b http:// -u -p yarn run validation-tests-advanced -b http:// -u -p ``` diff --git a/package.json b/package.json index 039766b7..174f8ceb 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,7 @@ { "name": "root", "private": true, - "devDependencies": { - "lerna": "^9.0.0" - }, + "devDependencies": {}, "version": "0.0.0", "workspaces": [ "apps/listwebserver", @@ -17,6 +15,10 @@ "apps/gui-v2/packages/*", "apps/gui-v2" ], + "scripts": { + "start:gui": "cd apps/gui-v2 && yarn start", + "build:gui": "cd apps/gui-v2 && yarn build:production" + }, "resolutions": { "@types/minimatch": "5.1.2", "semver": "^7.3.5", diff --git a/scripts/build_node.sh b/scripts/build_node.sh index 6dff95a2..4edfde6c 100755 --- a/scripts/build_node.sh +++ b/scripts/build_node.sh @@ -11,8 +11,8 @@ echo "Bootstrapping..." yarn install echo "Building..." -npx lerna run build -# to build single package: npx lerna run build --scope="@list/validation-tests" +(cd "$TOP_DIR/apps/gui-v2" && yarn build:production) || exit 1 +(cd "$TOP_DIR/apps/listwebserver" && yarn build) || exit 1 echo "Building GUI..." cd $TOP_DIR/apps/gui-v2/ diff --git a/scripts/run_all.sh b/scripts/run_all.sh index 99807ade..c1097f1b 100755 --- a/scripts/run_all.sh +++ b/scripts/run_all.sh @@ -41,39 +41,10 @@ run () { cd "$SRC/$1" && $2 } -if [ $INSTALL -eq 1 ] ; then - echo "INSTALL" - if ! command -v lerna &> /dev/null - then - echo "Lerna is not installed. Run 'npm i -g lerna'" - exit 1 - fi +if [ $INSTALL -eq 1 ] ; then - cd "$SRC" - - echo "lerna bootstrap..." - lerna bootstrap - if [ $? -ne 0 ]; then - echo "Failed" - exit 1 - fi - echo "Done" - - echo "lerna build" - lerna run build - if [ $? -ne 0 ]; then - echo "Failed" - exit 1 - fi - echo "Done" - - echo "lerna run production" - lerna run production - if [ $? -ne 0 ]; then - echo "Failed" - exit 1 - fi - echo "Done" + (cd "$SRC/apps/listwebserver" && yarn build) || exit 1 + (cd "$SRC/apps/gui-v2" && yarn build:production) || exit 1 fi if [ $RUN_ALL -eq 1 ] ; then diff --git a/scripts/setup_build_env.sh b/scripts/setup_build_env.sh index 54ffc8d4..26347d5b 100755 --- a/scripts/setup_build_env.sh +++ b/scripts/setup_build_env.sh @@ -28,7 +28,6 @@ pip install conan curl -sL https://deb.nodesource.com/setup_12.x | bash - apt-get install -y nodejs -npm i -g lerna npm i -g yarn echo "Please install FFMPEG v2.8 or newer using your package manager or https://www.ffmpeg.org/download.html" From 8cea311236961671ae9303dab6fcf526348676f3 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 15:58:26 +0000 Subject: [PATCH 21/27] Deps: axios v1.8.2 directly in apps --- apps/gui-v2/package.json | 1 + apps/gui-v2/packages/react-app/craco.config.js | 8 ++++++++ apps/listwebserver/package.json | 2 +- package.json | 1 - 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json index bc633022..068d818e 100644 --- a/apps/gui-v2/package.json +++ b/apps/gui-v2/package.json @@ -47,6 +47,7 @@ "dependencies": { "@wavesurfer/react": "^1.0.11", "assert": "^2.1.0", + "axios": "^1.8.2", "buffer": "^6.0.3", "csv-parse": "^6.1.0", "customize-cra": "^1.0.0", diff --git a/apps/gui-v2/packages/react-app/craco.config.js b/apps/gui-v2/packages/react-app/craco.config.js index f2c6f51c..994aafd6 100644 --- a/apps/gui-v2/packages/react-app/craco.config.js +++ b/apps/gui-v2/packages/react-app/craco.config.js @@ -32,6 +32,14 @@ module.exports = { }) ); + // Ensure .cjs files are parsed as JavaScript (prevents axios CJS from being treated as an asset) + config.module = config.module || {}; + config.module.rules = config.module.rules || []; + config.module.rules.push({ + test: /\.cjs$/, + type: 'javascript/auto', + }); + return config; }, }, diff --git a/apps/listwebserver/package.json b/apps/listwebserver/package.json index d7a7df4e..2a82c640 100644 --- a/apps/listwebserver/package.json +++ b/apps/listwebserver/package.json @@ -17,7 +17,7 @@ "abr-xcorr": "^1.0.0", "amqplib": "^0.5.5", "archiver": "^3.1.1", - "axios": "^0.19.0", + "axios": "^1.8.2", "base64-min": "^2.0.0", "bcrypt": "^5.0.0", "body-parser": "^1.19.0", diff --git a/package.json b/package.json index 174f8ceb..fd86093b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "mongoose": "^6.13.6", "form-data": "^2.5.4", "url-parse": "^1.5.10", - "axios": "^1.8.2", "multer": "^2.0.2", "jsonwebtoken": "^9.0.0", "follow-redirects": "^1.15.6", From 04f53a94a094cddcfdbf5defc226b9ce65c551fb Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 16:14:43 +0000 Subject: [PATCH 22/27] Deps: Upgrade @types/react+react-dom directly in apps package.json --- apps/gui-v2/packages/components/package.json | 4 ++-- apps/gui-v2/packages/react-app/package.json | 8 ++++---- package.json | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/gui-v2/packages/components/package.json b/apps/gui-v2/packages/components/package.json index 77ae9eea..dfc13918 100644 --- a/apps/gui-v2/packages/components/package.json +++ b/apps/gui-v2/packages/components/package.json @@ -6,8 +6,8 @@ "peerDependencies": { "@types/jest": "24.0.19", "@types/node": "12.11.1", - "@types/react": "^18.0.1", - "@types/react-dom": "^18.0.0", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", "customize-cra": "^0.8.0", "enzyme": "^3.10.0", "react": "^18.0.2", diff --git a/apps/gui-v2/packages/react-app/package.json b/apps/gui-v2/packages/react-app/package.json index 2f50ca3a..c8045d2d 100644 --- a/apps/gui-v2/packages/react-app/package.json +++ b/apps/gui-v2/packages/react-app/package.json @@ -28,8 +28,8 @@ }, "devDependencies": { "@craco/craco": "^7.1.0", - "@types/react": "^18.0.1", - "@types/react-dom": "^18.0.0", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", "react-scripts": "^5.0.1", "webpack": "^5.2.1", "webpack-dev-server": "5.2.1" @@ -40,8 +40,8 @@ "@types/lodash": "^4.14.176", "@types/node": "12.11.1", "@types/query-string": "^6.3.0", - "@types/react": "^18.0.1", - "@types/react-dom": "18.0.0", + "@types/react": "^18.2.21", + "@types/react-dom": "18.2.7", "@types/react-router-dom": "^5.3.3", "@types/smpte-timecode": "^1.2.1", "@types/wavesurfer.js": "^6.0.12", diff --git a/package.json b/package.json index fd86093b..22616775 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,6 @@ "resolutions": { "@types/minimatch": "5.1.2", "semver": "^7.3.5", - "@types/react": "^18.2.21", - "@types/react-dom": "^18.2.7", "mongoose": "^6.13.6", "form-data": "^2.5.4", "url-parse": "^1.5.10", From c4f04856fcb95aba4cccdcccfe942caaf95d597a Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 16:15:00 +0000 Subject: [PATCH 23/27] Deps: Remove mongoose resolution from workspace package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 22616775..7156d403 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "resolutions": { "@types/minimatch": "5.1.2", "semver": "^7.3.5", - "mongoose": "^6.13.6", "form-data": "^2.5.4", "url-parse": "^1.5.10", "multer": "^2.0.2", From 1773932008d528f722624296412ce2b521fd7246 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 16:15:24 +0000 Subject: [PATCH 24/27] Deps: Move npm-license-crawler to DevDependency --- apps/gui-v2/package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json index 068d818e..ba2c278c 100644 --- a/apps/gui-v2/package.json +++ b/apps/gui-v2/package.json @@ -38,6 +38,7 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^7.0.1", "jest": "^30.2.0", + "npm-license-crawler": "^0.2.1", "prettier": "^3.6.2", "sass": "^1.94.0", "sass-loader": "^16.0.6", @@ -60,7 +61,6 @@ "lodash": "^4.17.21", "luxon": "^3.7.2", "node-polyglot": "^2.6.0", - "npm-license-crawler": "^0.2.1", "os": "^0.1.2", "os-browserify": "^0.3.0", "process": "^0.11.10", @@ -107,8 +107,7 @@ "resolutions": { "nth-check": "^2.0.1", "postcss": "^8.4.31", - "webpack-dev-server": "^5.2.1", - "semver": "^5.7.2" + "webpack-dev-server": "^5.2.1" }, "browserslist": { "production": [ From 50d2139225a3f4a9d1a36cfa81f36f8c05149d53 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 16:16:27 +0000 Subject: [PATCH 25/27] Deps: Remove jsonwebtoken from workspace resolutions, updated credits --- apps/gui/data/credits/credits.json | 2 +- package.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/gui/data/credits/credits.json b/apps/gui/data/credits/credits.json index b6be6785..a81baa71 100644 --- a/apps/gui/data/credits/credits.json +++ b/apps/gui/data/credits/credits.json @@ -153,7 +153,7 @@ "licenseUrl": "https://github.com/benjamn/install/raw/master/LICENSE", "parents": "list-ebu-gui" }, - "jsonwebtoken@8.5.1": { + "jsonwebtoken@9.0.3": { "licenses": "MIT", "repository": "https://github.com/auth0/node-jsonwebtoken", "licenseUrl": "https://github.com/auth0/node-jsonwebtoken/raw/master/LICENSE", diff --git a/package.json b/package.json index 7156d403..ea063379 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "form-data": "^2.5.4", "url-parse": "^1.5.10", "multer": "^2.0.2", - "jsonwebtoken": "^9.0.0", "follow-redirects": "^1.15.6", "dicer": "^0.3.0", "nth-check": "^2.1.1", From e8c20453c66edca8a9bc90006d7883d7d5f70517 Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 16 Dec 2025 17:24:14 +0000 Subject: [PATCH 26/27] More dep fixes --- apps/gui-v2/packages/components/package.json | 42 ++++++++++---------- apps/gui-v2/packages/react-app/package.json | 13 ++---- apps/gui/data/credits/credits.json | 2 +- package.json | 6 +-- 4 files changed, 27 insertions(+), 36 deletions(-) diff --git a/apps/gui-v2/packages/components/package.json b/apps/gui-v2/packages/components/package.json index dfc13918..7796a086 100644 --- a/apps/gui-v2/packages/components/package.json +++ b/apps/gui-v2/packages/components/package.json @@ -3,41 +3,39 @@ "version": "0.1.0", "private": true, "main": "src/", - "peerDependencies": { + "devDependencies": { + "@babel/core": "^7.12.10", + "@storybook/addon-actions": "^6.1.11", + "@storybook/addon-essentials": "^6.1.11", + "@storybook/addon-links": "^6.1.11", + "@storybook/preset-scss": "^1.0.3", + "@storybook/react": "^6.1.11", "@types/jest": "24.0.19", "@types/node": "12.11.1", "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", + "@types/react-image-gallery": "^1.0.1", + "@types/react-custom-scrollbars": "^4.0.7", + "babel-loader": "^8.2.2", + "css-loader": "^5.0.1", "customize-cra": "^0.8.0", "enzyme": "^3.10.0", + "file-loader": "^6.2.0", "react": "^18.0.2", + "react-custom-scrollbars": "^4.2.1", "react-dom": "^18.2.7", - "react-scripts": "^5.0.1", "react-dropzone": "^14.3.8", - "typescript": "^4.0.3", - "recharts": ">=2.0.3", - "tippy.js": "^6.2.7", "react-image-gallery": "^1.0.8", - "@types/react-image-gallery": "^1.0.1", - "react-custom-scrollbars": "^4.2.1", - "@types/react-custom-scrollbars": "^4.0.7", - "file-loader": "^6.2.0", - "svg-url-loader": ">=7.1.1", + "react-scripts": "^5.0.1", + "react-toastify": "^7.0.4", "rc-scrollbars": "^1.1.2", - "react-toastify": "^7.0.4" - }, - "devDependencies": { - "@babel/core": "^7.12.10", - "@storybook/addon-actions": "^6.1.11", - "@storybook/addon-essentials": "^6.1.11", - "@storybook/addon-links": "^6.1.11", - "@storybook/preset-scss": "^1.0.3", - "@storybook/react": "^6.1.11", - "babel-loader": "^8.2.2", - "css-loader": "^5.0.1", + "recharts": ">=2.0.3", + "sass": "1.50.0", "sass-loader": "^10.1.0", "style-loader": "^2.0.0", - "sass": "1.50.0" + "svg-url-loader": ">=7.1.1", + "tippy.js": "^6.2.7", + "typescript": "^4.0.3" }, "dependencies": { "bulma": "^0.9.1", diff --git a/apps/gui-v2/packages/react-app/package.json b/apps/gui-v2/packages/react-app/package.json index c8045d2d..5284dbf7 100644 --- a/apps/gui-v2/packages/react-app/package.json +++ b/apps/gui-v2/packages/react-app/package.json @@ -5,6 +5,7 @@ "dependencies": { "@bisect/ebu-list-sdk": "^0.1.0", "@ebu-list/translations": "^0.1.0", + "@wavesurfer/react": "^1.0.11", "assert": "^2.0.0", "buffer": "^6.0.3", "bulma": "^0.9.1", @@ -13,9 +14,9 @@ "os": "^0.1.2", "os-browserify": "^0.3.0", "process": "^0.11.10", - "react": "^18.0.0", + "react": "^18.3.1", "react-circular-progressbar": "^2.0.3", - "react-dom": "^18.0.0", + "react-dom": "^18.3.1", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "url": "^0.11.0" @@ -27,14 +28,6 @@ "eject": "react-scripts eject" }, "devDependencies": { - "@craco/craco": "^7.1.0", - "@types/react": "^18.2.21", - "@types/react-dom": "^18.2.7", - "react-scripts": "^5.0.1", - "webpack": "^5.2.1", - "webpack-dev-server": "5.2.1" - }, - "peerDependencies": { "@craco/craco": "^7.1.0", "@types/jest": "24.0.19", "@types/lodash": "^4.14.176", diff --git a/apps/gui/data/credits/credits.json b/apps/gui/data/credits/credits.json index a81baa71..cd6ed7ea 100644 --- a/apps/gui/data/credits/credits.json +++ b/apps/gui/data/credits/credits.json @@ -399,7 +399,7 @@ "licenseUrl": "https://github.com/uuidjs/uuid/raw/master/LICENSE.md", "parents": "list-ebu-gui" }, - "validator@12.2.0": { + "validator@13.15.23": { "licenses": "MIT", "repository": "https://github.com/chriso/validator.js", "licenseUrl": "https://github.com/chriso/validator.js/raw/master/LICENSE", diff --git a/package.json b/package.json index ea063379..7c50aad8 100644 --- a/package.json +++ b/package.json @@ -21,20 +21,20 @@ }, "resolutions": { "@types/minimatch": "5.1.2", - "semver": "^7.3.5", + "@types/react": "18.3.27", + "@types/react-dom": "18.3.0", "form-data": "^2.5.4", "url-parse": "^1.5.10", "multer": "^2.0.2", "follow-redirects": "^1.15.6", "dicer": "^0.3.0", "nth-check": "^2.1.1", + "node-forge": "^1.3.2", "debug": "^4.3.4", "webpack-dev-server": "^5.2.1", "postcss": "^8.4.31", "js-yaml": "^3.14.1", - "tmp": "^0.2.4", "cookie": "^0.7.0", - "validator": "^13.15.20", "fast-xml-parser": "^4.1.2", "tough-cookie": "^4.1.3" } From 34f648356a251080378e8a404022188bfd9e4d7b Mon Sep 17 00:00:00 2001 From: Guillaume Domingues Date: Tue, 6 Jan 2026 15:42:09 +0000 Subject: [PATCH 27/27] Updated bisect-core-ts submodule commit --- third_party/bisect-core-ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/bisect-core-ts b/third_party/bisect-core-ts index b4c209b2..0436256c 160000 --- a/third_party/bisect-core-ts +++ b/third_party/bisect-core-ts @@ -1 +1 @@ -Subproject commit b4c209b2720c61913c42544d6469ed61f4b608cd +Subproject commit 0436256cac2a16c02dff3316a6f8b709ff0156a2