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