From f2631ea5b9771265178fa654f6971078e5b94173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Piechnik?= Date: Wed, 4 Jun 2025 20:19:11 +0200 Subject: [PATCH 01/24] feat: add configuration endpoint --- Control/lib/api.js | 7 ++ .../controllers/Configuration.controller.js | 95 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 Control/lib/controllers/Configuration.controller.js diff --git a/Control/lib/api.js b/Control/lib/api.js index 667936635..9ede775fc 100644 --- a/Control/lib/api.js +++ b/Control/lib/api.js @@ -33,6 +33,7 @@ const { } = require('./middleware/getDetectorsLockOwnershipMiddlewareFactory.js'); // controllers +const {ConfigurationController} = require('./controllers/Configuration.controller.js'); const {ConsulController} = require('./controllers/Consul.controller.js'); const {EnvironmentController} = require('./controllers/Environment.controller.js'); const {LockController} = require('./controllers/Lock.controller.js'); @@ -90,6 +91,9 @@ module.exports.setup = (http, ws) => { const broadcastService = new BroadcastService(ws); const cacheService = new CacheService(broadcastService); const environmentCacheService = new EnvironmentCacheService(broadcastService, eventEmitter); + + const configurationController = new ConfigurationController(consulService, config.consul); + configurationController.testConsulStatus(); const consulController = new ConsulController(consulService, config.consul); consulController.testConsulStatus(); @@ -230,6 +234,9 @@ module.exports.setup = (http, ws) => { statusController.getAliECSIntegratedServicesStatus.bind(statusController), ); + // Configuration + http.get('/configurations', configurationController.getConfigurations.bind(configurationController), {public: true}); + // Consul const validateService = consulController.validateService.bind(consulController); http.get('/consul/flps', validateService, consulController.getFLPs.bind(consulController)); diff --git a/Control/lib/controllers/Configuration.controller.js b/Control/lib/controllers/Configuration.controller.js new file mode 100644 index 000000000..109d1ff03 --- /dev/null +++ b/Control/lib/controllers/Configuration.controller.js @@ -0,0 +1,95 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const { LogManager, LogLevel } = require("@aliceo2/web-ui"); +const { errorHandler, errorLogger } = require("../utils.js"); +const { getConsulConfig } = require("../config/publicConfigProvider.js"); + +/** + * Gateway for all Consul Consumer calls + */ +class ConfigurationController { + /** + * Setup ConfigurationController + * @param {ConsulService} consulService + * @param {JSON} config + */ + constructor(consulService, config) { + this.consulService = consulService; + this.config = getConsulConfig({ consul: config }); + this.configurationsPath = `${this.config.qcPath}/ANY/any`; + + this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cog"}/consul`); + } + + /** + * Check if consulService is present: + * * If yes, allow request to continue + * * If not, send response accordingly + * @param {Request} req + * @param {Response} res + * @param {Next} next + */ + validateService(req, res, next) { + if (this.consulService) { + next(); + } else { + errorHandler("Unable to retrieve configuration of consul service", res, 502); + } + } + + /** + * Method to check if consul service can be used + */ + async testConsulStatus() { + this.consulService + .getConsulLeaderStatus() + .then((data) => this._logger.info(`Service is up and running on: ${data}`)) + .catch((error) => this._logger.error(`Connection failed due to ${error}`)); + } + + /** + * Get configurations from Consul + * @param {Request} req + * @param {Response} res + */ + async getConfigurations(req, res) { + const prefix = req.query.prefix; + const recurse = req.query.recurse === "true"; + const prefixPath = prefix ? `${this.configurationsPath}/${prefix}` : this.configurationsPath; + try { + const data = await this.consulService.getOnlyRawValuesByKeyPrefix(prefixPath); + const parsedData = Object.entries(data || {}) + .map(([key, value]) => { + try { + return { + key, + value: JSON.parse(value), + }; + } catch (e) { + return undefined; + } + }) + .filter((item) => item !== undefined) + .filter((item) => recurse || !item.key.replace(`${prefixPath}/`, "").includes("/")); + + res.status(200).json(parsedData); + } catch (error) { + errorLogger(error, this._logger); + errorHandler("Error retrieving configurations", res, 500); + } + } +} + +exports.ConfigurationController = ConfigurationController; From 63e0cb8d1cce2abe98fcf43155741fdbd908a547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Tue, 10 Jun 2025 18:44:54 +0200 Subject: [PATCH 02/24] chore: implement base application layout --- Configuration/package-lock.json | 6 + .../webapp/app/components/layout/Content.tsx | 17 + .../webapp/app/components/layout/Header.tsx | 7 + .../app/components/layout/LeftDrawer.tsx | 47 + .../app/components/layout/MainLayout.tsx | 31 + Configuration/webapp/app/root.tsx | 51 +- Configuration/webapp/package-lock.json | 1082 +++++++++++------ Configuration/webapp/package.json | 9 +- 8 files changed, 865 insertions(+), 385 deletions(-) create mode 100644 Configuration/package-lock.json create mode 100644 Configuration/webapp/app/components/layout/Content.tsx create mode 100644 Configuration/webapp/app/components/layout/Header.tsx create mode 100644 Configuration/webapp/app/components/layout/LeftDrawer.tsx create mode 100644 Configuration/webapp/app/components/layout/MainLayout.tsx diff --git a/Configuration/package-lock.json b/Configuration/package-lock.json new file mode 100644 index 000000000..17a2e204b --- /dev/null +++ b/Configuration/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "Configuration", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/Configuration/webapp/app/components/layout/Content.tsx b/Configuration/webapp/app/components/layout/Content.tsx new file mode 100644 index 000000000..f5ab7f3cc --- /dev/null +++ b/Configuration/webapp/app/components/layout/Content.tsx @@ -0,0 +1,17 @@ +import { Box, Toolbar, Typography } from '@mui/material'; +import React, { type FC, type PropsWithChildren } from 'react'; + +interface ContentProps extends PropsWithChildren {} + +const Content: FC = ({ children }) => { + return ( + + + Test + + {children} + + ); +}; + +export default Content; diff --git a/Configuration/webapp/app/components/layout/Header.tsx b/Configuration/webapp/app/components/layout/Header.tsx new file mode 100644 index 000000000..e4c90bd5d --- /dev/null +++ b/Configuration/webapp/app/components/layout/Header.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const Header = () => { + return
Header
; +}; + +export default Header; diff --git a/Configuration/webapp/app/components/layout/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/LeftDrawer.tsx new file mode 100644 index 000000000..d9b2b4e20 --- /dev/null +++ b/Configuration/webapp/app/components/layout/LeftDrawer.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { + Box, + Drawer, + List, + ListItem, + ListItemText, + Toolbar, + Typography, +} from '@mui/material'; + +const DRAWER_WIDTH = 200; + +const LeftDrawer = () => { + return ( + + + + + {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( + + + + ))} + + + + Footer Content Here + + + ); +}; + +export default LeftDrawer; diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx new file mode 100644 index 000000000..d0bacf3d7 --- /dev/null +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -0,0 +1,31 @@ +import { Box } from '@mui/material'; +import React, { type FC, type PropsWithChildren } from 'react'; +import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; +import LeftDrawer from './LeftDrawer'; +import Content from './Content'; +import Header from './Header'; + +interface MainLayoutProps extends PropsWithChildren {} + +const MainLayout: FC = ({ children }) => { + return ( + + + + + + + + + + + {children} + + + + + + ); +}; + +export default MainLayout; diff --git a/Configuration/webapp/app/root.tsx b/Configuration/webapp/app/root.tsx index 460caaae3..dad21aac2 100644 --- a/Configuration/webapp/app/root.tsx +++ b/Configuration/webapp/app/root.tsx @@ -1,39 +1,14 @@ -import { - isRouteErrorResponse, - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, useNavigation, -} from "react-router"; +import { isRouteErrorResponse, Outlet } from 'react-router'; -import type { Route } from "./+types/root"; -import "./app.css"; -import "@aliceo2/web-ui/Frontend/css/src/bootstrap.css" -import {Navbar} from "~/ui/navbar"; -import {Spinner} from "~/ui/spinner"; +import type { Route } from './+types/root'; +import './app.css'; +import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css'; +import { Spinner } from '~/ui/spinner'; -export function Layout({ children }: { children: React.ReactNode }) { - const { state } = useNavigation(); +import MainLayout from './components/layout/MainLayout'; - return ( - - - - - - - - - -
- {state === "loading" ? : children} -
- - - - - ); +export function Layout({ children }: { children: React.ReactNode }) { + return {children}; } export default function App() { @@ -41,19 +16,19 @@ export default function App() { } export function HydrateFallback() { - return + return ; } export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - let message = "Oops!"; - let details = "An unexpected error occurred."; + let message = 'Oops!'; + let details = 'An unexpected error occurred.'; let stack: string | undefined; if (isRouteErrorResponse(error)) { - message = error.status === 404 ? "404" : "Error"; + message = error.status === 404 ? '404' : 'Error'; details = error.status === 404 - ? "The requested page could not be found." + ? 'The requested page could not be found.' : error.statusText || details; } else if (import.meta.env.DEV && error && error instanceof Error) { details = error.message; diff --git a/Configuration/webapp/package-lock.json b/Configuration/webapp/package-lock.json index 1128840c8..241fb21a3 100644 --- a/Configuration/webapp/package-lock.json +++ b/Configuration/webapp/package-lock.json @@ -7,14 +7,17 @@ "name": "@aliceo2/test-react-webapp", "dependencies": { "@aliceo2/web-ui": "^2.7.4", - "@react-router/node": "^7.5.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/material": "^7.1.1", + "@react-router/node": "7.5.3", "isbot": "^5", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router": "^7.5.2" + "react-router": "7.5.3" }, "devDependencies": { - "@react-router/dev": "^7.5.0", + "@react-router/dev": "7.5.3", "@types/node": "^20", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", @@ -61,7 +64,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", @@ -127,7 +129,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.27.0", @@ -230,7 +231,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", - "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", @@ -317,7 +317,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -327,7 +326,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -361,7 +359,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.0" @@ -478,11 +475,19 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -497,7 +502,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -516,7 +520,6 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -546,6 +549,167 @@ "kuler": "^2.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.2.tgz", @@ -993,7 +1157,6 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -1008,7 +1171,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1018,7 +1180,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1028,14 +1189,12 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1048,6 +1207,213 @@ "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", "license": "MIT" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.1.tgz", + "integrity": "sha512-yBckQs4aQ8mqukLnPC6ivIRv6guhaXi8snVl00VtyojBbm+l6VbVhyTSZ68Abcx7Ah8B+GZhrB7BOli+e+9LkQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.1.tgz", + "integrity": "sha512-mTpdmdZCaHCGOH3SrYM41+XKvNL0iQfM9KlYgpSjgadXx/fEKhhvOktxm8++Xw6FFeOHoOiV+lzOI8X1rsv71A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/core-downloads-tracker": "^7.1.1", + "@mui/system": "^7.1.1", + "@mui/types": "^7.4.3", + "@mui/utils": "^7.1.1", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.1.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^7.1.1", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.1.tgz", + "integrity": "sha512-M8NbLUx+armk2ZuaxBkkMk11ultnWmrPlN0Xe3jUEaBChg/mcxa5HWIWS1EE4DF36WRACaAHVAvyekWlDQf0PQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/utils": "^7.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.1.tgz", + "integrity": "sha512-R2wpzmSN127j26HrCPYVQ53vvMcT5DaKLoWkrfwUYq3cYytL6TQrCH8JBH3z79B6g4nMZZVoaXrxO757AlShaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.1.tgz", + "integrity": "sha512-Kj1uhiqnj4Zo7PDjAOghtXJtNABunWvhcRU0O7RQJ7WOxeynoH6wXPcilphV8QTFtkKaip8EiNJRiCD+B3eROA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/private-theming": "^7.1.1", + "@mui/styled-engine": "^7.1.1", + "@mui/types": "^7.4.3", + "@mui/utils": "^7.1.1", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz", + "integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.1.tgz", + "integrity": "sha512-BkOt2q7MBYl7pweY2JWwfrlahhp+uGLR8S+EhiyRaofeRYUWL2YKbSGQvN4hgSN1i8poN0PaUiii1kEMrchvzg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1", + "@mui/types": "^7.4.3", + "@types/prop-types": "^15.7.14", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.1.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@npmcli/git": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-4.1.0.tgz", @@ -1121,10 +1487,20 @@ "node": ">=14" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@react-router/dev": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.5.0.tgz", - "integrity": "sha512-BSxuuMtm2YSwOzNTz9PJMFQzC33CzS6e6FXI/s/ZkcZIuYkujssQVG5q5nAuriKK17/T1xFLNeSoXVNI/+zCLA==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.5.3.tgz", + "integrity": "sha512-U+n8JYAREKg6eHIAXCjazsYlwPo/vcAbShpqePnDBUdDnePBwZ2JmoqhWV+7tIhyHvvHGQKlw6BcrSZtF549WQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1137,7 +1513,7 @@ "@babel/traverse": "^7.23.2", "@babel/types": "^7.22.5", "@npmcli/package-json": "^4.0.1", - "@react-router/node": "7.5.0", + "@react-router/node": "7.5.3", "arg": "^5.0.1", "babel-dead-code-elimination": "^1.0.6", "chokidar": "^4.0.0", @@ -1163,8 +1539,8 @@ "node": ">=20.0.0" }, "peerDependencies": { - "@react-router/serve": "^7.5.0", - "react-router": "^7.5.0", + "@react-router/serve": "^7.5.3", + "react-router": "^7.5.3", "typescript": "^5.1.0", "vite": "^5.1.0 || ^6.0.0", "wrangler": "^3.28.2 || ^4.0.0" @@ -1181,31 +1557,6 @@ } } }, - "node_modules/@react-router/dev/node_modules/@react-router/node": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.5.0.tgz", - "integrity": "sha512-jvTledQVx8BqHt2hhkW0LxPiYsAqrNGRI1rjttr0fcynu1lQFU3HzKtZx9z9e+0fyqTKtwOOnaMIgGqdlpFPNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@mjackson/node-fetch-server": "^0.2.0", - "source-map-support": "^0.5.21", - "stream-slice": "^0.1.2", - "undici": "^6.19.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react-router": "7.5.0", - "typescript": "^5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@react-router/node": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.5.3.tgz", @@ -1230,85 +1581,6 @@ } } }, - "node_modules/@react-router/serve": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@react-router/serve/-/serve-7.5.0.tgz", - "integrity": "sha512-0ifLtFCcuhW57oPNFvdL0rA+xVuICVV8T/tfXm+qjgeTl/I72y+Y5rYrIb2MqIseT8OMQADRKA3ERRwKFFrYdw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-router/express": "7.5.0", - "@react-router/node": "7.5.0", - "compression": "^1.7.4", - "express": "^4.19.2", - "get-port": "5.1.1", - "morgan": "^1.10.0", - "source-map-support": "^0.5.21" - }, - "bin": { - "react-router-serve": "bin.js" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react-router": "7.5.0" - } - }, - "node_modules/@react-router/serve/node_modules/@react-router/express": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@react-router/express/-/express-7.5.0.tgz", - "integrity": "sha512-GWlvMn6ap2Fcm4KvL73dR0ue3yUUSKYVPVhJCWClYmJYUJz1Pb67zRNdKAGgKRypSB8dZJloZkxq21q8wl1KEQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@react-router/node": "7.5.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "express": "^4.17.1 || ^5", - "react-router": "7.5.0", - "typescript": "^5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@react-router/serve/node_modules/@react-router/node": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@react-router/node/-/node-7.5.0.tgz", - "integrity": "sha512-jvTledQVx8BqHt2hhkW0LxPiYsAqrNGRI1rjttr0fcynu1lQFU3HzKtZx9z9e+0fyqTKtwOOnaMIgGqdlpFPNQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@mjackson/node-fetch-server": "^0.2.0", - "source-map-support": "^0.5.21", - "stream-slice": "^0.1.2", - "undici": "^6.19.2" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react-router": "7.5.0", - "typescript": "^5.1.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", @@ -1606,11 +1878,22 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -1626,6 +1909,15 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -1712,36 +2004,27 @@ "@babel/types": "^7.23.6" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "safe-buffer": "5.1.2" + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" }, "engines": { - "node": ">= 0.8" + "node": ">=10", + "npm": ">=6" } }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/bignumber.js": { "version": "9.0.0", @@ -1894,6 +2177,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001713", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", @@ -1931,6 +2223,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -1969,88 +2270,31 @@ "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } - }, - "node_modules/color/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "license": "MIT" - }, - "node_modules/colorspace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", - "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", - "license": "MIT", - "dependencies": { - "color": "^3.1.3", - "text-hex": "1.0.x" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.0.2", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + }, + "node_modules/color/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "license": "MIT", - "optional": true, - "peer": true, "dependencies": { - "ms": "2.0.0" + "color-name": "1.1.3" } }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, + "node_modules/color/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", "license": "MIT", - "optional": true, - "peer": true + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } }, "node_modules/content-disposition": { "version": "0.5.4", @@ -2101,6 +2345,31 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2136,14 +2405,12 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2191,6 +2458,16 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2263,6 +2540,21 @@ "dev": true, "license": "MIT" }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-ex/node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2357,6 +2649,18 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2494,6 +2798,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -2608,21 +2918,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-port": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz", - "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -2661,7 +2956,6 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -2726,6 +3020,21 @@ "node": ">=18.0.0" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "6.1.3", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.3.tgz", @@ -2777,6 +3086,22 @@ "node": ">=0.10.0" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2802,7 +3127,6 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2899,14 +3223,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3009,6 +3331,12 @@ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", "license": "MIT" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3075,6 +3403,18 @@ "node": ">= 12.0.0" } }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3133,18 +3473,6 @@ "node": ">=4" } }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", @@ -3202,61 +3530,6 @@ "ospec": "ospec/bin/ospec" } }, - "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3303,18 +3576,6 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3393,6 +3654,15 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3426,18 +3696,6 @@ "node": ">= 0.8" } }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3496,6 +3754,42 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3515,6 +3809,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", @@ -3545,6 +3845,15 @@ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -3556,7 +3865,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3654,6 +3962,23 @@ "node": ">=10" } }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -3727,6 +4052,12 @@ "react": "^19.1.0" } }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3738,9 +4069,9 @@ } }, "node_modules/react-router": { - "version": "7.5.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.2.tgz", - "integrity": "sha512-9Rw8r199klMnlGZ8VAsV/I8WrIF6IyJ90JQUdboupx1cdkgYqwnrYjH+I/nY/7cA1X5zia4mDJqH36npP7sxGQ==", + "version": "7.5.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.5.3.tgz", + "integrity": "sha512-3iUDM4/fZCQ89SXlDa+Ph3MevBrozBAI655OAfWQlTm9nBR0IKlrmNwFow5lPHttbwvITZfkeeeZFP6zt3F7pw==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -3769,6 +4100,22 @@ "node": ">=18" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readable-stream": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", @@ -3804,6 +4151,35 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -4316,6 +4692,24 @@ "node": ">=8" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", diff --git a/Configuration/webapp/package.json b/Configuration/webapp/package.json index f9eac2025..19ab22a07 100644 --- a/Configuration/webapp/package.json +++ b/Configuration/webapp/package.json @@ -11,14 +11,17 @@ }, "dependencies": { "@aliceo2/web-ui": "^2.7.4", - "@react-router/node": "^7.5.0", + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.0", + "@mui/material": "^7.1.1", + "@react-router/node": "7.5.3", "isbot": "^5", "react": "^19.0.0", "react-dom": "^19.0.0", - "react-router": "^7.5.2" + "react-router": "7.5.3" }, "devDependencies": { - "@react-router/dev": "^7.5.0", + "@react-router/dev": "7.5.3", "@types/node": "^20", "@types/react": "^19.0.1", "@types/react-dom": "^19.0.1", From 82407cd1d2a27ea2df801abea44872c9cd0850e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Piechnik?= Date: Tue, 10 Jun 2025 21:17:26 +0200 Subject: [PATCH 03/24] feat: implement getConfigurationByKey endpoint, refactor QCConfigurationController and create QCConfiguration.service --- Control/lib/api.js | 16 ++- .../controllers/Configuration.controller.js | 95 ------------- .../controllers/QCConfiguration.controller.js | 81 +++++++++++ .../lib/services/QCConfiguration.service.js | 129 ++++++++++++++++++ 4 files changed, 221 insertions(+), 100 deletions(-) delete mode 100644 Control/lib/controllers/Configuration.controller.js create mode 100644 Control/lib/controllers/QCConfiguration.controller.js create mode 100644 Control/lib/services/QCConfiguration.service.js diff --git a/Control/lib/api.js b/Control/lib/api.js index 9ede775fc..8beec400e 100644 --- a/Control/lib/api.js +++ b/Control/lib/api.js @@ -33,7 +33,7 @@ const { } = require('./middleware/getDetectorsLockOwnershipMiddlewareFactory.js'); // controllers -const {ConfigurationController} = require('./controllers/Configuration.controller.js'); +const {QCConfigurationController} = require('./controllers/QCConfiguration.controller.js'); const {ConsulController} = require('./controllers/Consul.controller.js'); const {EnvironmentController} = require('./controllers/Environment.controller.js'); const {LockController} = require('./controllers/Lock.controller.js'); @@ -54,6 +54,7 @@ const {LockService} = require('./services/Lock.service.js'); const {RunService} = require('./services/Run.service.js'); const {StatusService} = require('./services/Status.service.js'); const {WorkflowTemplateService} = require('./services/WorkflowTemplate.service.js'); +const {QCConfigurationService} = require('./services/QCConfiguration.service.js'); // web-ui services const {NotificationService, ConsulService} = require('@aliceo2/web-ui'); @@ -91,9 +92,9 @@ module.exports.setup = (http, ws) => { const broadcastService = new BroadcastService(ws); const cacheService = new CacheService(broadcastService); const environmentCacheService = new EnvironmentCacheService(broadcastService, eventEmitter); - - const configurationController = new ConfigurationController(consulService, config.consul); - configurationController.testConsulStatus(); + const qcConfigurationService = new QCConfigurationService(consulService); + + const qcConfigurationController = new QCConfigurationController(qcConfigurationService, config.consul); const consulController = new ConsulController(consulService, config.consul); consulController.testConsulStatus(); @@ -235,7 +236,12 @@ module.exports.setup = (http, ws) => { ); // Configuration - http.get('/configurations', configurationController.getConfigurations.bind(configurationController), {public: true}); + http.get('/configurations', qcConfigurationController.getConfigurationsKeys.bind(qcConfigurationController), { + public: true + }); + http.get('/configuration', qcConfigurationController.getConfigurationByKey.bind(qcConfigurationController), { + public: true + }); // Consul const validateService = consulController.validateService.bind(consulController); diff --git a/Control/lib/controllers/Configuration.controller.js b/Control/lib/controllers/Configuration.controller.js deleted file mode 100644 index 109d1ff03..000000000 --- a/Control/lib/controllers/Configuration.controller.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. - */ - -const { LogManager, LogLevel } = require("@aliceo2/web-ui"); -const { errorHandler, errorLogger } = require("../utils.js"); -const { getConsulConfig } = require("../config/publicConfigProvider.js"); - -/** - * Gateway for all Consul Consumer calls - */ -class ConfigurationController { - /** - * Setup ConfigurationController - * @param {ConsulService} consulService - * @param {JSON} config - */ - constructor(consulService, config) { - this.consulService = consulService; - this.config = getConsulConfig({ consul: config }); - this.configurationsPath = `${this.config.qcPath}/ANY/any`; - - this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cog"}/consul`); - } - - /** - * Check if consulService is present: - * * If yes, allow request to continue - * * If not, send response accordingly - * @param {Request} req - * @param {Response} res - * @param {Next} next - */ - validateService(req, res, next) { - if (this.consulService) { - next(); - } else { - errorHandler("Unable to retrieve configuration of consul service", res, 502); - } - } - - /** - * Method to check if consul service can be used - */ - async testConsulStatus() { - this.consulService - .getConsulLeaderStatus() - .then((data) => this._logger.info(`Service is up and running on: ${data}`)) - .catch((error) => this._logger.error(`Connection failed due to ${error}`)); - } - - /** - * Get configurations from Consul - * @param {Request} req - * @param {Response} res - */ - async getConfigurations(req, res) { - const prefix = req.query.prefix; - const recurse = req.query.recurse === "true"; - const prefixPath = prefix ? `${this.configurationsPath}/${prefix}` : this.configurationsPath; - try { - const data = await this.consulService.getOnlyRawValuesByKeyPrefix(prefixPath); - const parsedData = Object.entries(data || {}) - .map(([key, value]) => { - try { - return { - key, - value: JSON.parse(value), - }; - } catch (e) { - return undefined; - } - }) - .filter((item) => item !== undefined) - .filter((item) => recurse || !item.key.replace(`${prefixPath}/`, "").includes("/")); - - res.status(200).json(parsedData); - } catch (error) { - errorLogger(error, this._logger); - errorHandler("Error retrieving configurations", res, 500); - } - } -} - -exports.ConfigurationController = ConfigurationController; diff --git a/Control/lib/controllers/QCConfiguration.controller.js b/Control/lib/controllers/QCConfiguration.controller.js new file mode 100644 index 000000000..ce3296d0b --- /dev/null +++ b/Control/lib/controllers/QCConfiguration.controller.js @@ -0,0 +1,81 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const { LogManager } = require("@aliceo2/web-ui"); +const { errorHandler, errorLogger } = require("../utils.js"); +const { getConsulConfig } = require("../config/publicConfigProvider.js"); + +/** + * Gateway for all Consul Consumer calls + */ +class QCConfigurationController { + /** + * Setup ConfigurationController + * @param {QCConfigurationService} qcConfigurationService + * @param {JSON} config + */ + constructor(qcConfigurationService, config) { + this.qcConfigurationService = qcConfigurationService; + this.config = getConsulConfig({ consul: config }); + this.configurationsPath = `${this.config.qcPath}/ANY/any`; + + this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cog"}/consul`); + } + + /** + * Get configurations from Consul + * @param {Request} req + * @param {Response} res + */ + async getConfigurationsKeys(req, res) { + const { prefix = "", recurse = false } = req.query; + const prefixPath = prefix ? `${this.configurationsPath}/${prefix}` : this.configurationsPath; + + try { + const parsedData = await this.qcConfigurationService.getKeysOfValidConfigurations(prefixPath, recurse); + if (!parsedData || parsedData.length === 0) { + return errorHandler("No configurations found", res, 404); + } + + res.status(200).json(parsedData); + } catch (error) { + errorLogger(error, this._logger); + errorHandler("Error retrieving configurations", res, 500); + } + } + + /** + * Get configurations from Consul + * @param {Request} req + * @param {Response} res + */ + async getConfigurationByKey(req, res) { + const { key } = req.query; + + try { + const value = await this.qcConfigurationService.getConfigurationByKey(key); + console.log("Retrieved configuration:", value); + if (!value) { + return errorHandler("Configuration not found", res, 404); + } + + res.status(200).json(value); + } catch (error) { + errorLogger(error, this._logger); + errorHandler("Error retrieving configuration", res, 500); + } + } +} + +exports.QCConfigurationController = QCConfigurationController; diff --git a/Control/lib/services/QCConfiguration.service.js b/Control/lib/services/QCConfiguration.service.js new file mode 100644 index 000000000..8997eab2b --- /dev/null +++ b/Control/lib/services/QCConfiguration.service.js @@ -0,0 +1,129 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const { NotFoundError, ServiceUnavailableError } = require("@aliceo2/web-ui"); +const { errorHandler } = require("../utils.js"); + +/** + * @class + * QCConfigurationService class to be user for communicating with the Consul service + */ +class QCConfigurationService { + /** + * @constructor + * Constructor for configuring the initial state of stored information + * @param {ConsulService} consulService - service to communicate with Consul + */ + constructor(consulService) { + /** + * @type {ConsulService} + */ + this._consulService = consulService; + } + + /** + * Initialize Lock service based on the provided list of detectors + * @param {Array} detectors = [] - list of detectors to be used for the lock mechanism + * @return {void} + */ + // setLockStatesForDetectors(detectors = []) { + // for (const detectorName of detectors) { + // this._locksByDetector[detectorName] = new DetectorLock(detectorName); + // } + // } + + /** + * Check if consulService is present: + * * If yes, allow request to continue + * * If not, send response accordingly + * @param {Request} req + * @param {Response} res + * @param {Next} next + */ + validateService(req, res, next) { + if (this.consulService) { + next(); + } else { + errorHandler("Unable to retrieve configuration of consul service", res, 502); + } + } + + /** + * Method to check if consul service can be used + */ + async testConsulStatus() { + this.consulService + .getConsulLeaderStatus() + .then((data) => this._logger.info(`Service is up and running on: ${data}`)) + .catch((error) => this._logger.error(`Connection failed due to ${error}`)); + } + + /** + * Get keys of configurations stored in Consul + * @param {String} prefix - prefix to filter the keys + * @param {boolean} [recurse=false] - whether to recurse into subdirectories + */ + async getKeysOfValidConfigurations(prefix, recurse = false) { + if (!this._consulService) { + throw new ServiceUnavailableError("Consul service is not available"); + } + try { + const data = await this._consulService.getOnlyRawValuesByKeyPrefix(prefix); + const parsedData = []; + + Object.entries(data || {}).forEach(([key, value]) => { + try { + if (!JSON.parse(value)) { + return; + } + if (!recurse && key.replace(`${prefix}/`, "").includes("/")) { + return; + } + + parsedData.push(key); + } catch (e) { + // skip + } + }); + + return parsedData; + } catch (error) { + if (error instanceof NotFoundError) { + throw new NotFoundError(`No configurations found for prefix: ${prefix}`); + } + throw error; + } + } + + /** + * Get configuration by key from Consul + * @param {String} key - the key of the configuration + */ + async getConfigurationByKey(key) { + if (!this._consulService) { + throw new ServiceUnavailableError("Consul service is not available"); + } + try { + const value = await this._consulService.getOnlyRawValueByKey(key); + if (!value) { + throw new NotFoundError(`Configuration not found for key: ${key}`); + } + return value; + } catch (error) { + throw new ServiceUnavailableError(`Error retrieving configuration for key: ${key}`); + } + } +} + +exports.QCConfigurationService = QCConfigurationService; From 1e340a8c5a66018f339c8d325cdc863190488ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Piechnik?= Date: Wed, 11 Jun 2025 19:09:37 +0200 Subject: [PATCH 04/24] small code refactor --- Control/lib/api.js | 18 ++++++---- .../controllers/QCConfiguration.controller.js | 36 ++++++++++--------- .../lib/services/QCConfiguration.service.js | 14 +++----- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/Control/lib/api.js b/Control/lib/api.js index 8beec400e..040900bee 100644 --- a/Control/lib/api.js +++ b/Control/lib/api.js @@ -93,6 +93,7 @@ module.exports.setup = (http, ws) => { const cacheService = new CacheService(broadcastService); const environmentCacheService = new EnvironmentCacheService(broadcastService, eventEmitter); const qcConfigurationService = new QCConfigurationService(consulService); + qcConfigurationService.testConsulStatus(); const qcConfigurationController = new QCConfigurationController(qcConfigurationService, config.consul); @@ -236,12 +237,17 @@ module.exports.setup = (http, ws) => { ); // Configuration - http.get('/configurations', qcConfigurationController.getConfigurationsKeys.bind(qcConfigurationController), { - public: true - }); - http.get('/configuration', qcConfigurationController.getConfigurationByKey.bind(qcConfigurationController), { - public: true - }); + const qcValidateService = qcConfigurationService.validateService.bind(qcConfigurationService); + http.get( + "/configurations", qcValidateService, + qcConfigurationController.getConfigurationsKeys.bind(qcConfigurationController), + { public: true } + ); + http.get( + '/configuration', qcValidateService, + qcConfigurationController.getConfigurationByKey.bind(qcConfigurationController), + { public: true } + ); // Consul const validateService = consulController.validateService.bind(consulController); diff --git a/Control/lib/controllers/QCConfiguration.controller.js b/Control/lib/controllers/QCConfiguration.controller.js index ce3296d0b..44e4fba5c 100644 --- a/Control/lib/controllers/QCConfiguration.controller.js +++ b/Control/lib/controllers/QCConfiguration.controller.js @@ -21,29 +21,29 @@ const { getConsulConfig } = require("../config/publicConfigProvider.js"); */ class QCConfigurationController { /** - * Setup ConfigurationController + * Setup QCConfigurationController * @param {QCConfigurationService} qcConfigurationService * @param {JSON} config */ constructor(qcConfigurationService, config) { - this.qcConfigurationService = qcConfigurationService; - this.config = getConsulConfig({ consul: config }); - this.configurationsPath = `${this.config.qcPath}/ANY/any`; + this._qcConfigurationService = qcConfigurationService; + this._config = getConsulConfig({ consul: config }); + this._qcConfigurationsPath = `${this._config.qcPath}/ANY/any`; - this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cog"}/consul`); + this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cnf"}/qc-configuration-controller`); } /** - * Get configurations from Consul - * @param {Request} req - * @param {Response} res - */ + * Method to get configurations names + * @param {Request} req + * @param {Response} res + */ async getConfigurationsKeys(req, res) { const { prefix = "", recurse = false } = req.query; - const prefixPath = prefix ? `${this.configurationsPath}/${prefix}` : this.configurationsPath; + const prefixPath = prefix ? `${this._qcConfigurationsPath}/${prefix}` : this._qcConfigurationsPath; try { - const parsedData = await this.qcConfigurationService.getKeysOfValidConfigurations(prefixPath, recurse); + const parsedData = await this._qcConfigurationService.getKeysOfValidConfigurations(prefixPath, recurse); if (!parsedData || parsedData.length === 0) { return errorHandler("No configurations found", res, 404); } @@ -56,16 +56,18 @@ class QCConfigurationController { } /** - * Get configurations from Consul - * @param {Request} req - * @param {Response} res - */ + * Method to get configuration value by key + * @param {Request} req + * @param {Response} res + */ async getConfigurationByKey(req, res) { const { key } = req.query; + if (!key) { + return errorHandler("Missing configuration key", res, 400); + } try { - const value = await this.qcConfigurationService.getConfigurationByKey(key); - console.log("Retrieved configuration:", value); + const value = await this._qcConfigurationService.getConfigurationByKey(key); if (!value) { return errorHandler("Configuration not found", res, 404); } diff --git a/Control/lib/services/QCConfiguration.service.js b/Control/lib/services/QCConfiguration.service.js index 8997eab2b..455c017d0 100644 --- a/Control/lib/services/QCConfiguration.service.js +++ b/Control/lib/services/QCConfiguration.service.js @@ -12,7 +12,7 @@ * or submit itself to any jurisdiction. */ -const { NotFoundError, ServiceUnavailableError } = require("@aliceo2/web-ui"); +const { NotFoundError, ServiceUnavailableError, LogManager } = require("@aliceo2/web-ui"); const { errorHandler } = require("../utils.js"); /** @@ -30,6 +30,8 @@ class QCConfigurationService { * @type {ConsulService} */ this._consulService = consulService; + + this._logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? "cnf"}/qc-configuration-service`); } /** @@ -52,7 +54,7 @@ class QCConfigurationService { * @param {Next} next */ validateService(req, res, next) { - if (this.consulService) { + if (this._consulService) { next(); } else { errorHandler("Unable to retrieve configuration of consul service", res, 502); @@ -63,7 +65,7 @@ class QCConfigurationService { * Method to check if consul service can be used */ async testConsulStatus() { - this.consulService + this._consulService .getConsulLeaderStatus() .then((data) => this._logger.info(`Service is up and running on: ${data}`)) .catch((error) => this._logger.error(`Connection failed due to ${error}`)); @@ -75,9 +77,6 @@ class QCConfigurationService { * @param {boolean} [recurse=false] - whether to recurse into subdirectories */ async getKeysOfValidConfigurations(prefix, recurse = false) { - if (!this._consulService) { - throw new ServiceUnavailableError("Consul service is not available"); - } try { const data = await this._consulService.getOnlyRawValuesByKeyPrefix(prefix); const parsedData = []; @@ -111,9 +110,6 @@ class QCConfigurationService { * @param {String} key - the key of the configuration */ async getConfigurationByKey(key) { - if (!this._consulService) { - throw new ServiceUnavailableError("Consul service is not available"); - } try { const value = await this._consulService.getOnlyRawValueByKey(key); if (!value) { From 2b9f004b9afe68f9adc1a9b83501e87f26042bb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Wed, 11 Jun 2025 20:15:30 +0200 Subject: [PATCH 05/24] feat: created section headers --- Configuration/webapp/app/app.css | 4 ++ .../webapp/app/components/layout/Header.tsx | 7 --- .../app/components/layout/MainLayout.tsx | 12 +++-- .../layout/{ => content}/Content.tsx | 7 ++- .../layout/content/ContentHeader.tsx | 24 +++++++++ .../layout/{ => drawer}/LeftDrawer.tsx | 6 ++- .../components/user-section/UserSection.tsx | 54 +++++++++++++++++++ 7 files changed, 97 insertions(+), 17 deletions(-) delete mode 100644 Configuration/webapp/app/components/layout/Header.tsx rename Configuration/webapp/app/components/layout/{ => content}/Content.tsx (67%) create mode 100644 Configuration/webapp/app/components/layout/content/ContentHeader.tsx rename Configuration/webapp/app/components/layout/{ => drawer}/LeftDrawer.tsx (84%) create mode 100644 Configuration/webapp/app/components/user-section/UserSection.tsx diff --git a/Configuration/webapp/app/app.css b/Configuration/webapp/app/app.css index e69de29bb..1e59cba45 100644 --- a/Configuration/webapp/app/app.css +++ b/Configuration/webapp/app/app.css @@ -0,0 +1,4 @@ +html, +body { + overflow: hidden; +} diff --git a/Configuration/webapp/app/components/layout/Header.tsx b/Configuration/webapp/app/components/layout/Header.tsx deleted file mode 100644 index e4c90bd5d..000000000 --- a/Configuration/webapp/app/components/layout/Header.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; - -const Header = () => { - return
Header
; -}; - -export default Header; diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx index d0bacf3d7..c2eeb1ce4 100644 --- a/Configuration/webapp/app/components/layout/MainLayout.tsx +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -1,9 +1,8 @@ import { Box } from '@mui/material'; import React, { type FC, type PropsWithChildren } from 'react'; import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; -import LeftDrawer from './LeftDrawer'; -import Content from './Content'; -import Header from './Header'; +import LeftDrawer from './drawer/LeftDrawer'; +import Content from './content/Content'; interface MainLayoutProps extends PropsWithChildren {} @@ -17,7 +16,12 @@ const MainLayout: FC = ({ children }) => { - + {children} diff --git a/Configuration/webapp/app/components/layout/Content.tsx b/Configuration/webapp/app/components/layout/content/Content.tsx similarity index 67% rename from Configuration/webapp/app/components/layout/Content.tsx rename to Configuration/webapp/app/components/layout/content/Content.tsx index f5ab7f3cc..caa9ab3f0 100644 --- a/Configuration/webapp/app/components/layout/Content.tsx +++ b/Configuration/webapp/app/components/layout/content/Content.tsx @@ -1,14 +1,13 @@ -import { Box, Toolbar, Typography } from '@mui/material'; +import { Box } from '@mui/material'; import React, { type FC, type PropsWithChildren } from 'react'; +import ContentHeader from './ContentHeader'; interface ContentProps extends PropsWithChildren {} const Content: FC = ({ children }) => { return ( - - Test - + {children} ); diff --git a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx new file mode 100644 index 000000000..8e77b936c --- /dev/null +++ b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx @@ -0,0 +1,24 @@ +import { Toolbar, Typography } from '@mui/material'; +import React, { type FC } from 'react'; +import UserSection from '../../user-section/UserSection'; + +interface ContentHeaderProps { + currentPath: string; +} + +const ContentHeader: FC = ({ currentPath }) => { + return ( + + {currentPath} + + + ); +}; + +export default ContentHeader; diff --git a/Configuration/webapp/app/components/layout/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx similarity index 84% rename from Configuration/webapp/app/components/layout/LeftDrawer.tsx rename to Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index d9b2b4e20..9bced2105 100644 --- a/Configuration/webapp/app/components/layout/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -9,7 +9,7 @@ import { Typography, } from '@mui/material'; -const DRAWER_WIDTH = 200; +const DRAWER_WIDTH = 250; const LeftDrawer = () => { return ( @@ -27,7 +27,9 @@ const LeftDrawer = () => { variant="permanent" anchor="left" > - + + Configuration GUI + {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( diff --git a/Configuration/webapp/app/components/user-section/UserSection.tsx b/Configuration/webapp/app/components/user-section/UserSection.tsx new file mode 100644 index 000000000..a9984b530 --- /dev/null +++ b/Configuration/webapp/app/components/user-section/UserSection.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { + Box, + Toolbar, + Typography, + IconButton, + Menu, + MenuItem, + Avatar, +} from '@mui/material'; + +interface UserSectionProps { + userName: string; +} + +const UserSection: React.FC = ({ userName }) => { + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + + + {userName[0]} + + + Profile + My account + Logout + + + ); +}; + +export default UserSection; From 7a1b149845aaf5743a6499eb799af9065410bff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Wed, 11 Jun 2025 20:34:26 +0200 Subject: [PATCH 06/24] chore: minor component adjustments --- .../app/components/layout/drawer/LeftDrawer.tsx | 5 ++--- .../components/layout/drawer/LeftDrawerFooter.tsx | 12 ++++++++++++ .../app/components/user-section/UserSection.tsx | 12 ++---------- 3 files changed, 16 insertions(+), 13 deletions(-) create mode 100644 Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 9bced2105..69fa20489 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -8,6 +8,7 @@ import { Toolbar, Typography, } from '@mui/material'; +import LeftDrawerFooter from './LeftDrawerFooter'; const DRAWER_WIDTH = 250; @@ -39,9 +40,7 @@ const LeftDrawer = () => { ))} - - Footer Content Here - + ); }; diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx new file mode 100644 index 000000000..8da18de73 --- /dev/null +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx @@ -0,0 +1,12 @@ +import { Box, Typography } from '@mui/material'; +import React from 'react'; + +const LeftDrawerFooter = () => { + return ( + + Left Drawer Footer + + ); +}; + +export default LeftDrawerFooter; diff --git a/Configuration/webapp/app/components/user-section/UserSection.tsx b/Configuration/webapp/app/components/user-section/UserSection.tsx index a9984b530..08650e648 100644 --- a/Configuration/webapp/app/components/user-section/UserSection.tsx +++ b/Configuration/webapp/app/components/user-section/UserSection.tsx @@ -1,13 +1,5 @@ import React from 'react'; -import { - Box, - Toolbar, - Typography, - IconButton, - Menu, - MenuItem, - Avatar, -} from '@mui/material'; +import { Box, IconButton, Menu, MenuItem, Avatar } from '@mui/material'; interface UserSectionProps { userName: string; @@ -32,7 +24,7 @@ const UserSection: React.FC = ({ userName }) => { Date: Sun, 15 Jun 2025 20:25:17 +0200 Subject: [PATCH 07/24] tmp commit --- .../provisioning/nginx/conf.d/default.conf | 4 ++ .../config-navigator/ConfigNavigator.tsx | 28 +++++++++++ .../config-navigator/ConfigNavigatorItem.tsx | 34 +++++++++++++ .../app/components/layout/MainLayout.tsx | 32 +++--------- .../components/layout/drawer/LeftDrawer.tsx | 30 +++--------- Configuration/webapp/app/root.tsx | 26 +++++++++- Configuration/webapp/package-lock.json | 49 +++++++++++++++++++ Configuration/webapp/package.json | 3 ++ 8 files changed, 158 insertions(+), 48 deletions(-) create mode 100644 Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx create mode 100644 Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx diff --git a/Configuration/docker/provisioning/nginx/conf.d/default.conf b/Configuration/docker/provisioning/nginx/conf.d/default.conf index 62daf76bf..29d9af70d 100644 --- a/Configuration/docker/provisioning/nginx/conf.d/default.conf +++ b/Configuration/docker/provisioning/nginx/conf.d/default.conf @@ -9,6 +9,10 @@ server { client_max_body_size 32M; + location /api/ { + proxy_pass http://host.docker.internal:8081/; + } + location / { proxy_pass http://webapp:5173; diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx new file mode 100644 index 000000000..eb412008b --- /dev/null +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -0,0 +1,28 @@ +import { List, ListItem, ListItemText } from '@mui/material'; +import React, { useEffect, useState } from 'react'; +import ConfigNavigatorItem from './ConfigNavigatorItem'; + +const ConfigNavigator = () => { + const [configKeys, setConfigKeys] = useState([]); + + const fetchConfigurationKeys = async () => { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = await res.json(); + console.log(data); + setConfigKeys(data?.map((key) => key.split('/').pop())); + }; + + useEffect(() => { + fetchConfigurationKeys(); + }, []); + + return ( + + {configKeys?.map((text) => ( + + ))} + + ); +}; + +export default ConfigNavigator; diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx new file mode 100644 index 000000000..b4f079c17 --- /dev/null +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -0,0 +1,34 @@ +import React, { type FC } from 'react'; +import { + Icon, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, +} from '@mui/material'; + +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faFile } from '@fortawesome/free-solid-svg-icons'; + +interface ConfigNavigatorItemProps { + title: string; + onClick?: () => void; +} + +const ConfigNavigatorItem: FC = ({ + title, + onClick, +}) => { + return ( + + + + + + + + + ); +}; + +export default ConfigNavigatorItem; diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx index c2eeb1ce4..5d8b65936 100644 --- a/Configuration/webapp/app/components/layout/MainLayout.tsx +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -1,34 +1,18 @@ import { Box } from '@mui/material'; import React, { type FC, type PropsWithChildren } from 'react'; -import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; -import LeftDrawer from './drawer/LeftDrawer'; -import Content from './content/Content'; interface MainLayoutProps extends PropsWithChildren {} const MainLayout: FC = ({ children }) => { return ( - - - - - - - - - - - {children} - - - - - + + {children} + ); }; diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 69fa20489..36963c62e 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -1,18 +1,12 @@ -import React from 'react'; -import { - Box, - Drawer, - List, - ListItem, - ListItemText, - Toolbar, - Typography, -} from '@mui/material'; +import React, { type FC, type PropsWithChildren } from 'react'; +import { Box, Drawer, Toolbar, Typography } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; -const DRAWER_WIDTH = 250; +const DRAWER_WIDTH = 300; -const LeftDrawer = () => { +interface LeftDrawerProps extends PropsWithChildren {} + +const LeftDrawer: FC = ({ children }) => { return ( { Configuration GUI - - - {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( - - - - ))} - - - + {children} + ); }; diff --git a/Configuration/webapp/app/root.tsx b/Configuration/webapp/app/root.tsx index dad21aac2..0090cb62f 100644 --- a/Configuration/webapp/app/root.tsx +++ b/Configuration/webapp/app/root.tsx @@ -1,4 +1,4 @@ -import { isRouteErrorResponse, Outlet } from 'react-router'; +import { isRouteErrorResponse, Links, Meta, Outlet, Scripts, ScrollRestoration } from 'react-router'; import type { Route } from './+types/root'; import './app.css'; @@ -6,9 +6,31 @@ import '@aliceo2/web-ui/Frontend/css/src/bootstrap.css'; import { Spinner } from '~/ui/spinner'; import MainLayout from './components/layout/MainLayout'; +import LeftDrawer from './components/layout/drawer/LeftDrawer'; +import Content from './components/layout/content/Content'; +import ConfigNavigator from './components/config-navigator/ConfigNavigator'; export function Layout({ children }: { children: React.ReactNode }) { - return {children}; + return ( + + + + + + + + + + + + + {children} + + + + + + ); } export default function App() { diff --git a/Configuration/webapp/package-lock.json b/Configuration/webapp/package-lock.json index 241fb21a3..7c8a50ee7 100644 --- a/Configuration/webapp/package-lock.json +++ b/Configuration/webapp/package-lock.json @@ -9,6 +9,9 @@ "@aliceo2/web-ui": "^2.7.4", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@mui/material": "^7.1.1", "@react-router/node": "7.5.3", "isbot": "^5", @@ -1135,6 +1138,52 @@ "node": ">=18" } }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", + "integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", + "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.7.2", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz", + "integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.7.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/Configuration/webapp/package.json b/Configuration/webapp/package.json index 19ab22a07..341238240 100644 --- a/Configuration/webapp/package.json +++ b/Configuration/webapp/package.json @@ -13,6 +13,9 @@ "@aliceo2/web-ui": "^2.7.4", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@fortawesome/fontawesome-svg-core": "^6.7.2", + "@fortawesome/free-solid-svg-icons": "^6.7.2", + "@fortawesome/react-fontawesome": "^0.2.2", "@mui/material": "^7.1.1", "@react-router/node": "7.5.3", "isbot": "^5", From 2588750850bb5ac6c3f2e96208057183d0d5bfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Sun, 15 Jun 2025 20:28:37 +0200 Subject: [PATCH 08/24] chore: reorganize main layout --- .../app/components/layout/MainLayout.tsx | 31 ++++++------------- Configuration/webapp/app/root.tsx | 25 +++++++++++++-- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx index c2eeb1ce4..fe9f6103d 100644 --- a/Configuration/webapp/app/components/layout/MainLayout.tsx +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -1,6 +1,5 @@ import { Box } from '@mui/material'; import React, { type FC, type PropsWithChildren } from 'react'; -import { Links, Meta, Scripts, ScrollRestoration } from 'react-router'; import LeftDrawer from './drawer/LeftDrawer'; import Content from './content/Content'; @@ -8,27 +7,15 @@ interface MainLayoutProps extends PropsWithChildren {} const MainLayout: FC = ({ children }) => { return ( - - - - - - - - - - - {children} - - - - - + + + {children} + ); }; diff --git a/Configuration/webapp/app/root.tsx b/Configuration/webapp/app/root.tsx index dad21aac2..fb8c915cb 100644 --- a/Configuration/webapp/app/root.tsx +++ b/Configuration/webapp/app/root.tsx @@ -1,4 +1,11 @@ -import { isRouteErrorResponse, Outlet } from 'react-router'; +import { + isRouteErrorResponse, + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from 'react-router'; import type { Route } from './+types/root'; import './app.css'; @@ -8,7 +15,21 @@ import { Spinner } from '~/ui/spinner'; import MainLayout from './components/layout/MainLayout'; export function Layout({ children }: { children: React.ReactNode }) { - return {children}; + return ( + + + + + + + + + {children} + + + + + ); } export default function App() { From 7a29f9b4e443ec8cfe1ed84c70ffe259e50d3a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Sun, 15 Jun 2025 22:53:49 +0200 Subject: [PATCH 09/24] chore: implement tests --- .../app/components/layout/content/Content.tsx | 2 +- .../layout/content/ContentHeader.tsx | 1 + .../components/layout/drawer/LeftDrawer.tsx | 6 +- .../layout/drawer/LeftDrawerFooter.tsx | 5 +- .../layout/drawer/LeftDrawerHeader.tsx | 15 + .../components/user-section/UserSection.tsx | 4 +- Configuration/webapp/app/test/mocha-index.cjs | 51 + .../app/test/public/page-root-mocha.cjs | 55 + Configuration/webapp/app/test/test-config.cjs | 6 + Configuration/webapp/package-lock.json | 1278 ++++++++++++++++- Configuration/webapp/package.json | 5 +- 11 files changed, 1366 insertions(+), 62 deletions(-) create mode 100644 Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx create mode 100644 Configuration/webapp/app/test/mocha-index.cjs create mode 100644 Configuration/webapp/app/test/public/page-root-mocha.cjs create mode 100644 Configuration/webapp/app/test/test-config.cjs diff --git a/Configuration/webapp/app/components/layout/content/Content.tsx b/Configuration/webapp/app/components/layout/content/Content.tsx index caa9ab3f0..1cd135b90 100644 --- a/Configuration/webapp/app/components/layout/content/Content.tsx +++ b/Configuration/webapp/app/components/layout/content/Content.tsx @@ -6,7 +6,7 @@ interface ContentProps extends PropsWithChildren {} const Content: FC = ({ children }) => { return ( - + {children} diff --git a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx index 8e77b936c..18d08c3d4 100644 --- a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx +++ b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx @@ -14,6 +14,7 @@ const ContentHeader: FC = ({ currentPath }) => { display: 'flex', justifyContent: 'space-between', }} + className="content-section__header" > {currentPath} diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 69fa20489..58aa4bd1f 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -9,6 +9,7 @@ import { Typography, } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; +import LeftDrawerHeader from './LeftDrawerHeader'; const DRAWER_WIDTH = 250; @@ -27,10 +28,9 @@ const LeftDrawer = () => { }} variant="permanent" anchor="left" + className='left-drawer' > - - Configuration GUI - + {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx index 8da18de73..87af1b0f4 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx @@ -3,7 +3,10 @@ import React from 'react'; const LeftDrawerFooter = () => { return ( - + Left Drawer Footer ); diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx new file mode 100644 index 000000000..4d2022408 --- /dev/null +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx @@ -0,0 +1,15 @@ +import { Toolbar, Typography } from '@mui/material'; +import React from 'react'; + +const LeftDrawerHeader = () => { + return ( + + Configuration GUI + + ); +}; + +export default LeftDrawerHeader; diff --git a/Configuration/webapp/app/components/user-section/UserSection.tsx b/Configuration/webapp/app/components/user-section/UserSection.tsx index 08650e648..14a902f28 100644 --- a/Configuration/webapp/app/components/user-section/UserSection.tsx +++ b/Configuration/webapp/app/components/user-section/UserSection.tsx @@ -17,7 +17,7 @@ const UserSection: React.FC = ({ userName }) => { }; return ( - + {userName[0]} @@ -27,13 +27,13 @@ const UserSection: React.FC = ({ userName }) => { vertical: 'bottom', horizontal: 'right', }} - keepMounted transformOrigin={{ vertical: 'top', horizontal: 'right', }} open={Boolean(anchorEl)} onClose={handleClose} + className="user-section__menu" > Profile My account diff --git a/Configuration/webapp/app/test/mocha-index.cjs b/Configuration/webapp/app/test/mocha-index.cjs new file mode 100644 index 000000000..a652e4e16 --- /dev/null +++ b/Configuration/webapp/app/test/mocha-index.cjs @@ -0,0 +1,51 @@ +const puppeteer = require('puppeteer'); +const config = require('./test-config.cjs'); + +let page; +describe('Configuration', function () { + let browser; + this.timeout(50000); + this.slow(1000); + const url = `http://${config.http.hostname}:${config.http.port}/`; + + before(async () => { + browser = await puppeteer.launch({ + args: ['--no-sandbox', '--disable-setuid-sandbox'], + headless: true, + }); + page = await browser.newPage(); + + // Listen to browser + page.on('error', (pageerror) => { + console.error(' ', pageerror); + this.ok = false; + }); + page.on('pageerror', (pageerror) => { + console.error(' ', pageerror); + this.ok = false; + }); + page.on('console', (msg) => { + for (let i = 0; i < msg.args().length; ++i) { + console.log(` ${msg.args()[i]}`); + } + }); + await page.setViewport({ width: 1200, height: 770 }); + exports.page = page; + const helpers = { url }; + exports.helpers = helpers; + }); + + require('./public/page-root-mocha.cjs'); + + beforeEach(() => (this.ok = true)); + + afterEach(() => { + if (!this.ok) { + throw new Error('something went wrong'); + } + }); + + after(async () => { + await browser.close(); + }); +}); diff --git a/Configuration/webapp/app/test/public/page-root-mocha.cjs b/Configuration/webapp/app/test/public/page-root-mocha.cjs new file mode 100644 index 000000000..d1b71cc9f --- /dev/null +++ b/Configuration/webapp/app/test/public/page-root-mocha.cjs @@ -0,0 +1,55 @@ +const assert = require('assert'); +const test = require('../mocha-index.cjs'); + +describe('`pageRoot` test-suite', async () => { + let url; + let page; + + before(async () => { + url = test.helpers.url; + page = test.page; + }); + + it('should load root page', async () => { + await page.goto(url, { waitUntil: 'networkidle0' }); + + const location = await page.evaluate(() => window.location); + assert.strictEqual(location.search, ''); + }); + + it('should successfully display drawer', async () => { + const drawer = await page.$$('.left-drawer'); + assert.strictEqual(drawer.length, 1); + }); + + it('should successfully display drawer header', async () => { + const drawerHeader = await page.$$('.left-drawer__header'); + assert.strictEqual(drawerHeader.length, 1); + }); + + it('should successfully display drawer', async () => { + const drawerFooter = await page.$$('.left-drawer__footer'); + assert.strictEqual(drawerFooter.length, 1); + }); + + it('should successfully display content section', async () => { + const contentSection = await page.$$('.content-section'); + assert.strictEqual(contentSection.length, 1); + }); + + it('should successfully display content section header', async () => { + const contentSectionHeader = await page.$$('.content-section__header'); + assert.strictEqual(contentSectionHeader.length, 1); + }); + + it('should successfully display user section', async () => { + const userSection = await page.$$('.user-section'); + assert.strictEqual(userSection.length, 1); + }); + + it('should successfully display user section menu on clik', async () => { + await page.click('.user-section'); + const userSectionMenu = await page.$$('.user-section__menu'); + assert.strictEqual(userSectionMenu.length, 1); + }); +}); diff --git a/Configuration/webapp/app/test/test-config.cjs b/Configuration/webapp/app/test/test-config.cjs new file mode 100644 index 000000000..9aaeb1737 --- /dev/null +++ b/Configuration/webapp/app/test/test-config.cjs @@ -0,0 +1,6 @@ +module.exports = { + http: { + port: 8080, + hostname: 'localhost', + }, +}; diff --git a/Configuration/webapp/package-lock.json b/Configuration/webapp/package-lock.json index 241fb21a3..0e2fbe273 100644 --- a/Configuration/webapp/package-lock.json +++ b/Configuration/webapp/package-lock.json @@ -12,6 +12,8 @@ "@mui/material": "^7.1.1", "@react-router/node": "7.5.3", "isbot": "^5", + "mocha": "^11.6.0", + "puppeteer": "^24.10.1", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "7.5.3" @@ -1139,7 +1141,6 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1480,7 +1481,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1497,6 +1497,27 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.5", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", + "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.1", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.0.8", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@react-router/dev": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.5.3.tgz", @@ -1861,6 +1882,12 @@ "win32" ] }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1872,7 +1899,7 @@ "version": "20.17.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.30.tgz", "integrity": "sha512-7zf4YyHA+jvBNfVrk2Gtvs6x7E8V+YDW05bNfG2XkWDJfYRXrTiP/DsB2zSYTaHX0bGIujTBQdMVAhb+j7mwpg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -1924,6 +1951,16 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "license": "MIT" }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1946,11 +1983,19 @@ "node": ">= 0.6" } }, + "node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1963,7 +2008,6 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1979,18 +2023,42 @@ "dev": true, "license": "MIT" }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", "license": "MIT" }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "license": "Apache-2.0" + }, "node_modules/babel-dead-code-elimination": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.10.tgz", @@ -2023,9 +2091,89 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/bare-fs": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.5.tgz", + "integrity": "sha512-1zccWBMypln0jEE05LzZt+V/8y8AQsQQqxtklqaIyg5nu6OAYFhZxPXinJTSG+kU5qyNmeLgcn9AW7eHiCHVLA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", + "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "bare": ">=1.14.0" + } + }, + "node_modules/bare-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", + "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-os": "^3.0.1" + } + }, + "node_modules/bare-stream": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", + "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "streamx": "^2.21.0" + }, + "peerDependencies": { + "bare-buffer": "*", + "bare-events": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + }, + "bare-events": { + "optional": true + } + } + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bignumber.js": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.0.tgz", @@ -2078,12 +2226,17 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC" + }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -2117,6 +2270,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2186,6 +2348,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001713", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001713.tgz", @@ -2207,11 +2381,53 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, "license": "MIT", "dependencies": { "readdirp": "^4.0.1" @@ -2223,6 +2439,106 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chromium-bidi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", + "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -2246,7 +2562,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2374,7 +2689,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -2389,7 +2703,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -2407,10 +2720,19 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2424,6 +2746,18 @@ } } }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -2439,6 +2773,20 @@ } } }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "license": "MIT", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2458,6 +2806,21 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/devtools-protocol": { + "version": "0.0.1452169", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1452169.tgz", + "integrity": "sha512-FOFDVMGrAUNp0dDKsAU1TorWJUx2JOU1k9xdgBKKJF3IBh/Uhl2yswG5r3TEAOrCiGY2QRp1e6LVDQrCsTKO4g==", + "license": "BSD-3-Clause" + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2486,7 +2849,6 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, "license": "MIT" }, "node_modules/ecdsa-sig-formatter": { @@ -2515,7 +2877,6 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, "license": "MIT" }, "node_modules/enabled": { @@ -2533,6 +2894,24 @@ "node": ">= 0.8" } }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", @@ -2637,7 +3016,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2661,6 +3039,58 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2744,6 +3174,41 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/fdir": { "version": "6.4.4", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", @@ -2804,6 +3269,31 @@ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", "license": "MIT" }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", @@ -2814,7 +3304,6 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -2894,6 +3383,15 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2931,11 +3429,39 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.4.tgz", + "integrity": "sha512-E1b1lFFLvLgak2whF2xDBcOy6NLVGZBqqjJjsIhvopKfWWEi64pLVTWWehV8KlLerZkfNTA95sTe2OdJKm1OzQ==", + "license": "MIT", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -2987,6 +3513,15 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -3011,6 +3546,15 @@ "node": ">= 0.4" } }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, "node_modules/helmet": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", @@ -3074,6 +3618,32 @@ "node": ">= 0.8" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3108,6 +3678,19 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3142,7 +3725,15 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "license": "MIT", "engines": { "node": ">=8" @@ -3160,6 +3751,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -3179,14 +3782,12 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -3225,6 +3826,24 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "license": "MIT" + }, "node_modules/jsesc": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", @@ -3337,6 +3956,21 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -3386,6 +4020,22 @@ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/logform": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", @@ -3498,7 +4148,6 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -3514,7 +4163,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" @@ -3530,6 +4178,47 @@ "ospec": "ospec/bin/ospec" } }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mocha": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", + "license": "MIT", + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -3576,6 +4265,15 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", @@ -3696,6 +4394,15 @@ "node": ">= 0.8" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -3747,11 +4454,72 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", + "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", + "license": "MIT", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.6", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "license": "MIT", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { @@ -3799,11 +4567,19 @@ "node": ">= 0.8" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3819,7 +4595,6 @@ "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -3836,7 +4611,6 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, "license": "ISC" }, "node_modules/path-to-regexp": { @@ -3861,6 +4635,12 @@ "dev": true, "license": "MIT" }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3941,6 +4721,15 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -3992,6 +4781,114 @@ "node": ">= 0.10" } }, + "node_modules/proxy-agent": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", + "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.6", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.1.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.5" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.10.1.tgz", + "integrity": "sha512-7T3rfSaaPt5A31VITV5YKQ4wPCCv4aPn8byDaV+9lhDU9v7BWYY4Ncwerw3ZR5mIolrh/PvzGdIDK7yiBth75g==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1452169", + "puppeteer-core": "24.10.1", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.10.1.tgz", + "integrity": "sha512-AE6doA9znmEEps/pC5lc9p0zejCdNLR6UBp3EZ49/15Nbvh+uklXxGox7Qh8/lFGqGVwxInl0TXmsOmIuIMwiQ==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.5", + "chromium-bidi": "5.1.0", + "debug": "^4.4.1", + "devtools-protocol": "0.0.1452169", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -4007,6 +4904,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -4141,7 +5047,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 14.18.0" @@ -4151,6 +5056,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4272,9 +5186,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4331,6 +5245,15 @@ "node": ">= 0.8" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", @@ -4362,7 +5285,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -4375,7 +5297,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4457,7 +5378,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, "license": "ISC", "engines": { "node": ">=14" @@ -4475,6 +5395,44 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", + "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", + "license": "MIT", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4540,6 +5498,12 @@ "dev": true, "license": "CC0-1.0" }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "license": "BSD-3-Clause" + }, "node_modules/sqlstring": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.1.tgz", @@ -4573,6 +5537,19 @@ "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", "license": "MIT" }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -4592,7 +5569,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -4611,7 +5587,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4626,7 +5601,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4636,14 +5610,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4656,7 +5628,6 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -4673,7 +5644,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4686,18 +5656,44 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "license": "MIT" }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4710,6 +5706,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar-fs": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz", + "integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^4.0.1", + "bare-path": "^3.0.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4772,6 +5802,12 @@ } } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/turbo-stream": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", @@ -4791,6 +5827,12 @@ "node": ">= 0.6" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -4818,7 +5860,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/universalify": { @@ -5129,11 +6171,16 @@ "node": ">= 6" } }, + "node_modules/workerpool": { + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "license": "Apache-2.0" + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -5152,7 +6199,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -5170,7 +6216,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5180,7 +6225,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -5196,14 +6240,12 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -5218,7 +6260,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -5227,10 +6268,16 @@ "node": ">=8" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -5248,6 +6295,15 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -5269,6 +6325,120 @@ "engines": { "node": ">= 14" } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.64", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz", + "integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/Configuration/webapp/package.json b/Configuration/webapp/package.json index 19ab22a07..cad96f619 100644 --- a/Configuration/webapp/package.json +++ b/Configuration/webapp/package.json @@ -7,7 +7,8 @@ "dev": "react-router dev", "start": "react-router-serve ./build/server/index.js", "typecheck": "react-router typegen && tsc", - "docker:typecheck": "docker compose exec webapp npm run typecheck" + "docker:typecheck": "docker compose exec webapp npm run typecheck", + "mocha": "mocha --exit $(find app/test -name 'mocha-*.cjs')" }, "dependencies": { "@aliceo2/web-ui": "^2.7.4", @@ -16,6 +17,8 @@ "@mui/material": "^7.1.1", "@react-router/node": "7.5.3", "isbot": "^5", + "mocha": "^11.6.0", + "puppeteer": "^24.10.1", "react": "^19.0.0", "react-dom": "^19.0.0", "react-router": "7.5.3" From e57933c8bc2e43412af16f0aa85861a444749199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 16 Jun 2025 23:19:39 +0200 Subject: [PATCH 10/24] chore: add test for config keys list --- .../config-navigator/ConfigNavigator.tsx | 4 ++-- .../config-navigator/ConfigNavigatorItem.tsx | 2 +- .../components/layout/drawer/LeftDrawer.tsx | 18 ++++-------------- .../webapp/app/test/public/page-root-mocha.cjs | 8 ++++++++ 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx index eb412008b..098b116a7 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -1,4 +1,4 @@ -import { List, ListItem, ListItemText } from '@mui/material'; +import { List } from '@mui/material'; import React, { useEffect, useState } from 'react'; import ConfigNavigatorItem from './ConfigNavigatorItem'; @@ -8,7 +8,7 @@ const ConfigNavigator = () => { const fetchConfigurationKeys = async () => { const res = await fetch('http://localhost:8080/api/api/configurations'); const data = await res.json(); - console.log(data); + setConfigKeys(data?.map((key) => key.split('/').pop())); }; diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx index b4f079c17..66378f92a 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -20,7 +20,7 @@ const ConfigNavigatorItem: FC = ({ onClick, }) => { return ( - + diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index f55ba95be..8a914b6b2 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -3,12 +3,10 @@ import { Box, Drawer, List, ListItem, ListItemText } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; import LeftDrawerHeader from './LeftDrawerHeader'; - const DRAWER_WIDTH = 300; interface LeftDrawerProps extends PropsWithChildren {} - const LeftDrawer: FC = ({ children }) => { return ( = ({ children }) => { }} variant="permanent" anchor="left" - className='left-drawer' + className="left-drawer" > - - - - {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( - - - - ))} - - - + + {children} + ); }; diff --git a/Configuration/webapp/app/test/public/page-root-mocha.cjs b/Configuration/webapp/app/test/public/page-root-mocha.cjs index d1b71cc9f..cb3bfee4e 100644 --- a/Configuration/webapp/app/test/public/page-root-mocha.cjs +++ b/Configuration/webapp/app/test/public/page-root-mocha.cjs @@ -52,4 +52,12 @@ describe('`pageRoot` test-suite', async () => { const userSectionMenu = await page.$$('.user-section__menu'); assert.strictEqual(userSectionMenu.length, 1); }); + + it('should successfully display configurations list', async () => { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = await res.json(); + + const configNavigatorItems = await page.$$('.config_navigator__item'); + assert.strictEqual(configNavigatorItems.length, data.length); + }); }); From 56db191283afc007ea74106b2ebc5e2114319d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 16 Jun 2025 23:34:31 +0200 Subject: [PATCH 11/24] chore: adjust list styles --- .../config-navigator/ConfigNavigatorItem.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx index 66378f92a..69ec4172a 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -20,10 +20,17 @@ const ConfigNavigatorItem: FC = ({ onClick, }) => { return ( - - + + - + From 8770fae9daf602b91d2f011419b50bf29227f6c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Wed, 18 Jun 2025 21:17:15 +0200 Subject: [PATCH 12/24] chore: remove unused vars --- .../app/components/layout/drawer/LeftDrawer.tsx | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 58aa4bd1f..49068face 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -1,13 +1,5 @@ import React from 'react'; -import { - Box, - Drawer, - List, - ListItem, - ListItemText, - Toolbar, - Typography, -} from '@mui/material'; +import { Box, Drawer, List, ListItem, ListItemText } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; import LeftDrawerHeader from './LeftDrawerHeader'; @@ -28,9 +20,9 @@ const LeftDrawer = () => { }} variant="permanent" anchor="left" - className='left-drawer' + className="left-drawer" > - + {['Item 1', 'Item 2', 'Item 3', 'Item 4'].map((text) => ( @@ -40,7 +32,7 @@ const LeftDrawer = () => { ))} - + ); }; From 3d360cc8192e62bda7741ed9b0397f7e03bae092 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Wed, 18 Jun 2025 21:24:26 +0200 Subject: [PATCH 13/24] Merge branch 'feature/CNF/OGUI-1700/base-app-layout' into feature/CNF/OGUI-1699/configuration-keys-display --- .../components/layout/drawer/LeftDrawer.tsx | 2 +- Control/package-lock.json | 24 +- Control/package.json | 4 +- Control/protobuf/o2control.proto | 1 + Framework/package-lock.json | 118 +++---- Framework/package.json | 10 +- InfoLogger/package-lock.json | 111 ++++--- InfoLogger/package.json | 10 +- QualityControl/lib/QCModel.js | 12 + QualityControl/lib/api.js | 11 +- .../lib/controllers/ObjectController.js | 97 ++---- QualityControl/lib/dtos/ObjectGetDto.js | 97 ++++++ ...bjectGetByIdValidationMiddlewareFactory.js | 41 +++ ...tGetContentsValidationMiddlewareFactory.js | 40 +++ .../objectsGetValidationMiddlewareFactory.js | 40 +++ .../lib/services/QcObject.service.js | 23 +- .../lib/services/ccdb/CcdbService.js | 24 +- QualityControl/package-lock.json | 88 ++--- QualityControl/package.json | 8 +- QualityControl/public/app.css | 15 + QualityControl/public/object/objectDraw.js | 9 +- .../test/api/objects/api-get-object.test.js | 120 +++++++ .../lib/controllers/ObjectController.test.js | 308 +++++++++--------- ...GetByContentsValidation.middleware.test.js | 198 +++++++++++ ...objectGetByIdValidation.middleware.test.js | 172 ++++++++++ .../objectsGetValidation.middleware.test.js | 176 ++++++++++ .../test/lib/services/CcdbService.test.js | 35 +- .../test/setup/seeders/ccdbObjects.js | 83 +++++ .../seeders/object-view/mock-object-view.js | 23 +- QualityControl/test/setup/testSetupForCcdb.js | 96 +++--- QualityControl/test/test-index.js | 11 + .../backend/wrapper/proto/wrapper.proto | 65 ++++ 32 files changed, 1582 insertions(+), 490 deletions(-) create mode 100644 QualityControl/lib/dtos/ObjectGetDto.js create mode 100644 QualityControl/lib/middleware/objects/objectGetByIdValidationMiddlewareFactory.js create mode 100644 QualityControl/lib/middleware/objects/objectGetContentsValidationMiddlewareFactory.js create mode 100644 QualityControl/lib/middleware/objects/objectsGetValidationMiddlewareFactory.js create mode 100644 QualityControl/test/api/objects/api-get-object.test.js create mode 100644 QualityControl/test/lib/middlewares/objects/objectGetByContentsValidation.middleware.test.js create mode 100644 QualityControl/test/lib/middlewares/objects/objectGetByIdValidation.middleware.test.js create mode 100644 QualityControl/test/lib/middlewares/objects/objectsGetValidation.middleware.test.js create mode 100644 Tokenization/backend/wrapper/proto/wrapper.proto diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 8a914b6b2..7cfaa3e62 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -1,5 +1,5 @@ import React, { type FC, type PropsWithChildren } from 'react'; -import { Box, Drawer, List, ListItem, ListItemText } from '@mui/material'; +import { Box, Drawer } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; import LeftDrawerHeader from './LeftDrawerHeader'; diff --git a/Control/package-lock.json b/Control/package-lock.json index 0a5bfc762..55dffab9d 100644 --- a/Control/package-lock.json +++ b/Control/package-lock.json @@ -23,12 +23,12 @@ "devDependencies": { "eslint": "^8.56.0", "jsonwebtoken": "^9.0.2", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nock": "^14.0.0", "nyc": "^17.1.0", "proxyquire": "^2.1.3", "puppeteer": "^24.10.0", - "sinon": "20.0.0", + "sinon": "21.0.0", "supertest": "7.1.0" }, "engines": { @@ -4140,9 +4140,9 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", "dev": true, "license": "MIT", "dependencies": { @@ -4162,7 +4162,7 @@ "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -5747,9 +5747,9 @@ "license": "MIT" }, "node_modules/sinon": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", - "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6502,9 +6502,9 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", "dev": true, "license": "Apache-2.0" }, diff --git a/Control/package.json b/Control/package.json index b011c9959..89d2ff8b0 100644 --- a/Control/package.json +++ b/Control/package.json @@ -42,12 +42,12 @@ "devDependencies": { "eslint": "^8.56.0", "jsonwebtoken": "^9.0.2", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nock": "^14.0.0", "nyc": "^17.1.0", "proxyquire": "^2.1.3", "puppeteer": "^24.10.0", - "sinon": "20.0.0", + "sinon": "21.0.0", "supertest": "7.1.0" }, "bundleDependencies": [ diff --git a/Control/protobuf/o2control.proto b/Control/protobuf/o2control.proto index df3069b90..9f9d0c544 100644 --- a/Control/protobuf/o2control.proto +++ b/Control/protobuf/o2control.proto @@ -123,6 +123,7 @@ message TeardownReply {} message GetEnvironmentsRequest { bool showAll = 1; bool showTaskInfos = 2; + bool showDetailedIntegratedServices = 3; // integratedServices are returned everytime, setting this flag gives detailed report } message GetEnvironmentsReply { string frameworkId = 1; diff --git a/Framework/package-lock.json b/Framework/package-lock.json index 8dc043efc..abc1edb20 100644 --- a/Framework/package-lock.json +++ b/Framework/package-lock.json @@ -21,17 +21,17 @@ "ws": "^8.18.0" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", "long": "^5.3.1", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nock": "14.0.0", "nyc": "^17.1.0", "puppeteer": "^24.10.0", - "sinon": "20.0.0", + "sinon": "21.0.0", "supertest": "^7.1.0" }, "engines": { @@ -456,9 +456,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -531,9 +531,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -1173,10 +1173,11 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1276,12 +1277,13 @@ "dev": true }, "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.1.1.tgz", + "integrity": "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/argparse": { @@ -2178,19 +2180,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2202,9 +2204,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2239,14 +2241,14 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.7.1.tgz", - "integrity": "sha512-XBnVA5g2kUVokTNUiE1McEPse5n9/mNUmuJcx52psT6zBs2eVcXSmQBvjfa7NZdfLVSy3u1pEDDUxoxpwy89WA==", + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-51.0.1.tgz", + "integrity": "sha512-nnH6O8uk0Wp5EvHlVEPESKdGWTlu5g1tfBUZmL/jMZLBpUtttxxW+9hPzTMCYmYsQ3HwDsJdHJAiaDRKsP6iUg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.50.2", - "are-docs-informative": "^0.0.2", + "are-docs-informative": "^0.1.1", "comment-parser": "1.4.1", "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", @@ -2257,16 +2259,16 @@ "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2293,10 +2295,11 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2305,14 +2308,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2322,10 +2326,11 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3707,9 +3712,9 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", "dev": true, "license": "MIT", "dependencies": { @@ -3729,7 +3734,7 @@ "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -5037,9 +5042,9 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "node_modules/sinon": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", - "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5634,10 +5639,11 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", diff --git a/Framework/package.json b/Framework/package.json index 649f579a6..82a87f5f5 100644 --- a/Framework/package.json +++ b/Framework/package.json @@ -43,16 +43,16 @@ "protobufjs": "^7.5.0" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nock": "14.0.0", "nyc": "^17.1.0", "puppeteer": "^24.10.0", - "sinon": "20.0.0", + "sinon": "21.0.0", "supertest": "^7.1.0", "long": "^5.3.1" }, diff --git a/InfoLogger/package-lock.json b/InfoLogger/package-lock.json index d248b1152..24758491a 100644 --- a/InfoLogger/package-lock.json +++ b/InfoLogger/package-lock.json @@ -16,15 +16,15 @@ "mariadb": "3.4.0" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nyc": "^17.1.0", "puppeteer": "^24.10.0", - "sinon": "^20.0.0" + "sinon": "^21.0.0" }, "engines": { "node": ">= 22.x" @@ -462,9 +462,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -537,9 +537,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -1137,10 +1137,11 @@ } }, "node_modules/acorn": { - "version": "8.14.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", - "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -1240,12 +1241,13 @@ "dev": true }, "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.1.1.tgz", + "integrity": "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/argparse": { @@ -2146,19 +2148,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2170,9 +2172,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2207,14 +2209,14 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.7.1.tgz", - "integrity": "sha512-XBnVA5g2kUVokTNUiE1McEPse5n9/mNUmuJcx52psT6zBs2eVcXSmQBvjfa7NZdfLVSy3u1pEDDUxoxpwy89WA==", + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-51.0.1.tgz", + "integrity": "sha512-nnH6O8uk0Wp5EvHlVEPESKdGWTlu5g1tfBUZmL/jMZLBpUtttxxW+9hPzTMCYmYsQ3HwDsJdHJAiaDRKsP6iUg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.50.2", - "are-docs-informative": "^0.0.2", + "are-docs-informative": "^0.1.1", "comment-parser": "1.4.1", "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", @@ -2225,16 +2227,16 @@ "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2249,10 +2251,11 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2261,14 +2264,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3788,9 +3792,9 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.5.0.tgz", - "integrity": "sha512-VKDjhy6LMTKm0WgNEdlY77YVsD49LZnPSXJAaPNL9NRYQADxvORsyG1DIQY6v53BKTnlNbEE2MbVCDbnxr4K3w==", + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.6.0.tgz", + "integrity": "sha512-i0JVb+OUBqw63X/1pC3jCyJsqYisgxySBbsQa8TKvefpA1oEnw7JXxXnftfMHRsw7bEEVGRtVlHcDYXBa7FzVw==", "dev": true, "license": "MIT", "dependencies": { @@ -3810,7 +3814,7 @@ "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", + "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" @@ -5155,9 +5159,9 @@ "license": "MIT" }, "node_modules/sinon": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", - "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5769,10 +5773,11 @@ } }, "node_modules/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true + "version": "9.3.2", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.2.tgz", + "integrity": "sha512-Xz4Nm9c+LiBHhDR5bDLnNzmj6+5F+cyEAWPMkbs2awq/dYazR/efelZzUAjB/y3kNHL+uzkHvxVVpaOfGCPV7A==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "7.0.0", diff --git a/InfoLogger/package.json b/InfoLogger/package.json index 3445e58d3..5488b9017 100644 --- a/InfoLogger/package.json +++ b/InfoLogger/package.json @@ -34,15 +34,15 @@ "mariadb": "3.4.0" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", - "mocha": "^11.5.0", + "mocha": "^11.6.0", "nyc": "^17.1.0", "puppeteer": "^24.10.0", - "sinon": "^20.0.0" + "sinon": "^21.0.0" }, "bundleDependencies": [ "@aliceo2/web-ui" diff --git a/QualityControl/lib/QCModel.js b/QualityControl/lib/QCModel.js index 7a9ca8305..aa88c73fc 100644 --- a/QualityControl/lib/QCModel.js +++ b/QualityControl/lib/QCModel.js @@ -38,6 +38,11 @@ import { UserRepository } from './repositories/UserRepository.js'; import { ChartRepository } from './repositories/ChartRepository.js'; import { initDatabase } from './database/index.js'; import { SequelizeDatabase } from './database/SequelizeDatabase.js'; +import { objectGetByIdValidationMiddlewareFactory } + from './middleware/objects/objectGetByIdValidationMiddlewareFactory.js'; +import { objectsGetValidationMiddlewareFactory } from './middleware/objects/objectsGetValidationMiddlewareFactory.js'; +import { objectGetContentsValidationMiddlewareFactory } + from './middleware/objects/objectGetContentsValidationMiddlewareFactory.js'; /** * Model initialization for the QCG application @@ -77,6 +82,10 @@ export const setupQcModel = () => { const filterController = new FilterController(filterService); + const objectGetByIdValidation = objectGetByIdValidationMiddlewareFactory(filterService); + const objectsGetValidation = objectsGetValidationMiddlewareFactory(filterService); + const objectGetContentsValidation = objectGetContentsValidationMiddlewareFactory(filterService); + initializeIntervals(intervalsService, qcObjectService, filterService); return { @@ -89,6 +98,9 @@ export const setupQcModel = () => { filterController, layoutRepository, jsonFileService, + objectGetByIdValidation, + objectsGetValidation, + objectGetContentsValidation, }; }; diff --git a/QualityControl/lib/api.js b/QualityControl/lib/api.js index 452f3b602..3ad304198 100644 --- a/QualityControl/lib/api.js +++ b/QualityControl/lib/api.js @@ -46,11 +46,16 @@ export const setup = (http, ws) => { layoutRepository, jsonFileService, filterController, + objectGetByIdValidation, + objectsGetValidation, + objectGetContentsValidation, } = setupQcModel(); statusService.ws = ws; - http.get('/object/:id', objectController.getObjectById.bind(objectController)); - http.get('/object', objectController.getObjectContent.bind(objectController)); - http.get('/objects', objectController.getObjects.bind(objectController), { public: true }); + + http.get('/object/:id', objectGetByIdValidation, objectController.getObjectById.bind(objectController)); + http.get('/object', objectGetContentsValidation, objectController.getObjectContent.bind(objectController)); + + http.get('/objects', objectsGetValidation, objectController.getObjects.bind(objectController), { public: true }); http.get('/layouts', layoutController.getLayoutsHandler.bind(layoutController)); http.get('/layout/:id', layoutController.getLayoutHandler.bind(layoutController)); diff --git a/QualityControl/lib/controllers/ObjectController.js b/QualityControl/lib/controllers/ObjectController.js index 3a52bbf65..85412df71 100644 --- a/QualityControl/lib/controllers/ObjectController.js +++ b/QualityControl/lib/controllers/ObjectController.js @@ -12,8 +12,9 @@ * or submit itself to any jurisdiction. */ 'use strict'; -import { InvalidInputError, NotFoundError, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui'; -import { LogManager } from '@aliceo2/web-ui'; +import { LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui'; + +const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/object-ctrl`); /** * Gateway for all QC Objects requests @@ -30,7 +31,6 @@ export class ObjectController { * @type {QCObjectService} */ this._objService = objService; - this._logger = LogManager.getLogger('object/controller'); } /** @@ -40,30 +40,16 @@ export class ObjectController { * @returns {void} */ async getObjects(req, res) { - const { prefix, fields = [] } = req.query; - if (prefix && typeof prefix !== 'string') { - updateAndSendExpressResponseFromNativeError( - res, - new InvalidInputError('Invalid parameters provided: prefix must be of type string'), - ); - return; - } else if (!Array.isArray(fields)) { - updateAndSendExpressResponseFromNativeError( - res, - new InvalidInputError('Invalid parameters provided: fields must be of type Array'), - ); - return; - } else { - try { - const list = await this._objService.retrieveLatestVersionOfObjects(prefix, fields); - res.status(200).json(list); - } catch (error) { - updateAndSendExpressResponseFromNativeError( - res, - new Error('Failed to retrieve list of objects latest version'), - ); - this._logger.errorMessage(error); - } + try { + const { prefix, fields, filters } = req.query; + + const list = await this._objService.retrieveLatestVersionOfObjects({ prefix, fields, filters }); + res.status(200).json(list); + } catch (error) { + const responseError = new Error('Failed to retrieve list of objects latest version'); + + logger.errorMessage(`Error whilst retrieving objects: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); } } @@ -79,23 +65,16 @@ export class ObjectController { * @returns {void} */ async getObjectContent(req, res) { - const { path, validFrom, id, filters } = req.query; - if (!path) { - updateAndSendExpressResponseFromNativeError( - res, - new InvalidInputError('Invalid URL parameters: missing object path'), - ); - } else { - try { - const object = await this._objService.retrieveQcObject(path, Number(validFrom), id, filters); - res.status(200).json(object); - } catch (error) { - updateAndSendExpressResponseFromNativeError( - res, - new Error('Unable to identify object or read it'), - ); - this._logger.errorMessage(error); - } + try { + const { path, validFrom, filters, id } = req.query; + + const object = await this._objService.retrieveQcObject(path, validFrom, id, filters); + res.status(200).json(object); + } catch (error) { + const responseError = new Error('Failed to retrieve object content'); + + logger.errorMessage(`Error whilst retrieving object content: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); } } @@ -111,25 +90,17 @@ export class ObjectController { * @returns {void} */ async getObjectById(req, res) { - const qcgId = req.params?.id; - const { validFrom, filters, id } = req.query; - if (!qcgId) { - updateAndSendExpressResponseFromNativeError( - res, - new InvalidInputError('Invalid URL parameters: missing object ID'), - ); - return; - } else { - try { - const object = await this._objService.retrieveQcObjectByQcgId(qcgId, id, validFrom, filters); - res.status(200).json(object); - } catch (error) { - updateAndSendExpressResponseFromNativeError( - res, - new NotFoundError('Unable to identify object or read it by qcg id'), - ); - this._logger.errorMessage(error); - } + try { + const qcObjectId = req.params.id; + const { validFrom, filters, id } = req.query; + + const object = await this._objService.retrieveQcObjectByQcgId(qcObjectId, id, validFrom, filters); + res.status(200).json(object); + } catch (error) { + const responseError = new Error('Unable to identify object or read it by qcg id'); + + logger.errorMessage(`Error whilst retrieving object: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); } } } diff --git a/QualityControl/lib/dtos/ObjectGetDto.js b/QualityControl/lib/dtos/ObjectGetDto.js new file mode 100644 index 000000000..26846722e --- /dev/null +++ b/QualityControl/lib/dtos/ObjectGetDto.js @@ -0,0 +1,97 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import Joi from 'joi'; + +const periodNamePattern = /^LHC\d{1,2}[a-z]+$/i; + +/** + * Creates and returns a filters schema for object DTOs + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for filters + */ +function createFiltersSchema(runTypes) { + return Joi.object({ + RunNumber: Joi.number().integer().min(0).max(999999).optional(), + RunType: runTypes.length > 0 + ? Joi.string().valid(...runTypes).optional() + : Joi.string().optional(), + PeriodName: Joi.string().pattern(periodNamePattern).optional(), + PassName: Joi.string().optional(), + }).optional(); +} + +/** + * Creates and returns the base object get schema + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for base object get + */ +function createBaseObjectGetDto({ runTypes }) { + return Joi.object({ + token: Joi.string().required(), + id: Joi.string().optional(), + validFrom: Joi.number().optional().min(0), + filters: createFiltersSchema(runTypes), + }).options({ allowUnknown: false }); +} + +/** + * Creates and returns the base objects get schema + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for base objects get + */ +function createBaseObjectsGetDto({ runTypes }) { + return Joi.object({ + token: Joi.string().required(), + fields: Joi.array().default([]).items(Joi.string()), + filters: createFiltersSchema(runTypes), + }).options({ allowUnknown: false }); +} + +/** + * Creates and returns the ObjectsGetDto schema + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for getting multiple objects + */ +export function createObjectsGetDto({ runTypes }) { + return createBaseObjectsGetDto({ runTypes }).keys({ + prefix: Joi.string(), + }); +} + +/** + * Creates and returns the ObjectContentsGetDto schema + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for getting object contents + */ +export function createObjectContentsGetDto({ runTypes }) { + return createBaseObjectGetDto({ runTypes }).keys({ + path: Joi.string().required(), + }); +} + +/** + * Creates and returns the ObjectGetByIdDto schema + * @param {Array} runTypes - Array of valid run types + * @returns {Joi.ObjectSchema} Joi validation schema for getting an object by ID + */ +export function createObjectGetByIdDto({ runTypes }) { + return createBaseObjectGetDto({ runTypes }); // doesn't require any alterations. +} + +/** + * Joi validation schema for object ID in URL + */ +export const qcObjectIdDto = + Joi.string().required().trim().min(1).messages({ 'string.empty': 'Missing object ID in URL' }); diff --git a/QualityControl/lib/middleware/objects/objectGetByIdValidationMiddlewareFactory.js b/QualityControl/lib/middleware/objects/objectGetByIdValidationMiddlewareFactory.js new file mode 100644 index 000000000..d606ee825 --- /dev/null +++ b/QualityControl/lib/middleware/objects/objectGetByIdValidationMiddlewareFactory.js @@ -0,0 +1,41 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { InvalidInputError, LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui'; +import { createObjectGetByIdDto, qcObjectIdDto } from '../../dtos/ObjectGetDto.js'; + +const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/object-middleware`); + +/** + * Factory function to create object validation middleware with dynamic RUN_TYPES + * @param {FilterService} filterService - Service providing run types + * @returns {function(req, res, next): undefined} - Express middleware function + */ +export function objectGetByIdValidationMiddlewareFactory(filterService) { + const { runTypes } = filterService; + const ObjectGetByIdDto = createObjectGetByIdDto({ runTypes }); + + return async (req, res, next) => { + try { + req.params.id = await qcObjectIdDto.validateAsync(req.params?.id); + req.query = await ObjectGetByIdDto.validateAsync(req.query); + return next(); + } catch (error) { + const responseError = new InvalidInputError(`Invalid query parameters: ${error.details[0].message}`); + + logger.errorMessage(`Error validating query parameters: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); + } + }; +} diff --git a/QualityControl/lib/middleware/objects/objectGetContentsValidationMiddlewareFactory.js b/QualityControl/lib/middleware/objects/objectGetContentsValidationMiddlewareFactory.js new file mode 100644 index 000000000..3fcf9967d --- /dev/null +++ b/QualityControl/lib/middleware/objects/objectGetContentsValidationMiddlewareFactory.js @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { InvalidInputError, LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui'; +import { createObjectContentsGetDto } from '../../dtos/ObjectGetDto.js'; + +const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/object-middleware`); + +/** + * Factory function to create object validation middleware with dynamic RUN_TYPES + * @param {FilterService} filterService - Service providing run types + * @returns {function(req, res, next): undefined} - Express middleware function + */ +export function objectGetContentsValidationMiddlewareFactory(filterService) { + const { runTypes } = filterService; + const ObjectContentsGetDto = createObjectContentsGetDto({ runTypes }); + + return async (req, res, next) => { + try { + req.query = await ObjectContentsGetDto.validateAsync(req.query); + return next(); + } catch (error) { + const responseError = new InvalidInputError(`Invalid query parameters: ${error.details[0].message}`); + + logger.errorMessage(`Error validating query parameters: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); + } + }; +} diff --git a/QualityControl/lib/middleware/objects/objectsGetValidationMiddlewareFactory.js b/QualityControl/lib/middleware/objects/objectsGetValidationMiddlewareFactory.js new file mode 100644 index 000000000..91e9a126f --- /dev/null +++ b/QualityControl/lib/middleware/objects/objectsGetValidationMiddlewareFactory.js @@ -0,0 +1,40 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { InvalidInputError, LogManager, updateAndSendExpressResponseFromNativeError } from '@aliceo2/web-ui'; +import { createObjectsGetDto } from '../../dtos/ObjectGetDto.js'; + +const logger = LogManager.getLogger(`${process.env.npm_config_log_label ?? 'qcg'}/object-middleware`); + +/** + * Factory function to create object validation middleware with dynamic RUN_TYPES + * @param {FilterService} filterService - Service providing run types + * @returns {function(req, res, next): undefined} - Express middleware function + */ +export function objectsGetValidationMiddlewareFactory(filterService) { + const { runTypes } = filterService; + const ObjectsGetDto = createObjectsGetDto({ runTypes }); + + return async (req, res, next) => { + try { + req.query = await ObjectsGetDto.validateAsync(req.query); + return next(); + } catch (error) { + const responseError = new InvalidInputError(`Invalid query parameters: ${error.details[0].message}`); + + logger.errorMessage(`Error validating query parameters: ${error}`); + updateAndSendExpressResponseFromNativeError(res, responseError); + } + }; +} diff --git a/QualityControl/lib/services/QcObject.service.js b/QualityControl/lib/services/QcObject.service.js index 9606f3245..e08d8da1d 100644 --- a/QualityControl/lib/services/QcObject.service.js +++ b/QualityControl/lib/services/QcObject.service.js @@ -89,19 +89,28 @@ export class QcObjectService { * * from cache if it is requested by the client and the system is configured to use a cache; * * make a new request and get data directly from data service * * @example Equivalent of URL request: `/latest/qc/TPC/object.*` - * @param {string|Regex} prefix - Prefix for which CCDB should search for objects. - * @param {Array} [fields = []] - List of fields that should be requested for each object - * @param {boolean} [useCache = true] - if the list should be the cached version or not + * @param {object} options - An object that contains query parameters among other arguments + * @param {string|Regex} options.prefix - Prefix for which CCDB should search for objects. + * @param {Array} options.fields - List of fields that should be requested for each object + * @param {boolean} options.useCache - if the list should be the cached version or not + * @param {Array} options.filters - Filter object by which the objects from ccdb are filtered. * @returns {Promise.>} - results of objects with required fields * @rejects {Error} */ - async retrieveLatestVersionOfObjects(prefix = this._dbService.PREFIX, useCache = true) { - if (useCache && this._cache?.objects) { + async retrieveLatestVersionOfObjects({ prefix = this._dbService.PREFIX, fields, useCache = true, filters }) { + const hasFilters = typeof filters === 'object' && Object.keys(filters).length; + + if (!hasFilters && useCache && this._cache.objects?.length) { return this._cache.objects.filter((object) => object.name.startsWith(prefix)); + } + + let objects = []; + if (!hasFilters) { + objects = await this._dbService.getObjectsTreeList(prefix); } else { - const objects = await this._dbService.getObjectsTreeList(prefix); // TreeList links to the latest - return this._parseObjects(objects); + objects = await this._dbService.getObjectsLatestVersionList({ prefix, filters, fields }); } + return this._parseObjects(objects); } /** diff --git a/QualityControl/lib/services/ccdb/CcdbService.js b/QualityControl/lib/services/ccdb/CcdbService.js index 8e2f8bd13..46f9629c2 100644 --- a/QualityControl/lib/services/ccdb/CcdbService.js +++ b/QualityControl/lib/services/ccdb/CcdbService.js @@ -112,7 +112,6 @@ export class CcdbService { if (!Array.isArray(subfolders)) { throw new FailedDependencyError('Invalid response format from server - expected subfolders array'); } - // console.log(await this.getObjectsLatestVersionList(prefix)); return subfolders.map((folder) => ({ path: folder })); } @@ -126,18 +125,21 @@ export class CcdbService { * * Attributes of objects wished to be requested for each object can be passed through the fields parameter; * If attributes list is missing, a default minimal list will be used: PATH, CREATED, LAST_MODIFIED - * @example Equivalent of URL request: `/latest/qc/TPC/object.*` - * @param {string} [prefix] - Prefix for which CCDB should search for objects - * @param {Array} [fields] - List of fields that should be requested for each object + * @example Equivalent of URL request: `/latest/qc/TPC/object.* /RunNumber=42` + * @param {object} options - An object that contains the arguments + * @param {string} options.prefix - Prefix for which CCDB should search for objects + * @param {object} options.filters - Object metadata that will be used to construct a endpoint path. + * @param {Array} options.fields - List of fields that should be requested for each object * @returns {Promise.>} - results of objects query or error - * @rejects {Error} */ - async getObjectsLatestVersionList(prefix = this._PREFIX, fields = []) { - const headers = { - accept: 'application/json', - 'x-filter-fields': fields.length > 0 ? fields.join(',') : `${PATH},${CREATED},${LAST_MODIFIED}`, - }; - const { objects } = await httpGetJson(this._hostname, this._port, `/latest/${prefix}.*`, { headers }); + async getObjectsLatestVersionList({ prefix = this._PREFIX, filters, fields } = {}) { + fields = fields?.length ? fields : [PATH, CREATED, LAST_MODIFIED]; + const identification = { path: `${prefix}.*`, filters }; + + const headers = { accept: 'application/json', 'x-filter-fields': fields.join(',') }; + + const path = `/latest${this._buildCcdbUrlPath(identification)}`; + const { objects } = await httpGetJson(this._hostname, this._port, path, { headers }); return objects; } diff --git a/QualityControl/package-lock.json b/QualityControl/package-lock.json index a131bc6de..8995cb761 100644 --- a/QualityControl/package-lock.json +++ b/QualityControl/package-lock.json @@ -22,15 +22,15 @@ "umzug": "^3.8.2" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", "nock": "^14.0.4", "puppeteer": "^24.10.0", "sequelize-cli": "^6.6.2", - "sinon": "^20.0.0", + "sinon": "^21.0.0", "supertest": "^7.1.0" }, "engines": { @@ -295,9 +295,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", + "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -370,9 +370,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.29.0.tgz", + "integrity": "sha512-3PIF4cBw/y+1u2EazflInpV+lYsSG0aByVIQzAgb1m1MhHFSbqTyNqtBKHgWf/9Ykud+DhILS9EGkmekVhbKoQ==", "dev": true, "license": "MIT", "engines": { @@ -1216,9 +1216,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", "bin": { @@ -1331,13 +1331,13 @@ } }, "node_modules/are-docs-informative": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.1.1.tgz", + "integrity": "sha512-sqRsNQBwbKLRX0jV5Cu5uzmtflf892n4Vukz7T659ebL4pz3mpOqCMU7lxMoBTFwnp10E3YB5ZcyHM41W5bcDA==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/argparse": { @@ -2564,19 +2564,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.29.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.29.0.tgz", + "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", + "@eslint/config-array": "^0.20.1", "@eslint/config-helpers": "^0.2.1", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.29.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -2588,9 +2588,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2625,14 +2625,14 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "50.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-50.7.1.tgz", - "integrity": "sha512-XBnVA5g2kUVokTNUiE1McEPse5n9/mNUmuJcx52psT6zBs2eVcXSmQBvjfa7NZdfLVSy3u1pEDDUxoxpwy89WA==", + "version": "51.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-51.0.1.tgz", + "integrity": "sha512-nnH6O8uk0Wp5EvHlVEPESKdGWTlu5g1tfBUZmL/jMZLBpUtttxxW+9hPzTMCYmYsQ3HwDsJdHJAiaDRKsP6iUg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.50.2", - "are-docs-informative": "^0.0.2", + "are-docs-informative": "^0.1.1", "comment-parser": "1.4.1", "debug": "^4.4.1", "escape-string-regexp": "^4.0.0", @@ -2643,16 +2643,16 @@ "spdx-expression-parse": "^4.0.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, "node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2667,9 +2667,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2696,15 +2696,15 @@ } }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5799,9 +5799,9 @@ "license": "MIT" }, "node_modules/sinon": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-20.0.0.tgz", - "integrity": "sha512-+FXOAbdnj94AQIxH0w1v8gzNxkawVvNqE3jUzRLptR71Oykeu2RrQXXl/VQjKay+Qnh73fDt/oDfMo6xMeDQbQ==", + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.0.tgz", + "integrity": "sha512-TOgRcwFPbfGtpqvZw+hyqJDvqfapr1qUlOizROIk4bBLjlsjlB00Pg6wMFXNtJRpu+eCZuVOaLatG7M8105kAw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { diff --git a/QualityControl/package.json b/QualityControl/package.json index bc8b7cb69..a9a5209ba 100644 --- a/QualityControl/package.json +++ b/QualityControl/package.json @@ -45,15 +45,15 @@ "umzug": "^3.8.2" }, "devDependencies": { - "@eslint/js": "^9.28.0", + "@eslint/js": "^9.29.0", "@stylistic/eslint-plugin-js": "^4.4.0", - "eslint": "^9.28.0", - "eslint-plugin-jsdoc": "^50.7.1", + "eslint": "^9.29.0", + "eslint-plugin-jsdoc": "^51.0.1", "globals": "^16.2.0", "nock": "^14.0.4", "puppeteer": "^24.10.0", "sequelize-cli": "^6.6.2", - "sinon": "^20.0.0", + "sinon": "^21.0.0", "supertest": "^7.1.0" }, "bundleDependencies": [ diff --git a/QualityControl/public/app.css b/QualityControl/public/app.css index c716b9c03..1a44e7e01 100644 --- a/QualityControl/public/app.css +++ b/QualityControl/public/app.css @@ -100,3 +100,18 @@ border: 1px solid #ddd; background: var(--color-gray-light); } + +.error-box { + background-color: #f8d7da; + height: 100%; + max-width: 100%; + word-break: break-word; + overflow-wrap: break-word; + border-radius: 5px; + border: 1px solid var(--color-danger); +} + +.error-icon { + font-size: 2em; + margin-bottom: .5em; +} diff --git a/QualityControl/public/object/objectDraw.js b/QualityControl/public/object/objectDraw.js index e01d44801..339800fe1 100644 --- a/QualityControl/public/object/objectDraw.js +++ b/QualityControl/public/object/objectDraw.js @@ -14,7 +14,7 @@ /* global JSROOT */ -import { h } from '/js/src/index.js'; +import { h, iconWarning } from '/js/src/index.js'; import { timerDebouncer, pointerId } from '../common/utils.js'; import { isObjectOfTypeChecker } from './../library/qcObject/utils.js'; import checkersPanel from './../common/object/checkersPanel.js'; @@ -123,9 +123,10 @@ export function draw(model, tabObject, options) { // Not asked yet or loading return h('.flex-column.items-center.justify-center', [h('.animate-slow-appearance', 'Loading')]); } else if (objectRemoteData.isFailure()) { - return h('.scroll-y.p1.f6.text-center', { - style: 'word-break: break-all;', - }, objectRemoteData.payload); + return h('.error-box.danger.flex-column.justify-center.f6.text-center', {}, [ + h('span.error-icon', { title: 'Error' }, iconWarning()), + h('span', objectRemoteData.payload), + ]); } else { if (isObjectOfTypeChecker(objectRemoteData.payload.qcObject.root)) { return checkersPanel(objectRemoteData.payload.qcObject.root); diff --git a/QualityControl/test/api/objects/api-get-object.test.js b/QualityControl/test/api/objects/api-get-object.test.js new file mode 100644 index 000000000..f18fd4c11 --- /dev/null +++ b/QualityControl/test/api/objects/api-get-object.test.js @@ -0,0 +1,120 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { suite, test } from 'node:test'; +import { OWNER_TEST_TOKEN, URL_ADDRESS } from '../config.js'; +import request from 'supertest'; +import { deepStrictEqual, strictEqual } from 'node:assert'; +import { MOCK_OBJECT_BY_ID_RESULT, OBJECT_BY_PATH_RESULT, OBJECT_LATEST_FILTERED_BY_RUN_NUMBER, OBJECT_VERSIONS, + OBJECT_VERSIONS_FILTERED_BY_RUN_NUMBER, TREE_API_OBJECTS } from '../../setup/seeders/ccdbObjects.js'; + +export const apiGetObjectsTests = () => { + suite('GET /object', () => { + test('should return QCObject details with all versions', async () => { + const url = `${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}&path=qc/test/object/1`; + await testResult(url, 200, OBJECT_BY_PATH_RESULT, OBJECT_VERSIONS); + }); + + test('should return QCObject versions if a filter is added', async () => { + const url = `${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}&path=qc/test/object/1&filters[RunNumber]=0`; + await testResult(url, 200, OBJECT_BY_PATH_RESULT, OBJECT_VERSIONS_FILTERED_BY_RUN_NUMBER); + }); + + test('should return 400 if path parameter is missing', async () => { + await testResult(`${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}`, 400, { + message: 'Invalid query parameters: "path" is required', status: 400, title: 'Invalid Input' }); + }); + + test('should return 400 if path parameter is not a string', async () => { + await testResult(`${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}&path[]=array`, 400, { + message: 'Invalid query parameters: "path" must be a string', status: 400, title: 'Invalid Input' }); + }); + + test('should return 500 if service fails to retrieve object', async () => { + const url = `${URL_ADDRESS}/api/object?token=${OWNER_TEST_TOKEN}&path=invalid/path`; + await testResult(url, 500, { message: 'Failed to retrieve object content', status: 500, title: 'Unknown Error' }); + }); + }); + + suite('GET /object/:id', () => { + const objectId = '6724a6bd1b2bad3d713cc4ee'; + + test('should return QCObject details with all versions', async () => { + const url = `${URL_ADDRESS}/api/object/${objectId}?token=${OWNER_TEST_TOKEN}`; + await testResult(url, 200, MOCK_OBJECT_BY_ID_RESULT, OBJECT_VERSIONS); + }); + + test('should return QCObject versions if a filter is added', async () => { + const url = `${URL_ADDRESS}/api/object/${objectId}?token=${OWNER_TEST_TOKEN}&filters[RunNumber]=0`; + await testResult(url, 200, MOCK_OBJECT_BY_ID_RESULT, OBJECT_VERSIONS_FILTERED_BY_RUN_NUMBER); + }); + + test('should return error when filter is a string', async () => { + await testResult(`${URL_ADDRESS}/api/object/${objectId}?token=${OWNER_TEST_TOKEN}&filters=RunNumber=0`, 400, { + message: 'Invalid query parameters: "filters" must be of type object', status: 400, title: 'Invalid Input' }); + }); + + test('should return 400 if ID parameter is missing', async () => { + await testResult(`${URL_ADDRESS}/api/object/%20?token=${OWNER_TEST_TOKEN}`, 400, { + message: 'Invalid query parameters: Missing object ID in URL', status: 400, title: 'Invalid Input' }); + }); + + test('should return 500 if service fails to retrieve object by ID', async () => { + await testResult(`${URL_ADDRESS}/api/object/invalid_id?token=${OWNER_TEST_TOKEN}`, 500, { + message: 'Unable to identify object or read it by qcg id', status: 500, title: 'Unknown Error' }); + }); + }); + + suite('GET /objects', () => { + test('should return object names when no filter is provided', async () => { + await testResult(`${URL_ADDRESS}/api/objects?token=${OWNER_TEST_TOKEN}`, 200, TREE_API_OBJECTS); + }); + + test('should return detailed objects when filter is provided', async () => { + const url = `${URL_ADDRESS}/api/objects?token=${OWNER_TEST_TOKEN}&filters[RunNumber]=0`; + await testResult(url, 200, OBJECT_LATEST_FILTERED_BY_RUN_NUMBER); + }); + + test('should return 400 if prefix is not a string', async () => { + await testResult(`${URL_ADDRESS}/api/objects?token=${OWNER_TEST_TOKEN}&prefix[]=array`, 400, { + message: 'Invalid query parameters: "prefix" must be a string', status: 400, title: 'Invalid Input' }); + }); + + test('should return 400 if fields is not an array', async () => { + await testResult(`${URL_ADDRESS}/api/objects?token=${OWNER_TEST_TOKEN}&fields=not_an_array`, 400, { + message: 'Invalid query parameters: "fields" must be an array', status: 400, title: 'Invalid Input' }); + }); + }); +}; + +/** + * Unified test helper function + * @param {string} url - Full URL to test + * @param {number} expectedStatus - Expected HTTP status code + * @param {object} expectedBody - Expected response body + * @param {Array} [expectedVersions] - Optional expected versions array (for object tests) + */ +async function testResult(url, expectedStatus, expectedBody, expectedVersions = undefined) { + const response = await request(url).get(''); + const { versions } = response.body; + delete response.body.versions; + delete response.body.root; + + if (expectedVersions) { + deepStrictEqual(versions, expectedVersions, 'Versions do not match up'); + } + + deepStrictEqual(response.body, expectedBody, 'Unexpected response body'); + strictEqual(response.status, expectedStatus, 'Unexpected status code'); +} diff --git a/QualityControl/test/lib/controllers/ObjectController.test.js b/QualityControl/test/lib/controllers/ObjectController.test.js index d0c7bf892..d2f521f09 100644 --- a/QualityControl/test/lib/controllers/ObjectController.test.js +++ b/QualityControl/test/lib/controllers/ObjectController.test.js @@ -1,194 +1,186 @@ -import { strict as assert } from 'assert'; -import { suite, test, beforeEach } from 'node:test'; -import { ObjectController } from '../../../lib/controllers/ObjectController.js'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import test, { afterEach, beforeEach, suite } from 'node:test'; +import { ok } from 'node:assert'; import sinon from 'sinon'; +import { ObjectController } from '../../../lib/controllers/ObjectController.js'; +import { QcObjectService } from '../../../lib/services/QcObject.service.js'; +import { NotFoundError } from '@aliceo2/web-ui'; export const objectControllerTestSuite = async () => { - suite('ObjectController test suite', () => { - let res = null; - let req = null; - let qcObjectServiceStub = null; - let objectController = null; - - beforeEach(() => { - res = { - status: sinon.stub().returnsThis(), - json: sinon.stub(), - }; - req = { - params: {}, - query: {}, - }; - qcObjectServiceStub = { - retrieveLatestVersionOfObjects: sinon.stub(), - retrieveQcObjectByQcgId: sinon.stub(), - retrieveQcObject: sinon.stub(), - }; - objectController = new ObjectController(qcObjectServiceStub); - }); + let QcObjectServiceMock = null; + let reqMock = null; + let resMock = null; + let objectController = null; + + beforeEach(() => { + resMock = { + status: sinon.stub().returnsThis(), + send: sinon.spy(), + json: sinon.spy(), + }; + reqMock = { + query: { token: 'someToken' }, + params: {}, + }; + }); - suite('`getObjects()` tests', () => { - test('should return 400 if prefix is not a string', async () => { - const req = { - query: { prefix: 123, fields: [] }, - }; + afterEach(() => { + sinon.restore(); + }); - await objectController.getObjects(req, res); + suite('getObjects() tests', () => { + const mockObjectsList = [ + { objectName: 'object1', path: 'qc/path/object1' }, + { objectName: 'object2', path: 'qc/path/object2' }, + ]; - assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({ - message: 'Invalid parameters provided: prefix must be of type string', - title: 'Invalid Input', - status: 400, - })); + test('should successfully retrieve objects list without prefix', async () => { + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveLatestVersionOfObjects: sinon.stub().resolves(mockObjectsList), }); - test('should return 400 if fields is not an array', async () => { - const req = { - query: { prefix: 'some/path', fields: 'not-an-array' }, - }; - - await objectController.getObjects(req, res); + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjects(reqMock, resMock); - assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({ - message: 'Invalid parameters provided: fields must be of type Array', - title: 'Invalid Input', - status: 400, - })); - }); + ok(resMock.status.calledWith(200)); + ok(resMock.json.calledWith(mockObjectsList)); - test('should return 200 and list of objects if parameters are valid', async () => { - const mockList = [{ name: 'object1' }, { name: 'object2' }]; - qcObjectServiceStub.retrieveLatestVersionOfObjects.resolves(mockList); - - const req = { - query: { prefix: 'valid/path', fields: ['field1', 'field2'] }, - }; + ok(QcObjectServiceMock.retrieveLatestVersionOfObjects.calledWith({ + prefix: undefined, fields: undefined, filters: undefined })); + }); - await objectController.getObjects(req, res); + test('should successfully retrieve objects list with prefix and fields', async () => { + reqMock.query.prefix = 'qc/path'; + reqMock.query.fields = ['objectName', 'path']; - assert.ok(res.status.calledWith(200)); - assert.ok(res.json.calledWith(mockList)); + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveLatestVersionOfObjects: sinon.stub().resolves(mockObjectsList), }); - test('should return 500 if service throws error', async () => { - qcObjectServiceStub.retrieveLatestVersionOfObjects.rejects(new Error('Service failed')); - - const req = { - query: { prefix: 'valid/path', fields: ['f'] }, - }; + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjects(reqMock, resMock); - await objectController.getObjects(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({ - message: 'Failed to retrieve list of objects latest version', - title: 'Unknown Error', - status: 500, - })); - }); + ok(resMock.status.calledWith(200)); + ok(QcObjectServiceMock.retrieveLatestVersionOfObjects + .calledWith({ prefix: 'qc/path', fields: ['objectName', 'path'], filters: undefined })); }); - suite('`getObjectsContent()` test suite', () => { - test('should return a 400 error if URL is invalid', async () => { - await objectController.getObjectContent(req, res); - assert.ok(res.json.calledWith({ - message: 'Invalid URL parameters: missing object path', - title: 'Invalid Input', - status: 400, - })); + test('should handle service errors when retrieving objects', async () => { + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveLatestVersionOfObjects: sinon.stub().rejects(new NotFoundError('Object not found')), }); - test('should return 200 with the QcObject', async () => { - const mockObject = { id: 'obj1', name: 'Test Object' }; - - req.query = { - path: 'valid/path.json', - validFrom: '1700000000', - id: 'id123', - filters: JSON.stringify({ a: 'b', empty: '' }), - }; - qcObjectServiceStub.retrieveQcObject.resolves(mockObject); + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjects(reqMock, resMock); - await objectController.getObjectContent(req, res); + ok(resMock.status.calledWith(500)); + ok(resMock.json.calledWithMatch({ + message: 'Failed to retrieve list of objects latest version', + status: 500, + title: 'Unknown Error', + })); + }); + }); - assert.ok(res.status.calledWith(200)); - assert.ok(res.json.calledWith(mockObject)); - }); - test('should return a 500 error if service fails', async () => { - const error = new Error('Service failure'); - - req.query = { - path: 'valid/path.json', - validFrom: '1700000000', - id: 'id123', - filters: JSON.stringify({ key: 'value' }), - }; - - qcObjectServiceStub.retrieveQcObject.rejects(error); - - objectController.updateAndSendExpressResponseFromNativeError = (res, err) => { - res.status(500).json({ - message: err.message, - title: 'Unknown error', - status: 500, - }); - }; - - await objectController.getObjectContent(req, res); - - assert.ok(res.status.calledWith(500)); - assert.ok(res.json.calledWith({ - message: 'Unable to identify object or read it', - title: 'Unknown Error', - status: 500, - })); + suite('getObjectContent() tests', () => { + const stubObject = { + path: 'qc/path', + versions: [ + { + validFrom: 1736424299423, + id: '4f9917a2-ce82-11ef-936c-c0a80209250c', + createdAt: 1736424454827, + }, + { + validFrom: 1736420279131, + id: '21a6de32-ce79-11ef-936b-c0a80209250c', + createdAt: 1736420512272, + }, + ], + }; + + test('should successfully retrieve object content', async () => { + reqMock.query.path = stubObject.path; + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveQcObject: sinon.stub().resolves(stubObject), }); - }); - suite('`getObjectById()` tests', () => { - test('should return 400 if ID param is missing', async () => { - await objectController.getObjectById(req, res); + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjectContent(reqMock, resMock); - assert.ok(res.status.calledWith(400)); - assert.ok(res.json.calledWith({ - message: 'Invalid URL parameters: missing object ID', - title: 'Invalid Input', - status: 400, - })); - }); + ok(resMock.status.calledWith(200)); + ok(resMock.json.calledWith(stubObject)); + ok(QcObjectServiceMock.retrieveQcObject.calledWith(stubObject.path, undefined, undefined, undefined)); + }); - test('should return 200 and the object if retrieval is successful', async () => { - const mockObject = { name: 'ObjectName' }; - req.params.id = 'abc123'; - req.query = { id: 'sub-id', validFrom: '2024-01-01', filters: { foo: 'bar' } }; + test('should handle service errors when retrieving object content', async () => { + reqMock.query.path = 'qc/test'; + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveQcObject: sinon.stub().rejects(new NotFoundError('Object not found')), + }); - qcObjectServiceStub.retrieveQcObjectByQcgId.resolves(mockObject); + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjectContent(reqMock, resMock); - await objectController.getObjectById(req, res); + ok(resMock.status.calledWith(500)); + ok(resMock.json.calledWithMatch({ + message: 'Failed to retrieve object content', + status: 500, + title: 'Unknown Error', + })); + }); + }); - assert.ok(qcObjectServiceStub.retrieveQcObjectByQcgId - .calledWith('abc123', 'sub-id', '2024-01-01', { foo: 'bar' })); - assert.ok(res.status.calledWith(200)); - assert.ok(res.json.calledWith(mockObject)); + suite('getObjectById() tests', () => { + const mockObject = { + id: '21a6de32-ce79-11ef-936b-c0a80209250c', + path: 'qc/path/object', + validFrom: 1736420279131, + }; + + test('should successfully retrieve object by QCG ID', async () => { + reqMock.params.id = mockObject.id; + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveQcObjectByQcgId: sinon.stub().resolves(mockObject), }); - test('should call errorHandler with 404 if service throws error', async () => { - req.params.id = 'abc123'; - req.query = { id: 'some-id' }; + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjectById(reqMock, resMock); - const error = new Error('Service failure'); - qcObjectServiceStub.retrieveQcObjectByQcgId.rejects(error); + ok(resMock.status.calledWith(200)); + ok(resMock.json.calledWith(mockObject)); + ok(QcObjectServiceMock.retrieveQcObjectByQcgId.calledWith(mockObject.id, undefined, undefined, undefined)); + }); - await objectController.getObjectById(req, res); - assert.ok(res.json.calledWith({ - message: 'Unable to identify object or read it by qcg id', - title: 'Not Found', - status: 404, - })); - assert.ok(res.status.calledWith(404)); + test('should handle service errors when retrieving object by ID', async () => { + reqMock.params.id = 'some-id'; + QcObjectServiceMock = sinon.createStubInstance(QcObjectService, { + retrieveQcObjectByQcgId: sinon.stub().rejects(new NotFoundError('Object not found')), }); + + objectController = new ObjectController(QcObjectServiceMock); + await objectController.getObjectById(reqMock, resMock); + + ok(resMock.status.calledWith(500)); + ok(resMock.json.calledWithMatch({ + message: 'Unable to identify object or read it by qcg id', + status: 500, + title: 'Unknown Error', + })); + ok(QcObjectServiceMock.retrieveQcObjectByQcgId.calledWith('some-id', undefined, undefined, undefined)); }); }); }; diff --git a/QualityControl/test/lib/middlewares/objects/objectGetByContentsValidation.middleware.test.js b/QualityControl/test/lib/middlewares/objects/objectGetByContentsValidation.middleware.test.js new file mode 100644 index 000000000..b78a719d1 --- /dev/null +++ b/QualityControl/test/lib/middlewares/objects/objectGetByContentsValidation.middleware.test.js @@ -0,0 +1,198 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { beforeEach, suite, test } from 'node:test'; +import { ok } from 'node:assert'; +import sinon from 'sinon'; +import { objectGetContentsValidationMiddlewareFactory } + from '../../../../lib/middleware/objects/objectGetContentsValidationMiddlewareFactory.js'; + +/** + * Test suite for the middleware that validates query parameters + */ + +export const objectGetContentsValidationMiddlewareTest = () => { + suite('Object get contents validation Middleware', () => { + let req = {}; + let res = {}; + let next = {}; + let middleWare = {}; + + let mockFilterService = {}; + let runTypes = []; + + beforeEach(() => { + runTypes = ['PHYSICS', 'PROTON-PROTON', '0', '1', '2']; + mockFilterService = { runTypes }; + + req = { + query: { + token: 'valid-token', + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + + middleWare = objectGetContentsValidationMiddlewareFactory(mockFilterService); + }); + suite('getObjectContent() tests', () => { + test('should successfully validate a request with token and path', async () => { + req.query.path = 'valid/path'; + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() on successful validation'); + ok(res.status.notCalled, 'Should not set status on success'); + ok(res.json.notCalled, 'Should not send response on success'); + }); + + test('should reject request without token', async () => { + delete req.query.token; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "token" is required', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with a non-string path', async () => { + req.query.path = 1; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "path" must be a string', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with empty path', async () => { + req.query.path = ''; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "path" is not allowed to be empty', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should successfully validate request with valid filters', async () => { + req.query.path = 'valid/path'; + req.query.filters = { + RunNumber: '12345', + RunType: 'PHYSICS', + PeriodName: 'LHC22a', + PassName: 'pass1', + }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() when filters are valid'); + }); + + test('should reject non-object filters', async () => { + req.query.filters = 'PassName=100'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters" must be of type object', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid RunNumber filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { RunNumber: 'abc' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunNumber" must be a number', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid RunType filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { RunType: 'INVALID_TYPE' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunType" must be one of [PHYSICS, PROTON-PROTON, 0, 1, 2]', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid PeriodName filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { PeriodName: 'invalid-period' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.PeriodName" with value "invalid-period"' + + ' fails to match the required pattern: /^LHC\\d{1,2}[a-z]+$/i', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with unknown filter field', async () => { + req.query.path = 'valid/path'; + req.query.filters = { UnknownField: 'value' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.UnknownField" is not allowed', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should successfully validate request with validFrom timestamp', async () => { + req.query.path = 'valid/path'; + req.query.validFrom = '1234567890'; + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() when validFrom is valid'); + }); + + test('should reject request with negative validFrom timestamp', async () => { + req.query.path = 'valid/path'; + req.query.validFrom = '-1'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "validFrom" must be greater than or equal to 0', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with non-numeric validFrom', async () => { + req.query.path = 'valid/path'; + req.query.validFrom = 'not-a-number'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "validFrom" must be a number', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + }); + }); +}; diff --git a/QualityControl/test/lib/middlewares/objects/objectGetByIdValidation.middleware.test.js b/QualityControl/test/lib/middlewares/objects/objectGetByIdValidation.middleware.test.js new file mode 100644 index 000000000..da8f643c1 --- /dev/null +++ b/QualityControl/test/lib/middlewares/objects/objectGetByIdValidation.middleware.test.js @@ -0,0 +1,172 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { beforeEach, suite, test } from 'node:test'; +import { ok } from 'node:assert'; +import sinon from 'sinon'; +import { objectGetByIdValidationMiddlewareFactory } + from '../../../../lib/middleware/objects/objectGetByIdValidationMiddlewareFactory.js'; + +/** + * Test suite for the middleware that validates query parameters + */ + +export const objectGetByIdValidationMiddlewareTest = () => { + suite('Object get by id validation Middleware', () => { + let req = {}; + let res = {}; + let next = {}; + let middleWare = {}; + + let mockFilterService = {}; + let runTypes = []; + + beforeEach(() => { + runTypes = ['PHYSICS', 'PROTON-PROTON', '0', '1', '2']; + mockFilterService = { runTypes }; + + req = { + query: { + token: 'valid-token', + }, + params: { + id: 'valid-id', + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + + middleWare = objectGetByIdValidationMiddlewareFactory(mockFilterService); + }); + + test('should successfully validate a request with valid query parameters', async () => { + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() on successful validation'); + }); + + test('should reject request without token', async () => { + delete req.query.token; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "token" is required', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should successfully validate request with valid filters', async () => { + req.query.filters = { + RunNumber: '12345', + RunType: 'PHYSICS', + PeriodName: 'LHC22a', + PassName: 'pass1', + }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() when filters are valid'); + }); + test('should reject non-object filters', async () => { + req.query.filters = 'PassName=100'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters" must be of type object', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid RunNumber filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { RunNumber: 'abc' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunNumber" must be a number', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid RunType filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { RunType: 'INVALID_TYPE' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunType" must be one of [PHYSICS, PROTON-PROTON, 0, 1, 2]', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with invalid PeriodName filter', async () => { + req.query.path = 'valid/path'; + req.query.filters = { PeriodName: 'invalid-period' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.PeriodName" with value "invalid-period"' + + ' fails to match the required pattern: /^LHC\\d{1,2}[a-z]+$/i', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with unknown filter field', async () => { + req.query.path = 'valid/path'; + req.query.filters = { UnknownField: 'value' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.UnknownField" is not allowed', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should successfully validate request with validFrom timestamp', async () => { + req.query.validFrom = '1234567890'; + await middleWare(req, res, next); + ok(next.calledOnce, 'Should call next() when validFrom is valid'); + }); + + test('should reject request with negative validFrom timestamp', async () => { + req.query.path = 'valid/path'; + req.query.validFrom = '-1'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "validFrom" must be greater than or equal to 0', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject request with non-numeric validFrom', async () => { + req.query.path = 'valid/path'; + req.query.validFrom = 'not-a-number'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "validFrom" must be a number', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + }); +}; diff --git a/QualityControl/test/lib/middlewares/objects/objectsGetValidation.middleware.test.js b/QualityControl/test/lib/middlewares/objects/objectsGetValidation.middleware.test.js new file mode 100644 index 000000000..528630811 --- /dev/null +++ b/QualityControl/test/lib/middlewares/objects/objectsGetValidation.middleware.test.js @@ -0,0 +1,176 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +import { beforeEach, suite, test } from 'node:test'; +import { ok } from 'node:assert'; +import sinon from 'sinon'; +import { objectsGetValidationMiddlewareFactory } from + '../../../../lib/middleware/objects/objectsGetValidationMiddlewareFactory.js'; + +/** + * Test suite for the middleware that validates query parameters + */ + +export const objectsGetValidationMiddlewareTest = () => { + suite('Objects get validation Middleware', () => { + let req = {}; + let res = {}; + let next = {}; + let middleWare = {}; + + let mockFilterService = {}; + let runTypes = []; + + beforeEach(() => { + runTypes = ['PHYSICS', 'PROTON-PROTON', '0', '1', '2']; + mockFilterService = { runTypes }; + + req = { + query: { + token: 'valid-token', + }, + }; + res = { + status: sinon.stub().returnsThis(), + json: sinon.stub(), + }; + next = sinon.stub(); + + middleWare = objectsGetValidationMiddlewareFactory(mockFilterService); + }); + + test('should pass validation with minimal required fields', async () => { + await middleWare(req, res, next); + ok(next.calledOnce, 'Next should be called'); + ok(res.status.notCalled, 'Status should not be called'); + }); + + test('should reject request without token', async () => { + delete req.query.token; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "token" is required', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should accept valid RunNumber filter', async () => { + req.query.filters = { RunNumber: 123456 }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Next should be called'); + }); + + test('should reject invalid RunNumber (too high)', async () => { + req.query.filters = { RunNumber: 1000000 }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunNumber" must be less than or equal to 999999', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject invalid RunNumber (negative)', async () => { + req.query.filters = { RunNumber: -1 }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunNumber" must be greater than or equal to 0', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should accept valid RunType filter', async () => { + req.query.filters = { RunType: 'PHYSICS' }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Next should be called'); + }); + + test('should reject invalid RunType filter', async () => { + req.query.filters = { RunType: 'INVALID_TYPE' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.RunType" must be one of [PHYSICS, PROTON-PROTON, 0, 1, 2]', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should accept valid PeriodName filter', async () => { + req.query.filters = { PeriodName: 'LHC22a' }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Next should be called'); + }); + + test('should reject invalid PeriodName filter (wrong format)', async () => { + req.query.filters = { PeriodName: 'INVALID_PERIOD' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.PeriodName" with value "INVALID_PERIOD"' + + ' fails to match the required pattern: /^LHC\\d{1,2}[a-z]+$/i', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should accept valid PassName filter', async () => { + req.query.filters = { PassName: 'apass1' }; + await middleWare(req, res, next); + ok(next.calledOnce, 'Next should be called'); + }); + + test('should reject non-object filters', async () => { + req.query.filters = 'PassName=100'; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters" must be of type object', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject empty PassName filter', async () => { + req.query.filters = { PassName: 100 }; // not a string + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.PassName" must be a string', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + + test('should reject unknown filter field', async () => { + req.query.filters = { UnknownField: 'value' }; + await middleWare(req, res, next); + ok(res.status.calledWith(400), 'Should return 400 status'); + + ok(res.json.calledWithMatch({ + message: 'Invalid query parameters: "filters.UnknownField" is not allowed', + status: 400, + title: 'Invalid Input', + }), 'Should return validation error'); + }); + }); +}; diff --git a/QualityControl/test/lib/services/CcdbService.test.js b/QualityControl/test/lib/services/CcdbService.test.js index 85ce2f1bf..f01725f3f 100644 --- a/QualityControl/test/lib/services/CcdbService.test.js +++ b/QualityControl/test/lib/services/CcdbService.test.js @@ -19,7 +19,7 @@ import { suite, test, before } from 'node:test'; import nock from 'nock'; import { CcdbService } from '../../../lib/services/ccdb/CcdbService.js'; -import { CCDB_MONITOR, CCDB_VERSION_KEY } from '../../../lib/services/ccdb/CcdbConstants.js'; +import { CCDB_FILTER_FIELDS, CCDB_MONITOR, CCDB_VERSION_KEY } from '../../../lib/services/ccdb/CcdbConstants.js'; const ccdbConfig = { hostname: 'ccdb-local', @@ -27,6 +27,7 @@ const ccdbConfig = { protocol: 'https', prefix: 'qc-test', }; +const { ID, CREATED, PATH } = CCDB_FILTER_FIELDS; export const ccdbServiceTestSuite = async () => { suite('CCDB Test Suite - ', () => { @@ -120,7 +121,7 @@ export const ccdbServiceTestSuite = async () => { test('should reject with error for fields parameter not being a list', async () => { const ccdb = new CcdbService(ccdbConfig); await rejects( - async () => await ccdb.getObjectsLatestVersionList('/qc', 'bad-fields'), + async () => ccdb.getObjectsLatestVersionList({ prefix: '/qc', fields: 'bad-fields' }), new TypeError('fields.join is not a function'), ); }); @@ -147,19 +148,20 @@ export const ccdbServiceTestSuite = async () => { test('should successfully return a list of the objects with specified headers', async () => { const ccdb = new CcdbService(ccdbConfig); const objects = [ - { path: 'object/one', Created: '101', 'Last-Modified': '102', Id: 1 }, - { path: 'object/two', Created: '101', 'Last-Modified': '102', Id: 2 }, - { path: 'object/three', Created: '101', 'Last-Modified': '102', Id: 3 }, + { [PATH]: 'object/one', [CREATED]: '101', [ID]: 1 }, + { [PATH]: 'object/two', [CREATED]: '101', [ID]: 2 }, + { [PATH]: 'object/three', [CREATED]: '101', [ID]: 3 }, ]; nock('http://ccdb-local:8083', { reqheaders: { Accept: 'application/json', - 'X-Filter-Fields': 'Id', + 'X-Filter-Fields': [ID, CREATED, PATH].join(','), }, }) .get('/latest/.*') .reply(200, { objects: objects, subfolders: [] }); - const objectsRetrieved = await ccdb.getObjectsLatestVersionList('', ['Id']); + const objectsRetrieved = await ccdb.getObjectsLatestVersionList({ + prefix: '', filters: undefined, fields: [ID, CREATED, PATH] }); deepStrictEqual(objectsRetrieved, objects, 'Received objects are not alike'); }); @@ -171,6 +173,25 @@ export const ccdbServiceTestSuite = async () => { .replyWithError(error); await rejects(async () => await ccdb.getObjectsLatestVersionList(), new Error(`${error.message || error}`)); }); + + test('should send HTTP request with filters', async () => { + const ccdb = new CcdbService(ccdbConfig); + const objects = [{ [ID]: 1 }, { [ID]: 2 }, { [ID]: 3 }]; + nock('http://ccdb-local:8083', { + reqheaders: { + Accept: 'application/json', + 'X-Filter-Fields': ID, + }, + }) + .get('/latest/.*/RunNumber=535/RunType=PHYSICS') + .reply(200, { objects: objects, subfolders: [] }); + + const objectsRetrieved = + await ccdb.getObjectsLatestVersionList({ + prefix: '', filters: { RunNumber: 535, RunType: 'PHYSICS' }, fields: [ID] }); + + deepStrictEqual(objectsRetrieved, objects, 'Received objects are not alike'); + }); }); suite('`getObjectsTreeList()` tests', () => { diff --git a/QualityControl/test/setup/seeders/ccdbObjects.js b/QualityControl/test/setup/seeders/ccdbObjects.js index b1b43ab14..04066287a 100644 --- a/QualityControl/test/setup/seeders/ccdbObjects.js +++ b/QualityControl/test/setup/seeders/ccdbObjects.js @@ -47,3 +47,86 @@ export const subfolders = [ 'qc/test/object/2', 'qc/test/object/11', ]; + +export const MOCK_OBJECT_BY_ID_RESULT = { + id: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + path: 'qc/test/object/1', + name: 'qc/test/object/1', + validFrom: 1656072357492, + validUntil: 1971432357492, + createdAt: 1656072357533, + lastModified: 1656072357000, + drawOptions: [], + displayHints: [], + etag: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + runNumber: '0', + runType: '0', + partName: 'send', + qcCheckName: 'Pedestals/mPedestalChannelFECHG', + qcQuality: '3', + qcDetectorName: 'TPC', + qcVersion: '1.64.0', + objectType: 'o2::quality_control::core::QualityObject', + location: '/download/016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + layoutDisplayOptions: [], + layoutName: 'a-test', + tabName: 'main', + ignoreDefaults: false, +}; + +export const OBJECT_VERSIONS = [ + { + validFrom: 1656072357492, + createdAt: 1656072357533, + id: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + }, + { + validFrom: 1655916321231, + createdAt: 1655916321276, + id: 'b4944c1d-f24a-11ec-a509-c0a80209250c', + }, +]; + +export const OBJECT_VERSIONS_FILTERED_BY_RUN_NUMBER = [ + { + createdAt: 1656072357533, + id: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + validFrom: 1656072357492, + }, +]; + +export const OBJECT_BY_PATH_RESULT = { + id: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + path: 'qc/test/object/1', + name: 'qc/test/object/1', + validFrom: 1656072357492, + validUntil: 1971432357492, + createdAt: 1656072357533, + lastModified: 1656072357000, + drawOptions: [], + displayHints: [], + etag: '016fa8ac-f3b6-11ec-b9a9-c0a80209250c', + runNumber: '0', + runType: '0', + partName: 'send', + qcCheckName: 'Pedestals/mPedestalChannelFECHG', + qcQuality: '3', + qcDetectorName: 'TPC', + qcVersion: '1.64.0', + objectType: 'o2::quality_control::core::QualityObject', + location: '/download/016fa8ac-f3b6-11ec-b9a9-c0a80209250c', +}; + +export const TREE_API_OBJECTS = [ + { name: 'qc/test/object/1' }, + { name: 'qc/test/object/2' }, + { name: 'qc/test/object/11' }, +]; + +export const OBJECT_LATEST_FILTERED_BY_RUN_NUMBER = [ + { + [PATH]: 'qc/test/object/1', + createdAt: 1656072357533, + name: 'qc/test/object/1', + }, +]; diff --git a/QualityControl/test/setup/seeders/object-view/mock-object-view.js b/QualityControl/test/setup/seeders/object-view/mock-object-view.js index 9ed226c34..56b683499 100644 --- a/QualityControl/test/setup/seeders/object-view/mock-object-view.js +++ b/QualityControl/test/setup/seeders/object-view/mock-object-view.js @@ -1,6 +1,7 @@ -import { CCDB_RESPONSE_BODY_KEYS } from '../../../../lib/services/ccdb/CcdbConstants.js'; +import { CCDB_RESPONSE_BODY_KEYS, CCDB_FILTER_FIELDS } from '../../../../lib/services/ccdb/CcdbConstants.js'; const { ID, PATH, VALID_FROM, VALID_UNTIL, CREATED } = CCDB_RESPONSE_BODY_KEYS; +const { LAST_MODIFIED } = CCDB_FILTER_FIELDS; export const MOCK_OBJECT_IDENTIFICATION_RESPONSE = { path: 'qc/test/object/1', @@ -65,3 +66,23 @@ export const MOCK_OBJECT_VERSIONS_RESPONSE = { }, ], }; + +export const MOCK_OBJECT_VERSIONS_RESPONSE_RUN_NUMBER_FILTER = { + objects: [ + { + [VALID_FROM]: 1656072357492, + [CREATED]: 1656072357533, + [ID]: '"016fa8ac-f3b6-11ec-b9a9-c0a80209250c"', + }, + ], +}; + +export const MOCK_LATEST_OBJECT_FILTERED_BY_RUN_NUMBER = { + objects: [ + { + [PATH]: 'qc/test/object/1', + [CREATED]: 1656072357533, + [LAST_MODIFIED]: 1656072357492, + }, + ], +}; diff --git a/QualityControl/test/setup/testSetupForCcdb.js b/QualityControl/test/setup/testSetupForCcdb.js index 6c0fa31e3..b44cfcecb 100644 --- a/QualityControl/test/setup/testSetupForCcdb.js +++ b/QualityControl/test/setup/testSetupForCcdb.js @@ -17,7 +17,9 @@ import { readFileSync } from 'fs'; import { CCDB_FILTER_FIELDS, CCDB_MONITOR, CCDB_VERSION_KEY } from './../../lib/services/ccdb/CcdbConstants.js'; import { config } from './../config.js'; import { objects, subfolders } from './seeders/ccdbObjects.js'; -import { MOCK_OBJECT_DETAILS_RESPONSE, MOCK_OBJECT_IDENTIFICATION_RESPONSE, MOCK_OBJECT_VERSIONS_RESPONSE } +import { MOCK_LATEST_OBJECT_FILTERED_BY_RUN_NUMBER, + MOCK_OBJECT_DETAILS_RESPONSE, MOCK_OBJECT_IDENTIFICATION_RESPONSE, + MOCK_OBJECT_VERSIONS_RESPONSE, MOCK_OBJECT_VERSIONS_RESPONSE_RUN_NUMBER_FILTER } from './seeders/object-view/mock-object-view.js'; import { CCDB_MOCK_VERSION } from './seeders/ccdbVersion.js'; @@ -40,82 +42,68 @@ const versionResponse = {}; versionResponse[CCDB_MONITOR] = {}; versionResponse[CCDB_MONITOR][config.ccdb.hostname] = [CCDB_MOCK_VERSION]; const fileContent = readFileSync(CCDB_API_DOWNLOAD_ROOT_OBJECT.objectPath); +const acceptHeader = { reqheaders: { Accept: 'application/json' } }; +const xFieldHeader1 = { + reqheaders: { Accept: 'application/json', 'X-Filter-Fields': `${PATH},${CREATED},${LAST_MODIFIED}` }, +}; +const xFieldHeader2 = { + reqheaders: { Accept: 'application/json', 'X-Filter-Fields': `${PATH},${ID},${VALID_FROM},${VALID_UNTIL}` }, +}; +const xFieldHeader3 = { + reqheaders: { Accept: 'application/json', 'X-Filter-Fields': `${VALID_FROM},${ID},${CREATED}` }, +}; /** * Setup nock environment for ccdb which is to intercept all CCDB requests used in the Frontend test suites * Requests will have to persist as tests might run multiple times and we want to intercept all */ export const initializeNockForCcdb = () => { - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - }, - }).persist() + nock(CCDB_URL, acceptHeader).persist() .get(CCDB_API_MONITOR) .reply(200, versionResponse); - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - 'X-Filter-Fields': `${PATH},${CREATED},${LAST_MODIFIED}`, - }, - }).persist() + nock(CCDB_URL, xFieldHeader1).persist() .get(`${CCDB_API_PATH_LATEST}.*`) - .reply(200, { - objects, - }); + .reply(200, { objects }) + .get(`${CCDB_API_PATH_LATEST}.*/RunNumber=0`) + .reply(200, MOCK_LATEST_OBJECT_FILTERED_BY_RUN_NUMBER); - nock(CCDB_URL, { - reqheaders: { Accept: 'application/json' }, - }).persist() + nock(CCDB_URL, acceptHeader).persist() .get(`${CCDB_API_PATH_TREE}.*`) - .reply(200, { - subfolders, - }); - - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - 'X-Filter-Fields': `${PATH},${ID},${VALID_FROM},${VALID_UNTIL}`, - }, - }).persist() + .reply(200, { subfolders }) + + .head('/qc/test/object/1/1656072357492/1971432357492/016fa8ac-f3b6-11ec-b9a9-c0a80209250c') + .reply(200, null, MOCK_OBJECT_DETAILS_RESPONSE.headers) + + .head(CCDB_API_PATH_OBJECT_DETAILS) + .reply(200, null, MOCK_OBJECT_DETAILS_RESPONSE.headers) + + .head('/qc/test/object/1/1656072357492/1971432357492/016fa8ac-f3b6-11ec-b9a9-c0a80209250c/RunNumber=0') + .reply(200, null, MOCK_OBJECT_DETAILS_RESPONSE.headers); + + nock(CCDB_URL, xFieldHeader2).persist() .get(CCDB_API_PATH_OBJECT_IDENTIFICATION) .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE) + .get(`${CCDB_API_PATH_LATEST}/object/1`) - .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE); + .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE) + + .get(`${CCDB_API_PATH_LATEST}/object/1/RunNumber=0`) + .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE) - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - 'X-Filter-Fields': `${PATH},${ID},${VALID_FROM},${VALID_UNTIL}`, - }, - }).persist() .get(CCDB_API_PATH_TREE_OBJECT_IDENTIFICATION) .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE) + .get(`${CCDB_API_PATH_TREE}/object/1`) .reply(200, MOCK_OBJECT_IDENTIFICATION_RESPONSE); - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - }, - }).persist() - .head(CCDB_API_PATH_OBJECT_DETAILS) - .reply(200, null, MOCK_OBJECT_DETAILS_RESPONSE.headers) - .head('/qc/test/object/1/1656072357492/1971432357492/016fa8ac-f3b6-11ec-b9a9-c0a80209250c') - .reply(200, null, MOCK_OBJECT_DETAILS_RESPONSE.headers); - - nock(CCDB_URL, { - reqheaders: { - Accept: 'application/json', - 'X-Filter-Fields': `${VALID_FROM},${ID},${CREATED}`, - }, - }) + nock(CCDB_URL, xFieldHeader3) .persist() - // .get('/browse/qc/EMC/MO/Pedestals/mPedestalChannelFECHG') - // .reply(200, MOCK_OBJECT_VERSIONS_RESPONSE) .get('/browse/qc/test/object/1') - .reply(200, MOCK_OBJECT_VERSIONS_RESPONSE); + .reply(200, MOCK_OBJECT_VERSIONS_RESPONSE) + + .get('/browse/qc/test/object/1/RunNumber=0') + .reply(200, MOCK_OBJECT_VERSIONS_RESPONSE_RUN_NUMBER_FILTER); nock(CCDB_URL) .persist() diff --git a/QualityControl/test/test-index.js b/QualityControl/test/test-index.js index 27e4d34b5..5d8600ddb 100644 --- a/QualityControl/test/test-index.js +++ b/QualityControl/test/test-index.js @@ -73,6 +73,12 @@ import { userControllerTestSuite } from './lib/controllers/UserController.test.j import { chartRepositoryTest } from './lib/repositories/ChartRepository.test.js'; import { filterServiceTestSuite } from './lib/services/FilterService.test.js'; import { apiGetLayoutsTests } from './api/layouts/api-get-layout.test.js'; +import { apiGetObjectsTests } from './api/objects/api-get-object.test.js'; +import { objectsGetValidationMiddlewareTest } from './lib/middlewares/objects/objectsGetValidation.middleware.test.js'; +import { objectGetContentsValidationMiddlewareTest } + from './lib/middlewares/objects/objectGetByContentsValidation.middleware.test.js'; +import { objectGetByIdValidationMiddlewareTest } + from './lib/middlewares/objects/objectGetByIdValidation.middleware.test.js'; const FRONT_END_PER_TEST_TIMEOUT = 5000; // each front-end test is allowed this timeout // remaining tests are based on the number of individual tests in each suite @@ -174,6 +180,7 @@ suite('All Tests - QCG', { timeout: FRONT_END_TIMEOUT + BACK_END_TIMEOUT }, asyn suite('Layout GET request test suite', async () => apiGetLayoutsTests()); suite('Layout PUT request test suite', async () => apiPutLayoutTests()); suite('Layout PATCH request test suite', async () => apiPatchLayoutTests()); + suite('Object GET request test suite', async () => apiGetObjectsTests()); }); suite('Back-end test suite', { timeout: BACK_END_TIMEOUT }, async () => { @@ -206,6 +213,10 @@ suite('All Tests - QCG', { timeout: FRONT_END_TIMEOUT + BACK_END_TIMEOUT }, asyn suite('LayoutOwnerMiddleware test suite', async () => layoutOwnerMiddlewareTest()); suite('StatusComponentMiddleware test suite', async () => statusComponentMiddlewareTest()); suite('BookkeepingServiceTest test suite', async () => await bookkeepingServiceTestSuite()); + suite('ObjectsGetValidationMiddleware test suite', async () => objectsGetValidationMiddlewareTest()); + suite('ObjectGetContentsValidationMiddleware test suite', async () => + objectGetContentsValidationMiddlewareTest()); + suite('ObjectGetByIdValidationMiddleware test suite', async () => objectGetByIdValidationMiddlewareTest()); }); suite('Controllers - Test Suite', async () => { diff --git a/Tokenization/backend/wrapper/proto/wrapper.proto b/Tokenization/backend/wrapper/proto/wrapper.proto new file mode 100644 index 000000000..9cb03f258 --- /dev/null +++ b/Tokenization/backend/wrapper/proto/wrapper.proto @@ -0,0 +1,65 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ +syntax = "proto3"; + +package webui.tokenization; + +// ====================================== +// SERVICES +// ====================================== + +// Central System service handling duplex communication with wrapper client +service CentralSystem { + rpc ClientStream(stream Payload) returns (stream Payload); +} + +// ====================================== +// MESSAGES +// ====================================== + +// Simulates an empty message because protobuffer doesn't support void +message EmptyMessage {} + +// Message with token and target address binded to it +message Token { + string token = 1; + string targetAddress = 2; +} + +// Stream message that can contain one of specific messages +message Payload { + // Message event type + MessageEvent event = 1; + // Data related to specific event type + oneof data { + EmptyMessage emptyMessage = 2; + Token newToken = 3; + Token revokeToken = 4; + } +} + +// ====================================== +// ENUMS +// ====================================== + +enum MessageEvent { + // Default value, represents an empty event + MESSAGE_EVENT_EMPTY = 0; + + // New token message type, contains a new token and target address + MESSAGE_EVENT_NEW_TOKEN = 1; + + // Revoke token message type, contains a token to be revoked + MESSAGE_EVENT_REVOKE_TOKEN = 2; +} From c2664e30776806ddb55212bd0e039a5ac14f0efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Wed, 18 Jun 2025 21:31:19 +0200 Subject: [PATCH 14/24] chore: add license sections --- .../config-navigator/ConfigNavigator.tsx | 16 +++++++++++++- .../config-navigator/ConfigNavigatorItem.tsx | 17 ++++++++++++-- .../app/components/layout/MainLayout.tsx | 16 +++++++++++++- .../app/components/layout/content/Content.tsx | 16 +++++++++++++- .../layout/content/ContentHeader.tsx | 16 +++++++++++++- .../components/layout/drawer/LeftDrawer.tsx | 16 +++++++++++++- .../layout/drawer/LeftDrawerFooter.tsx | 15 ++++++++++++- .../layout/drawer/LeftDrawerHeader.tsx | 15 ++++++++++++- .../components/user-section/UserSection.tsx | 22 +++++++++++++++---- Configuration/webapp/app/test/mocha-index.cjs | 14 ++++++++++++ .../app/test/public/page-root-mocha.cjs | 14 ++++++++++++ Configuration/webapp/app/test/test-config.cjs | 14 ++++++++++++ 12 files changed, 178 insertions(+), 13 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx index 098b116a7..e7890caf7 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -1,5 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { List } from '@mui/material'; -import React, { useEffect, useState } from 'react'; +import { useEffect, useState } from 'react'; import ConfigNavigatorItem from './ConfigNavigatorItem'; const ConfigNavigator = () => { diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx index 69ec4172a..30633a1da 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -1,6 +1,19 @@ -import React, { type FC } from 'react'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +import { type FC } from 'react'; import { - Icon, ListItem, ListItemButton, ListItemIcon, diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx index 5d8b65936..7e223725e 100644 --- a/Configuration/webapp/app/components/layout/MainLayout.tsx +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -1,5 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Box } from '@mui/material'; -import React, { type FC, type PropsWithChildren } from 'react'; +import { type FC, type PropsWithChildren } from 'react'; interface MainLayoutProps extends PropsWithChildren {} diff --git a/Configuration/webapp/app/components/layout/content/Content.tsx b/Configuration/webapp/app/components/layout/content/Content.tsx index 1cd135b90..c1813f5f1 100644 --- a/Configuration/webapp/app/components/layout/content/Content.tsx +++ b/Configuration/webapp/app/components/layout/content/Content.tsx @@ -1,5 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Box } from '@mui/material'; -import React, { type FC, type PropsWithChildren } from 'react'; +import { type FC, type PropsWithChildren } from 'react'; import ContentHeader from './ContentHeader'; interface ContentProps extends PropsWithChildren {} diff --git a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx index 18d08c3d4..1729df693 100644 --- a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx +++ b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx @@ -1,5 +1,19 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Toolbar, Typography } from '@mui/material'; -import React, { type FC } from 'react'; +import { type FC } from 'react'; import UserSection from '../../user-section/UserSection'; interface ContentHeaderProps { diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 7cfaa3e62..b8a674940 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -1,4 +1,18 @@ -import React, { type FC, type PropsWithChildren } from 'react'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + +import { type FC, type PropsWithChildren } from 'react'; import { Box, Drawer } from '@mui/material'; import LeftDrawerFooter from './LeftDrawerFooter'; import LeftDrawerHeader from './LeftDrawerHeader'; diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx index 87af1b0f4..433bd8473 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawerFooter.tsx @@ -1,5 +1,18 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Box, Typography } from '@mui/material'; -import React from 'react'; const LeftDrawerFooter = () => { return ( diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx index 4d2022408..12787166b 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawerHeader.tsx @@ -1,5 +1,18 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Toolbar, Typography } from '@mui/material'; -import React from 'react'; const LeftDrawerHeader = () => { return ( diff --git a/Configuration/webapp/app/components/user-section/UserSection.tsx b/Configuration/webapp/app/components/user-section/UserSection.tsx index 14a902f28..758347e69 100644 --- a/Configuration/webapp/app/components/user-section/UserSection.tsx +++ b/Configuration/webapp/app/components/user-section/UserSection.tsx @@ -1,14 +1,28 @@ -import React from 'react'; +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + import { Box, IconButton, Menu, MenuItem, Avatar } from '@mui/material'; +import { useState, type FC, type MouseEvent } from 'react'; interface UserSectionProps { userName: string; } -const UserSection: React.FC = ({ userName }) => { - const [anchorEl, setAnchorEl] = React.useState(null); +const UserSection: FC = ({ userName }) => { + const [anchorEl, setAnchorEl] = useState(null); - const handleClick = (event: React.MouseEvent) => { + const handleClick = (event: MouseEvent) => { setAnchorEl(event.currentTarget); }; diff --git a/Configuration/webapp/app/test/mocha-index.cjs b/Configuration/webapp/app/test/mocha-index.cjs index a652e4e16..bccd68f1d 100644 --- a/Configuration/webapp/app/test/mocha-index.cjs +++ b/Configuration/webapp/app/test/mocha-index.cjs @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + const puppeteer = require('puppeteer'); const config = require('./test-config.cjs'); diff --git a/Configuration/webapp/app/test/public/page-root-mocha.cjs b/Configuration/webapp/app/test/public/page-root-mocha.cjs index cb3bfee4e..361b163ab 100644 --- a/Configuration/webapp/app/test/public/page-root-mocha.cjs +++ b/Configuration/webapp/app/test/public/page-root-mocha.cjs @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + const assert = require('assert'); const test = require('../mocha-index.cjs'); diff --git a/Configuration/webapp/app/test/test-config.cjs b/Configuration/webapp/app/test/test-config.cjs index 9aaeb1737..9f4e30b5d 100644 --- a/Configuration/webapp/app/test/test-config.cjs +++ b/Configuration/webapp/app/test/test-config.cjs @@ -1,3 +1,17 @@ +/** + * @license + * Copyright 2019-2020 CERN and copyright holders of ALICE O2. + * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. + * All rights not expressly granted are reserved. + * + * This software is distributed under the terms of the GNU General Public + * License v3 (GPL Version 3), copied verbatim in the file "COPYING". + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. +*/ + module.exports = { http: { port: 8080, From 1c60f3484a258f5b3bcbf1dfd95bec836a85ce62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 25 Aug 2025 22:41:10 +0200 Subject: [PATCH 15/24] chore: add missing documentation and typefixes --- .../config-navigator/ConfigNavigator.tsx | 20 ++++---- .../config-navigator/ConfigNavigatorItem.tsx | 48 ++++++++----------- .../layout/content/ContentHeader.tsx | 6 +++ .../components/layout/drawer/LeftDrawer.tsx | 43 ++++++++--------- 4 files changed, 56 insertions(+), 61 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx index 9c247dfc4..b3caa334d 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -10,31 +10,31 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import { List } from '@mui/material'; import { useEffect, useState } from 'react'; import ConfigNavigatorItem from './ConfigNavigatorItem'; +/** + * ConfigNavigator component + * Represents the configuration navigator sidebar. + * @returns {React.ReactElement} ConfigNavigator + */ export const ConfigNavigator = () => { const [configKeys, setConfigKeys] = useState([]); const fetchConfigurationKeys = async () => { const res = await fetch('http://localhost:8080/api/api/configurations'); - const data = await res.json(); + const data = (await res.json()) as string[]; + const newConfigKeys = data?.map((key) => key.split('/').pop() ?? ''); - setConfigKeys(data?.map((key) => key.split('/').pop())); + setConfigKeys(newConfigKeys); }; useEffect(() => { void fetchConfigurationKeys(); }, []); - return ( - - {configKeys?.map((text) => ( - - ))} - - ); + return {configKeys?.map((text) => )}; }; diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx index 30633a1da..29ae01997 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -10,15 +10,10 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import { type FC } from 'react'; -import { - ListItem, - ListItemButton, - ListItemIcon, - ListItemText, -} from '@mui/material'; +import { ListItem, ListItemButton, ListItemIcon, ListItemText } from '@mui/material'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faFile } from '@fortawesome/free-solid-svg-icons'; @@ -28,27 +23,22 @@ interface ConfigNavigatorItemProps { onClick?: () => void; } -const ConfigNavigatorItem: FC = ({ - title, - onClick, -}) => { - return ( - - - - - - - - - ); -}; +/** + * ConfigNavigatorItem component + * Represents an item in the configuration navigator. + * @param {string} title - The title of the configuration item. + * @param {function} onClick - Callback function to handle item click. + * @returns {React.ReactElement} ConfigNavigatorItem + */ +const ConfigNavigatorItem: FC = ({ title, onClick }) => ( + + + + + + + + +); export default ConfigNavigatorItem; diff --git a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx index 4a2c16b19..9bf8e1ccb 100644 --- a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx +++ b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx @@ -20,6 +20,12 @@ interface ContentHeaderProps { currentPath: string; } +/** + * Content component + * Represents the header of the content area. + * @param {string} currentPath - Current configuration path. + * @returns {React.ReactElement} Content + */ export const ContentHeader: FC = ({ currentPath }) => ( = ({ children }) => { - return ( - = ({ children }) => ( + - - {children} - - - ); -}; + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + }, + }} + variant="permanent" + anchor="left" + className="left-drawer" + > + + {children} + + +); From 3c0843bb8458b1a2a6b2cd66bc53833b62f4e596 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 25 Aug 2025 22:45:36 +0200 Subject: [PATCH 16/24] chore: eslint fixes --- .../app/components/layout/content/Content.tsx | 2 +- .../components/user-section/UserSection.tsx | 2 +- Configuration/webapp/app/root.tsx | 2 +- Configuration/webapp/app/test/mocha-index.cjs | 65 ---------------- .../app/test/public/page-root-mocha.cjs | 77 ------------------- Configuration/webapp/app/test/test-config.cjs | 20 ----- .../webapp/test/public/page-root-mocha.cjs | 8 ++ 7 files changed, 11 insertions(+), 165 deletions(-) delete mode 100644 Configuration/webapp/app/test/mocha-index.cjs delete mode 100644 Configuration/webapp/app/test/public/page-root-mocha.cjs delete mode 100644 Configuration/webapp/app/test/test-config.cjs diff --git a/Configuration/webapp/app/components/layout/content/Content.tsx b/Configuration/webapp/app/components/layout/content/Content.tsx index cd834575a..e82c25791 100644 --- a/Configuration/webapp/app/components/layout/content/Content.tsx +++ b/Configuration/webapp/app/components/layout/content/Content.tsx @@ -32,4 +32,4 @@ export const Content: FC = ({ children }) => ( {children} -); \ No newline at end of file +); diff --git a/Configuration/webapp/app/components/user-section/UserSection.tsx b/Configuration/webapp/app/components/user-section/UserSection.tsx index fc29573e8..2a0e7c55f 100644 --- a/Configuration/webapp/app/components/user-section/UserSection.tsx +++ b/Configuration/webapp/app/components/user-section/UserSection.tsx @@ -10,7 +10,7 @@ * In applying this license CERN does not waive the privileges and immunities * granted to it by virtue of its status as an Intergovernmental Organization * or submit itself to any jurisdiction. -*/ + */ import { Box, IconButton, Menu, MenuItem, Avatar } from '@mui/material'; import { useState, type FC, type MouseEvent } from 'react'; diff --git a/Configuration/webapp/app/root.tsx b/Configuration/webapp/app/root.tsx index ce8e606b1..48324a8aa 100644 --- a/Configuration/webapp/app/root.tsx +++ b/Configuration/webapp/app/root.tsx @@ -84,7 +84,7 @@ export function HydrateFallback() { export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { let message = 'Oops!'; let details = 'An unexpected error occurred.'; - let stack: string | undefined; + let stack: string | undefined = undefined; if (isRouteErrorResponse(error)) { message = error.status === 404 ? '404' : 'Error'; diff --git a/Configuration/webapp/app/test/mocha-index.cjs b/Configuration/webapp/app/test/mocha-index.cjs deleted file mode 100644 index bccd68f1d..000000000 --- a/Configuration/webapp/app/test/mocha-index.cjs +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. -*/ - -const puppeteer = require('puppeteer'); -const config = require('./test-config.cjs'); - -let page; -describe('Configuration', function () { - let browser; - this.timeout(50000); - this.slow(1000); - const url = `http://${config.http.hostname}:${config.http.port}/`; - - before(async () => { - browser = await puppeteer.launch({ - args: ['--no-sandbox', '--disable-setuid-sandbox'], - headless: true, - }); - page = await browser.newPage(); - - // Listen to browser - page.on('error', (pageerror) => { - console.error(' ', pageerror); - this.ok = false; - }); - page.on('pageerror', (pageerror) => { - console.error(' ', pageerror); - this.ok = false; - }); - page.on('console', (msg) => { - for (let i = 0; i < msg.args().length; ++i) { - console.log(` ${msg.args()[i]}`); - } - }); - await page.setViewport({ width: 1200, height: 770 }); - exports.page = page; - const helpers = { url }; - exports.helpers = helpers; - }); - - require('./public/page-root-mocha.cjs'); - - beforeEach(() => (this.ok = true)); - - afterEach(() => { - if (!this.ok) { - throw new Error('something went wrong'); - } - }); - - after(async () => { - await browser.close(); - }); -}); diff --git a/Configuration/webapp/app/test/public/page-root-mocha.cjs b/Configuration/webapp/app/test/public/page-root-mocha.cjs deleted file mode 100644 index 361b163ab..000000000 --- a/Configuration/webapp/app/test/public/page-root-mocha.cjs +++ /dev/null @@ -1,77 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. -*/ - -const assert = require('assert'); -const test = require('../mocha-index.cjs'); - -describe('`pageRoot` test-suite', async () => { - let url; - let page; - - before(async () => { - url = test.helpers.url; - page = test.page; - }); - - it('should load root page', async () => { - await page.goto(url, { waitUntil: 'networkidle0' }); - - const location = await page.evaluate(() => window.location); - assert.strictEqual(location.search, ''); - }); - - it('should successfully display drawer', async () => { - const drawer = await page.$$('.left-drawer'); - assert.strictEqual(drawer.length, 1); - }); - - it('should successfully display drawer header', async () => { - const drawerHeader = await page.$$('.left-drawer__header'); - assert.strictEqual(drawerHeader.length, 1); - }); - - it('should successfully display drawer', async () => { - const drawerFooter = await page.$$('.left-drawer__footer'); - assert.strictEqual(drawerFooter.length, 1); - }); - - it('should successfully display content section', async () => { - const contentSection = await page.$$('.content-section'); - assert.strictEqual(contentSection.length, 1); - }); - - it('should successfully display content section header', async () => { - const contentSectionHeader = await page.$$('.content-section__header'); - assert.strictEqual(contentSectionHeader.length, 1); - }); - - it('should successfully display user section', async () => { - const userSection = await page.$$('.user-section'); - assert.strictEqual(userSection.length, 1); - }); - - it('should successfully display user section menu on clik', async () => { - await page.click('.user-section'); - const userSectionMenu = await page.$$('.user-section__menu'); - assert.strictEqual(userSectionMenu.length, 1); - }); - - it('should successfully display configurations list', async () => { - const res = await fetch('http://localhost:8080/api/api/configurations'); - const data = await res.json(); - - const configNavigatorItems = await page.$$('.config_navigator__item'); - assert.strictEqual(configNavigatorItems.length, data.length); - }); -}); diff --git a/Configuration/webapp/app/test/test-config.cjs b/Configuration/webapp/app/test/test-config.cjs deleted file mode 100644 index 9f4e30b5d..000000000 --- a/Configuration/webapp/app/test/test-config.cjs +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @license - * Copyright 2019-2020 CERN and copyright holders of ALICE O2. - * See http://alice-o2.web.cern.ch/copyright for details of the copyright holders. - * All rights not expressly granted are reserved. - * - * This software is distributed under the terms of the GNU General Public - * License v3 (GPL Version 3), copied verbatim in the file "COPYING". - * - * In applying this license CERN does not waive the privileges and immunities - * granted to it by virtue of its status as an Intergovernmental Organization - * or submit itself to any jurisdiction. -*/ - -module.exports = { - http: { - port: 8080, - hostname: 'localhost', - }, -}; diff --git a/Configuration/webapp/test/public/page-root-mocha.cjs b/Configuration/webapp/test/public/page-root-mocha.cjs index b495ec1f9..f39b3b6fd 100644 --- a/Configuration/webapp/test/public/page-root-mocha.cjs +++ b/Configuration/webapp/test/public/page-root-mocha.cjs @@ -65,4 +65,12 @@ describe('`pageRoot` test-suite', function () { const userSectionMenu = await page.$$('.user-section__menu'); assert.strictEqual(userSectionMenu.length, 1); }); + + it('should successfully display configurations list', async function () { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = await res.json(); + + const configNavigatorItems = await page.$$('.config_navigator__item'); + assert.strictEqual(configNavigatorItems.length, data.length); + }); }); From 4234c1ee71768782626febc5eca823717c120beb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 25 Aug 2025 22:58:31 +0200 Subject: [PATCH 17/24] chore: fix package-lock.json --- Configuration/package-lock.json | 6 - Configuration/webapp/package-lock.json | 629 +++++++++++++++++++++++++ 2 files changed, 629 insertions(+), 6 deletions(-) delete mode 100644 Configuration/package-lock.json diff --git a/Configuration/package-lock.json b/Configuration/package-lock.json deleted file mode 100644 index 17a2e204b..000000000 --- a/Configuration/package-lock.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "name": "Configuration", - "lockfileVersion": 3, - "requires": true, - "packages": {} -} diff --git a/Configuration/webapp/package-lock.json b/Configuration/webapp/package-lock.json index 5915c9e02..827502cbe 100644 --- a/Configuration/webapp/package-lock.json +++ b/Configuration/webapp/package-lock.json @@ -744,6 +744,74 @@ "node": ">=18" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/darwin-arm64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", @@ -761,6 +829,74 @@ "node": ">=18" } }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-arm64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", @@ -778,6 +914,261 @@ "node": ">=18" } }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -1688,6 +2079,34 @@ } } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, "node_modules/@rollup/rollup-darwin-arm64": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", @@ -1702,6 +2121,76 @@ "darwin" ] }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@rollup/rollup-linux-arm64-gnu": { "version": "4.44.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", @@ -1730,6 +2219,146 @@ "linux" ] }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@stylistic/eslint-plugin": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", From d08bc9eb5494c954d1f6c2d4a6e5dc588f77c7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 25 Aug 2025 23:06:08 +0200 Subject: [PATCH 18/24] chore: add config navigator test --- .../config-navigator/ConfigNavigator.tsx | 6 ++++- .../webapp/test/public/page-root-mocha.cjs | 25 ++++++++++++++----- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx index b3caa334d..acff03147 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -36,5 +36,9 @@ export const ConfigNavigator = () => { void fetchConfigurationKeys(); }, []); - return {configKeys?.map((text) => )}; + return ( + + {configKeys?.map((text) => )} + + ); }; diff --git a/Configuration/webapp/test/public/page-root-mocha.cjs b/Configuration/webapp/test/public/page-root-mocha.cjs index f39b3b6fd..12c861eab 100644 --- a/Configuration/webapp/test/public/page-root-mocha.cjs +++ b/Configuration/webapp/test/public/page-root-mocha.cjs @@ -67,10 +67,23 @@ describe('`pageRoot` test-suite', function () { }); it('should successfully display configurations list', async function () { - const res = await fetch('http://localhost:8080/api/api/configurations'); - const data = await res.json(); - - const configNavigatorItems = await page.$$('.config_navigator__item'); - assert.strictEqual(configNavigatorItems.length, data.length); - }); + const configNavigator = await page.$$('.config_navigator'); + assert.strictEqual(configNavigator.length, 1); + }); + + it('should successfully display configurations list items', async function () { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = await res.json(); + + const configNavigatorItems = await page.$$('.config_navigator__item'); + assert.strictEqual(configNavigatorItems.length, data.length); + }); + + it('should display configurations list', async function () { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = await res.json(); + + const configNavigatorItems = await page.$$('.config_navigator__item'); + assert.strictEqual(configNavigatorItems.length, data.length); + }); }); From 0dfdc6c48d0900da27a67fe9266443570513a96c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Thu, 28 Aug 2025 16:00:40 +0200 Subject: [PATCH 19/24] fix: remove old control changes --- Control/lib/api.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Control/lib/api.js b/Control/lib/api.js index ef2908f17..93a37113c 100644 --- a/Control/lib/api.js +++ b/Control/lib/api.js @@ -102,10 +102,6 @@ module.exports.setup = (http, ws) => { const broadcastService = new BroadcastService(ws); const cacheService = new CacheService(broadcastService); const environmentCacheService = new EnvironmentCacheService(broadcastService, eventEmitter); - const qcConfigurationService = new QCConfigurationService(consulService); - qcConfigurationService.testConsulStatus(); - - const qcConfigurationController = new QCConfigurationController(qcConfigurationService, config.consul); const qcConfigurationService = new QCConfigurationService(consulService); const qcConfigurationController = new QCConfigurationController(qcConfigurationService, config.consul); From c3be3f097d1c6b46cce1c7d0e9e7a0bf7030255b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 6 Oct 2025 00:00:02 +0200 Subject: [PATCH 20/24] chore: add config keys fetching error handling --- .../config-navigator/ConfigNavigator.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx index acff03147..3f474ae25 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigator.tsx @@ -23,13 +23,21 @@ import ConfigNavigatorItem from './ConfigNavigatorItem'; */ export const ConfigNavigator = () => { const [configKeys, setConfigKeys] = useState([]); + const [isError, setIsError] = useState(false); const fetchConfigurationKeys = async () => { - const res = await fetch('http://localhost:8080/api/api/configurations'); - const data = (await res.json()) as string[]; - const newConfigKeys = data?.map((key) => key.split('/').pop() ?? ''); - - setConfigKeys(newConfigKeys); + try { + const res = await fetch('http://localhost:8080/api/api/configurations'); + const data = (await res.json()) as string[]; + const newConfigKeys = data?.map((key) => key.split('/').pop() ?? ''); + setConfigKeys(newConfigKeys); + } catch (_error) { + // temporarily disable the linting rule for this line + // fetching will be refactored once Tanstack Query is introduced + // eslint-disable-next-line no-console + console.error('Error while fetching configuration keys', _error); + setIsError(true); + } }; useEffect(() => { @@ -38,7 +46,11 @@ export const ConfigNavigator = () => { return ( - {configKeys?.map((text) => )} + {isError ? ( +

Error while fetching configuration keys

+ ) : ( + configKeys?.map((text) => ) + )}
); }; From 0d1bd2807aa455bd97e312c4751e4bbd5cb864fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 6 Oct 2025 00:00:18 +0200 Subject: [PATCH 21/24] chore: adjust docs --- .../app/components/config-navigator/ConfigNavigatorItem.tsx | 5 +++-- Configuration/webapp/app/components/layout/MainLayout.tsx | 1 + .../webapp/app/components/layout/content/ContentHeader.tsx | 3 ++- .../webapp/app/components/layout/drawer/LeftDrawer.tsx | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx index 29ae01997..3a51ce89c 100644 --- a/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx +++ b/Configuration/webapp/app/components/config-navigator/ConfigNavigatorItem.tsx @@ -26,8 +26,9 @@ interface ConfigNavigatorItemProps { /** * ConfigNavigatorItem component * Represents an item in the configuration navigator. - * @param {string} title - The title of the configuration item. - * @param {function} onClick - Callback function to handle item click. + * @param {ConfigNavigatorItemProps} props - The props of the component. + * @param {string} props.title - The title of the configuration item. + * @param {Function} props.onClick - Callback function to handle item click. * @returns {React.ReactElement} ConfigNavigatorItem */ const ConfigNavigatorItem: FC = ({ title, onClick }) => ( diff --git a/Configuration/webapp/app/components/layout/MainLayout.tsx b/Configuration/webapp/app/components/layout/MainLayout.tsx index 76a2d3274..25bb05395 100644 --- a/Configuration/webapp/app/components/layout/MainLayout.tsx +++ b/Configuration/webapp/app/components/layout/MainLayout.tsx @@ -19,6 +19,7 @@ import { type FC, type PropsWithChildren } from 'react'; * MainLayout component * Represents the main layout of the application, including the left drawer and content area. * @param {PropsWithChildren} props - Component props. + * @param {ReactElement} props.children - The children elements to render inside main layout. * @returns {React.ReactElement} MainLayout */ export const MainLayout: FC = ({ children }) => ( diff --git a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx index 9bf8e1ccb..8149c036c 100644 --- a/Configuration/webapp/app/components/layout/content/ContentHeader.tsx +++ b/Configuration/webapp/app/components/layout/content/ContentHeader.tsx @@ -23,7 +23,8 @@ interface ContentHeaderProps { /** * Content component * Represents the header of the content area. - * @param {string} currentPath - Current configuration path. + * @param {ContentHeaderProps} props - The props of the component. + * @param {string} props.currentPath - Current configuration path. * @returns {React.ReactElement} Content */ export const ContentHeader: FC = ({ currentPath }) => ( diff --git a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx index 31a68bc5f..74dace6b4 100644 --- a/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx +++ b/Configuration/webapp/app/components/layout/drawer/LeftDrawer.tsx @@ -22,7 +22,8 @@ const DRAWER_WIDTH = 300; /** * LeftDrawer component * Represents the left sidebar of the application layout. - * @param {PropsWithChildren} children - The children elements to render inside the drawer. + * @param {PropsWithChildren} props - The props of the component. + * @param {ReactElement} props.children - The children elements to render inside the drawer. * @returns {ReactElement} LeftDrawer */ export const LeftDrawer: FC = ({ children }) => ( From 8ab1dc6937076639708346d9fa0b4a522b1dfce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Mon, 6 Oct 2025 00:04:27 +0200 Subject: [PATCH 22/24] chore: adjust tests --- Configuration/webapp/test/public/page-root-mocha.cjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/webapp/test/public/page-root-mocha.cjs b/Configuration/webapp/test/public/page-root-mocha.cjs index 12c861eab..91453e7e6 100644 --- a/Configuration/webapp/test/public/page-root-mocha.cjs +++ b/Configuration/webapp/test/public/page-root-mocha.cjs @@ -76,7 +76,7 @@ describe('`pageRoot` test-suite', function () { const data = await res.json(); const configNavigatorItems = await page.$$('.config_navigator__item'); - assert.strictEqual(configNavigatorItems.length, data.length); + assert.strictEqual(configNavigatorItems.length, data?.length ?? 0); }); it('should display configurations list', async function () { @@ -84,6 +84,6 @@ describe('`pageRoot` test-suite', function () { const data = await res.json(); const configNavigatorItems = await page.$$('.config_navigator__item'); - assert.strictEqual(configNavigatorItems.length, data.length); + assert.strictEqual(configNavigatorItems.length, data?.length ?? 0); }); }); From 66977be625c695da5cf4ed5488710ea862a16cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Fri, 31 Oct 2025 15:53:47 +0100 Subject: [PATCH 23/24] chore: fix tests --- Configuration/webapp/test/public/page-root-mocha.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/webapp/test/public/page-root-mocha.spec.ts b/Configuration/webapp/test/public/page-root-mocha.spec.ts index cec16951f..8727e23d4 100644 --- a/Configuration/webapp/test/public/page-root-mocha.spec.ts +++ b/Configuration/webapp/test/public/page-root-mocha.spec.ts @@ -118,7 +118,7 @@ describe('`pageRoot` test-suite', function () { }); it('should successfully display configurations list items', async function () { - const res = await fetch('http://localhost:8080/api/api/configurations'); + const res = await fetch('http://localhost:8080/control/api/configurations'); const data = await res.json(); const configNavigatorItems = await page.$$('.config_navigator__item'); @@ -126,7 +126,7 @@ describe('`pageRoot` test-suite', function () { }); it('should display configurations list', async function () { - const res = await fetch('http://localhost:8080/api/api/configurations'); + const res = await fetch('http://localhost:8080/control/api/configurations'); const data = await res.json(); const configNavigatorItems = await page.$$('.config_navigator__item'); From 3fe9490f322423444e383bc7f581b43172847246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20=C5=9Aliwi=C5=84ski?= Date: Fri, 31 Oct 2025 16:04:57 +0100 Subject: [PATCH 24/24] chore: update tests --- .../webapp/test/public/page-root-mocha.spec.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Configuration/webapp/test/public/page-root-mocha.spec.ts b/Configuration/webapp/test/public/page-root-mocha.spec.ts index 8727e23d4..11fbae83e 100644 --- a/Configuration/webapp/test/public/page-root-mocha.spec.ts +++ b/Configuration/webapp/test/public/page-root-mocha.spec.ts @@ -113,11 +113,21 @@ describe('`pageRoot` test-suite', function () { }); it('should successfully display configurations list', async function () { + if (page === null) { + assert.equal('Page is null', 'test suite failed'); + return; + } + const configNavigator = await page.$$('.config_navigator'); assert.strictEqual(configNavigator.length, 1); }); it('should successfully display configurations list items', async function () { + if (page === null || url === null) { + assert.equal('Page is null', 'test suite failed'); + return; + } + const res = await fetch('http://localhost:8080/control/api/configurations'); const data = await res.json(); @@ -126,6 +136,11 @@ describe('`pageRoot` test-suite', function () { }); it('should display configurations list', async function () { + if (page === null || url === null) { + assert.equal('Page is null', 'test suite failed'); + return; + } + const res = await fetch('http://localhost:8080/control/api/configurations'); const data = await res.json();