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
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
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
diff --git a/apps/gui-v2/package.json b/apps/gui-v2/package.json
index 7de25910..ba2c278c 100644
--- a/apps/gui-v2/package.json
+++ b/apps/gui-v2/package.json
@@ -2,91 +2,93 @@
"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": {
- "@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": "^3.18.1",
- "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",
+ "npm-license-crawler": "^0.2.1",
+ "prettier": "^3.6.2",
+ "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",
+ "axios": "^1.8.2",
"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",
- "npm-license-crawler": "^0.2.1",
+ "luxon": "^3.7.2",
+ "node-polyglot": "^2.6.0",
"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 +104,11 @@
"packages/*/src/**/*.{js,jsx,ts,tsx}"
]
},
+ "resolutions": {
+ "nth-check": "^2.0.1",
+ "postcss": "^8.4.31",
+ "webpack-dev-server": "^5.2.1"
+ },
"browserslist": {
"production": [
">0.2%",
diff --git a/apps/gui-v2/packages/components/package.json b/apps/gui-v2/packages/components/package.json
index f3bcf592..7796a086 100644
--- a/apps/gui-v2/packages/components/package.json
+++ b/apps/gui-v2/packages/components/package.json
@@ -3,29 +3,6 @@
"version": "0.1.0",
"private": true,
"main": "src/",
- "peerDependencies": {
- "@types/jest": "24.0.19",
- "@types/node": "12.11.1",
- "@types/react": "^18.0.1",
- "@types/react-dom": "16.9.2",
- "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",
- "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",
- "rc-scrollbars": "^1.1.2",
- "react-toastify": "^7.0.4"
- },
"devDependencies": {
"@babel/core": "^7.12.10",
"@storybook/addon-actions": "^6.1.11",
@@ -33,11 +10,32 @@
"@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-dropzone": "^14.3.8",
+ "react-image-gallery": "^1.0.8",
+ "react-scripts": "^5.0.1",
+ "react-toastify": "^7.0.4",
+ "rc-scrollbars": "^1.1.2",
+ "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/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/craco.config.js b/apps/gui-v2/packages/react-app/craco.config.js
new file mode 100644
index 00000000..994aafd6
--- /dev/null
+++ b/apps/gui-v2/packages/react-app/craco.config.js
@@ -0,0 +1,101 @@
+/* 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'],
+ })
+ );
+
+ // 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;
+ },
+ },
+ 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 b3c42e8e..5284dbf7 100644
--- a/apps/gui-v2/packages/react-app/package.json
+++ b/apps/gui-v2/packages/react-app/package.json
@@ -4,57 +4,62 @@
"private": true,
"dependencies": {
"@bisect/ebu-list-sdk": "^0.1.0",
- "bulma": "^0.9.1",
- "react-circular-progressbar": "^2.0.3",
"@ebu-list/translations": "^0.1.0",
- "fs": "^0.0.1-security",
+ "@wavesurfer/react": "^1.0.11",
"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.3.1",
+ "react-circular-progressbar": "^2.0.3",
+ "react-dom": "^18.3.1",
"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"
},
- "peerDependencies": {
- "@types/query-string": "^6.3.0",
- "node-polyglot": "^2.4.0",
+ "devDependencies": {
+ "@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/react": "^18.0.1",
- "@types/react-dom": "18.0.0",
+ "@types/query-string": "^6.3.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",
"customize-cra": "^0.8.0",
"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",
- "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"
}
-}
\ No newline at end of file
+}
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/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/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;
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/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/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx b/apps/gui-v2/packages/react-app/src/pages/PCapDetails/StreamExplorerPage/Audio/AudioPlayer.tsx
index a0aedfc1..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
@@ -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';
@@ -28,126 +29,92 @@ function timeInterval(pxPerSec: number) {
return retval;
}
-const createWaveSurfer = (waveform: any) => {
- const wavesurfer = WaveSurfer.create({
- 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: [
- TimelinePlugin.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 = 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');
@@ -156,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 9a7667d5..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,17 +38,14 @@ 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;
React.useEffect(() => {
- if (wsClient === (null || undefined)) {
+ if (wsClient == null) {
return;
}
const handleMessage = (msg: any) => {
@@ -92,7 +89,7 @@ function AudioPlayerDisplay({
{mp3Url !== '' ? (
-
+
) : (
ERROR: MP3_FILE_FAILED
)}
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/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]);
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..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 !== (undefined || 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;
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/gui/data/credits/credits.json b/apps/gui/data/credits/credits.json
index b6be6785..cd6ed7ea 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",
@@ -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/apps/listwebserver/package.json b/apps/listwebserver/package.json
index 5d2f0cad..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",
@@ -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..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;
@@ -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/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/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/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;
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();
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/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':
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/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..7c50aad8 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,41 @@
{
"name": "root",
"private": true,
- "devDependencies": {
- "lerna": "^4.0.0"
+ "devDependencies": {},
+ "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"
+ ],
+ "scripts": {
+ "start:gui": "cd apps/gui-v2 && yarn start",
+ "build:gui": "cd apps/gui-v2 && yarn build:production"
},
- "version": "0.0.0"
+ "resolutions": {
+ "@types/minimatch": "5.1.2",
+ "@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",
+ "cookie": "^0.7.0",
+ "fast-xml-parser": "^4.1.2",
+ "tough-cookie": "^4.1.3"
+ }
}
diff --git a/scripts/build_node.sh b/scripts/build_node.sh
index f31d8536..4edfde6c 100755
--- a/scripts/build_node.sh
+++ b/scripts/build_node.sh
@@ -9,15 +9,14 @@ cd $TOP_DIR
echo "Bootstrapping..."
yarn install
-npx lerna bootstrap
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/
-yarn run build:production
+yarn run build:production --verbose
echo "Done"
diff --git a/scripts/deploy/artifacts/listwebserver/Dockerfile b/scripts/deploy/artifacts/listwebserver/Dockerfile
index 20ab11c2..2701f3e7 100644
--- a/scripts/deploy/artifacts/listwebserver/Dockerfile
+++ b/scripts/deploy/artifacts/listwebserver/Dockerfile
@@ -1,15 +1,22 @@
# 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 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
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
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"
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