diff --git a/Procfile b/Procfile index cc0b07a963..5418acf42f 100644 --- a/Procfile +++ b/Procfile @@ -2,6 +2,4 @@ # If you wish to use a different server then the default, use e.g. `export RAILS_STARTUP='puma -w 3 -p 3000 --preload'` rails: [ -n "$RAILS_STARTUP" ] && env PRY_WARNING=1 $RAILS_STARTUP || [ -n "$BIND" ] && bin/rails server -b $BIND || env PRY_WARNING=1 bin/rails server -# you can use WEBPACK_OPTS to customize webpack server, e.g. 'WEBPACK_OPTS=--progress' foreman start ' -# filter out webpack options that are commonly used but not supported by webpack 5 and not needed in the new configutation as webpack is not run as a server anymore -webpack: FILTERED_WEBPACK_OPTS=$(echo $WEBPACK_OPTS | sed -e 's/--key [^ ]*//g' -e 's/--public [^ ]*//g' -e 's/--https [^ ]*//g' -e 's/--cert [^ ]*//g' -e 's/--cacert [^ ]*//g' -e 's/--host [^ ]*//g') && [ -n "$NODE_ENV" ] && npx webpack --config config/webpack.config.js --watch $FILTERED_WEBPACK_OPTS || env NODE_ENV=development npx webpack --config config/webpack.config.js --watch $FILTERED_WEBPACK_OPTS \ No newline at end of file +webpack: env NODE_OPTIONS=${NODE_OPTIONS:---max-old-space-size=8096} NODE_ENV=${NODE_ENV:-development} npx webpack --config config/webpack.config.js --watch $WEBPACK_OPTS \ No newline at end of file diff --git a/app/helpers/reactjs_helper.rb b/app/helpers/reactjs_helper.rb index 7c8425f5b2..6afdc4f5ec 100644 --- a/app/helpers/reactjs_helper.rb +++ b/app/helpers/reactjs_helper.rb @@ -36,11 +36,6 @@ def get_webpack_chunk(name, extension) data['assetsByChunkName'][name]&.find { |value| value.end_with?(".#{extension}") } end - def get_webpack_foreman_vendor_js - foreman_vendor_js = get_webpack_chunk('foreman-vendor', 'js') - javascript_include_tag("/webpack/#{foreman_vendor_js}") - end - def get_webpack_foreman_vendor_css foreman_vendor_css = get_webpack_chunk('vendorStyles', 'css') stylesheet_link_tag("/webpack/#{foreman_vendor_css}") diff --git a/app/views/layouts/base.html.erb b/app/views/layouts/base.html.erb index 1b94dd0d26..927f57bd08 100644 --- a/app/views/layouts/base.html.erb +++ b/app/views/layouts/base.html.erb @@ -36,7 +36,7 @@ - <%= get_webpack_foreman_vendor_js %> + <%= javascript_include_tag("/webpack/#{get_webpack_chunk('vendor', 'js')}") %> <%= javascript_include_tag("/webpack/#{get_webpack_chunk('bundle', 'js')}") %> <%= javascript_include_tag("/webpack/#{get_webpack_chunk('reactExports', 'js')}") %> diff --git a/config/webpack.config.js b/config/webpack.config.js index cd249d3491..5c16d1799a 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -4,17 +4,33 @@ var path = require('path'); var webpack = require('webpack'); const dotenv = require('dotenv'); +const root = path.resolve(__dirname, '..'); dotenv.config(); -var ForemanVendorPlugin = require('@theforeman/vendor') - .WebpackForemanVendorPlugin; var StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; var vendorEntry = require('./webpack.vendor'); var fs = require('fs'); const { ModuleFederationPlugin } = require('webpack').container; var pluginUtils = require('../script/plugin_webpack_directories'); -var { generateExportsFile }= require('../webpack/assets/javascripts/exportAll'); +var { + generateExportsFile, +} = require('../webpack/assets/javascripts/exportAll'); var CompressionPlugin = require('compression-webpack-plugin'); -const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const packageJsonPath = path.resolve(root, 'package.json'); +const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); +const dependencies = packageJson.dependencies || {}; +const devDependencies = packageJson.devDependencies || {}; +const allDependencies = { ...dependencies, ...devDependencies }; +const shared = isPlugin => + Object.keys(allDependencies).map(dep => ({ + [dep]: { + eager: !isPlugin, // core should load all dependencies eagerly so they will be available for plugins + singleton: true, + requiredVersion: allDependencies[dep], + import: isPlugin ? false : dep, + }, + })); class AddRuntimeRequirement { // to avoid "webpackRequire.l is not a function" error @@ -33,7 +49,7 @@ class AddRuntimeRequirement { } const supportedLocales = () => { - const localeDir = path.join(__dirname, '..', 'locale'); + const localeDir = path.join(root, 'locale'); // Find all files in ./locale/* const localesFiles = fs.readdirSync(localeDir); @@ -80,14 +96,31 @@ const commonConfig = function() { os: require.resolve('os-browserify'), }, alias: { + 'patternfly-react$': path.resolve( + __dirname, + '..', + 'node_modules/patternfly-react/dist/js/index.js' + ), // to avoid circular dependency in dist/esm + '/node_modules/jquery': path.resolve( + __dirname, + '..', + 'webpack/assets/javascripts/jquery.js' + ), + jquery: path.resolve(root, 'webpack/assets/javascripts/jquery.js'), foremanReact: path.join( __dirname, '../webpack/assets/javascripts/react_app' ), + 'react/jsx-runtime': 'react/jsx-runtime.js', // for react-dnd + 'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js', // for react-dnd + 'datatables.net': path.resolve( + root, + 'node_modules/datatables.net/js/jquery.dataTables.js' + ), // otherwise we get datatables.net-bs Cannot read properties of undefined (reading 'classes') since dataTables.mjs is a module. }, }, resolveLoader: { - modules: [path.resolve(__dirname, '..', 'node_modules')], + modules: [path.resolve(root, 'node_modules')], }, module: { rules: [ @@ -118,9 +151,6 @@ const commonConfig = function() { ], }, plugins: [ - new ForemanVendorPlugin({ - mode, - }), new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(mode), @@ -148,30 +178,32 @@ const commonConfig = function() { const coreConfig = function() { var config = commonConfig(); var manifestFilename = 'manifest.json'; - var bundleEntry = path.join( - __dirname, - '..', - 'webpack/assets/javascripts/bundle.js' - ); - config.context = path.resolve(__dirname, '..'); + var bundleEntry = path.join(root, 'webpack/assets/javascripts/bundle.js'); + config.context = path.resolve(root); if (config.mode == 'production') { - var chunkFilename = '[name]-[chunkhash].js' + var chunkFilename = '[name]-[chunkhash].js'; } else { - var chunkFilename = '[name].js' + var chunkFilename = '[name].js'; } config.entry = { + /* keep bundle entry files and reactExports seperate to avoid late loading issues of mixed files, import in react_app only from react_app and node_modules */ bundle: { import: bundleEntry, dependOn: ['vendor', 'reactExports'] }, + reactExports: { + import: path.join( + root, + 'webpack/assets/javascripts/all_react_app_exports.js' + ), + dependOn: 'vendor', + }, vendor: vendorEntry, - reactExports: path.join( - __dirname, - '..', - 'webpack/assets/javascripts/all_react_app_exports.js' - ), - vendorStyles: path.join(__dirname, '..', 'webpack/assets/javascripts/react_app/common/scss/vendor-core.scss'), + vendorStyles: path.join( + root, + 'webpack/assets/javascripts/react_app/common/scss/vendor-core.scss' + ), }; config.output = { - path: path.join(__dirname, '..', 'public', 'webpack'), + path: path.join(root, 'public', 'webpack'), publicPath: '/webpack/', library: { name: ['TheForeman', '[name]'], @@ -181,9 +213,11 @@ const coreConfig = function() { }; var plugins = config.plugins; + plugins.push(new MiniCssExtractPlugin()); plugins.push( new ModuleFederationPlugin({ name: 'foremanReact', + shared: shared(false), }) ); plugins.push( @@ -191,11 +225,10 @@ const coreConfig = function() { filename: manifestFilename, }) ); - plugins.push( - new MiniCssExtractPlugin() - ); + plugins.push(new MiniCssExtractPlugin()); config.plugins = plugins; var rules = config.module.rules; + rules.push({ test: /\.(sa|sc|c)ss$/, exclude: /vendor-core/i, @@ -213,11 +246,7 @@ const coreConfig = function() { }); rules.push({ test: /vendor-core/i, - use: [ - MiniCssExtractPlugin.loader, - 'css-loader', - 'sass-loader', - ], + use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'], }); config.module.rules = rules; return config; @@ -239,7 +268,9 @@ const pluginConfig = function(plugin) { config.externals = function({ request }, callback) { if (/^foremanReact(\/.*)?$/.test(request)) { const prefix = 'var TheForeman.reactExports.'; - const newPath = prefix + convertImportStatement(request.substring('foremanReact'.length)); + const newPath = + prefix + + convertImportStatement(request.substring('foremanReact'.length)); return callback(null, newPath); } return callback(); @@ -257,7 +288,7 @@ const pluginConfig = function(plugin) { if (config.mode == 'production') { var outputPath = path.join(pluginRoot, 'public', 'webpack', pluginName); - var chunkFilename = '[name]-[chunkhash].js' + var chunkFilename = '[name]-[chunkhash].js'; } else { var outputPath = path.join( __dirname, @@ -266,7 +297,7 @@ const pluginConfig = function(plugin) { 'webpack', pluginName ); - var chunkFilename = '[name].js' + var chunkFilename = '[name].js'; } config.output = { path: outputPath, @@ -276,7 +307,7 @@ const pluginConfig = function(plugin) { }; var configModules = config.resolve.modules || []; // make webpack to resolve modules from core first - configModules.unshift(path.resolve(__dirname, '..', 'node_modules')); + configModules.unshift(path.resolve(root, 'node_modules')); // add plugin's node_modules to the reslver list configModules.push(path.resolve(pluginRoot, 'node_modules')); configModules.push('node_modules/'); @@ -290,8 +321,10 @@ const pluginConfig = function(plugin) { name: pluginName, filename: pluginName + '_remoteEntry.js', exposes: pluginEntries, + shared: shared(true), }) ); + config.plugins = plugins; var rules = config.module.rules; rules.push({ diff --git a/config/webpack.vendor.js b/config/webpack.vendor.js index d6f31b3222..19b5aa439b 100644 --- a/config/webpack.vendor.js +++ b/config/webpack.vendor.js @@ -1 +1,104 @@ -module.exports = ['react-intl', 'intl']; +/* eslint-disable */ + +module.exports = [ + 'unleash-proxy-client', + '@unleash/proxy-client-react', + '@scalprum/core', + '@scalprum/react-core', + 'intl', + /** + * React related + */ + '@apollo/client', + '@apollo/client/link/batch-http', + '@reduxjs/toolkit', + 'core-js/shim', + 'regenerator-runtime/runtime', + 'formik', + 'rc-input-number', + 'react', + 'react-ace', + 'react-dom', + 'react-dnd', + 'react-dnd-html5-backend', + 'react-debounce-input', + 'react-diff-view', + 'react-ellipsis-with-tooltip', + 'react-onclickoutside', + 'react-password-strength', + 'react-router-dom', + 'react-router-bootstrap', + 'react-loading-skeleton', + 'react-redux', + 'redux', + 'redux-logger', + 'redux-thunk', + 'reselect', + 'prop-types', + 'classnames', + 'seamless-immutable', + 'connected-react-router', + 'react-helmet', + 'react-intl', + + /** + * Patternfly related + */ + 'patternfly-react', + 'patternfly-react-extensions', + '@patternfly/react-core', + '@patternfly/react-icons', + '@patternfly/react-table', + '@patternfly/react-tokens', + '@patternfly/react-styles', + '@patternfly/react-charts', + + /** + * ace-builds related + */ + 'ace-builds', + 'ace-builds/src-noconflict/ace', + 'ace-builds/src-noconflict/ext-language_tools', + 'ace-builds/src-noconflict/mode-ruby', + 'ace-builds/src-noconflict/mode-json', + 'ace-builds/src-noconflict/mode-sh', + 'ace-builds/src-noconflict/mode-html_ruby', + 'ace-builds/src-noconflict/mode-xml', + 'ace-builds/src-noconflict/mode-yaml', + 'ace-builds/src-noconflict/theme-github', + 'ace-builds/src-noconflict/theme-monokai', + 'ace-builds/src-noconflict/keybinding-vim', + 'ace-builds/src-noconflict/keybinding-emacs', + 'ace-builds/src-min-noconflict/ext-searchbox', + + /** + * UUID + */ + 'uuid', + 'uuid/v1', + 'uuid/v3', + 'uuid/v4', + 'uuid/v5', + + 'jstz', + 'diff', + + /** + * Other packages + */ + 'history', + 'number_helpers', + 'lodash', + 'axios', + 'file-saver', + 'humanize-duration', + 'unidiff', + 'urijs', + 'yup', + 'select2', + 'multiselect', + '@novnc/novnc/core/rfb', + + // '@webcomponents/webcomponentsjs/webcomponents-bundle', + // '@webcomponents/webcomponentsjs/custom-elements-es5-adapter', +]; diff --git a/developer_docs/foreman_dev_setup.asciidoc b/developer_docs/foreman_dev_setup.asciidoc index f91cb94f04..e85be57626 100644 --- a/developer_docs/foreman_dev_setup.asciidoc +++ b/developer_docs/foreman_dev_setup.asciidoc @@ -150,7 +150,7 @@ list of options that can be set in an .env file (as well as in the cli call): * `NOTIFICATIONS_POLLING` is the notification polling interval in ms. -* `NODE_OPTIONS` is used for npm actions, when running plugin tests with `npm run test:plugins` we add `--max-old-space-size=8192` to the options, but it can be overridden by writing your own `NODE_OPTIONS` +* `NODE_OPTIONS` is used for npm actions, when running plugin tests with `npm run test:plugins` we add `--max-old-space-size=8192` to the options, but it can be overridden by writing your own `NODE_OPTIONS`. It is also added for webpack compile (`foreman start webpack`). === Resetting password diff --git a/package.json b/package.json index 46fbc59005..4d362b8429 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "description": "Foreman isn't really a node module, these are just dependencies needed to build the webpack bundle. 'dependencies' are the asset libraries in use and 'devDependencies' are used for the build process.", "private": true, "engines": { - "node": ">=18.0.0 <21.0.0" + "node": ">=18.0.0 <21.0.0", + "npm": ">=8.0.0" }, "scripts": { "lint": "eslint ./webpack ./script", @@ -22,24 +23,85 @@ "analyze": "./script/webpack-analyze" }, "dependencies": { + "@apollo/client": "^3.3.7", "@module-federation/utilities": "^1.7.0", + "@novnc/novnc": "1.3", "@patternfly/patternfly": "^5.4.2", + "@patternfly/react-charts": "~6.94.15", + "@patternfly/react-core": "^5.2.0", + "@patternfly/react-icons": "^5.2.0", + "@patternfly/react-styles": "^5.2.0", + "@patternfly/react-table": "^5.2.0", + "@patternfly/react-tokens": "^5.2.0", + "@reduxjs/toolkit": "^1.6.0", + "@scalprum/core": "^0.8.1", + "@scalprum/react-core": "^0.9.3", + "@spice-project/spice-html5": "^0.2.1", "@theforeman/vendor": "^15.0.0", + "@unleash/proxy-client-react": "^5.0.0", + "@webcomponents/webcomponentsjs": "^2.2.10", + "ace-builds": "^1.4.13", + "axios": "^0.21.1", "bootstrap-sass": "^3.4.3", + "classnames": "^2.2.5", + "connected-react-router": "6.6.1", + "core-js": "^2.5.7", "datatables.net-bs": "1.13.5", + "datatables.net-dt": "1.13.5", + "datatables.net": "1.13.5", + "diff": "^4.0.1", "dsmorse-gridster": "^0.8.0", + "file-saver": "^2.0.1", + "formik": "^1.5.8", "graphql-tag": "^2.11.0", + "graphql": "^15.5.0", + "history": "^4.7.2", + "humanize-duration": "3.27.0", "intl": "~1.2.5", + "ipaddr.js": "~1.2.0", "jed": "^1.1.1", + "jquery-ujs": "~1.2.0", + "jquery": "^3.7.1", "js-cookie": "^3.0.5", + "jstz": "~1.0.7", + "lodash": "^4.17.14", "multiselect": "~0.9.12", + "number_helpers": "^0.1.1", "os-browserify": "^0.3.0", "patternfly-react-extensions": "^3.0.15", "patternfly-react": "^2.40.0", "patternfly": "^3.59.5", + "prop-types": "^15.6.0", + "rc-input-number": "^6.0.0", + "react-ace": "^9.5.0", + "react-debounce-input": "^3.2.0", "react-diff-view": "^2.6.0", + "react-dnd-html5-backend": "^14.0.0", + "react-dnd": "^14.0.2", + "react-dom": "^16.8.1", + "react-ellipsis-with-tooltip": "^1.0.8", + "react-helmet": "^6.1.0", "react-intl": "^2.8.0", - "select2": "4.0.12" + "react-loading-skeleton": "^1.1.2", + "react-onclickoutside": "^6.6.2", + "react-password-strength": "^2.4.0", + "react-redux": "^7.1.0", + "react-router-bootstrap": "^0.25.0", + "react-router-dom": "^6.18.0", + "react": "^16.9.0", + "redux-logger": "^2.8.1", + "redux-thunk": "^2.2.0", + "redux": "^4.0.4", + "regenerator-runtime": "^0.13.3", + "reselect": "^3.0.1", + "sanitize-html": "2.3.2", + "seamless-immutable": "^7.1.2", + "select2": "4.0.12", + "unidiff": "^1.0.0", + "unleash-proxy-client": "^3.7.6", + "urijs": "^1.19.4", + "uuid": "^3.3.2", + "yup": "^0.29.3" }, "devDependencies": { "@apollo/react-testing": "^4.0.0", @@ -49,7 +111,6 @@ "@testing-library/react": "^10.0.2", "@testing-library/react-hooks": "^3.4.2", "@theforeman/builder": "^15.0.0", - "@theforeman/vendor-core": "^15.0.0", "@types/jest": "<27.0.0", "argv-parse": "^1.0.1", "axios-mock-adapter": "^1.1.7", @@ -67,11 +128,11 @@ "enzyme-to-json": "^3.4.3", "eslint": "^6.7.2", "eslint-import-resolver-alias": "^1.1.2", - "eslint-plugin-spellcheck": "0.0.17", - "eslint-plugin-patternfly-react": "0.2.0", "eslint-plugin-jquery": "^1.5.1", + "eslint-plugin-patternfly-react": "0.2.0", "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react-hooks": "^2.1.1", + "eslint-plugin-spellcheck": "0.0.17", "graphql": "^15.5.0", "identity-obj-proxy": "^3.0.0", "jest": "^26.4.0", @@ -92,12 +153,27 @@ "stylelint": "^9.3.0", "stylelint-config-standard": "^18.0.0", "tabbable": "^6.2.0", - "victory-core": "~36.8.6", - "victory-legend": "~36.8.6", - "victory-pie": "~36.8.6", "webpack": "^5.75.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^5.0.1", "webpack-stats-plugin": "^1.0.3" + }, + "overrides": { + "victory-core": "36.8.6", + "victory-pie": "36.8.6", + "victory-area": "36.8.6", + "victory-axis": "36.8.6", + "victory-bar": "36.8.6", + "victory-chart": "36.8.6", + "victory-create-container": "36.8.6", + "victory-cursor-container": "36.8.6", + "victory-group": "36.8.6", + "victory-legend": "36.8.6", + "victory-line": "36.8.6", + "victory-scatter": "36.8.6", + "victory-stack": "36.8.6", + "victory-tooltip": "36.8.6", + "victory-voronoi-container": "36.8.6", + "victory-zoom-container": "36.8.6" } } diff --git a/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js b/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js index cd0d40ce54..db34d33645 100644 --- a/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js +++ b/script/lint/@theforeman/eslint-plugin-foreman/lint_generic_config.js @@ -1,6 +1,4 @@ -const foremanVendorRelative = './node_modules/@theforeman/vendor-core/'; - -const packageJsonDirectories = ['./', foremanVendorRelative]; +const packageJsonDirectories = ['./']; module.exports = { plugins: [ diff --git a/script/lint/lint_core_config.js b/script/lint/lint_core_config.js index 25b20804db..ff93cb9d04 100644 --- a/script/lint/lint_core_config.js +++ b/script/lint/lint_core_config.js @@ -38,6 +38,7 @@ module.exports = { 'datacenter', 'datastore', 'datastores', + 'datatables', 'datepicker', 'datetime', 'datetimepicker', @@ -50,6 +51,7 @@ module.exports = { 'dow', 'dropdown', 'dropdowns', + 'dsmorse', 'ec2', 'erb', 'fieldset', @@ -177,6 +179,7 @@ module.exports = { 'txt', 'typeahead', 'ubuntu', + 'ujs', 'uncheck', 'unencrypted', 'unmount', diff --git a/script/npm_lint_plugins.js b/script/npm_lint_plugins.js index d06403638c..fcb05698a8 100755 --- a/script/npm_lint_plugins.js +++ b/script/npm_lint_plugins.js @@ -30,11 +30,8 @@ try { console.error(`Error: ${error.message}`); } -const packageJsonDirectories = [ - './', - './node_modules/@theforeman/vendor-core/', -]; const errors = []; +const packageJsonDirectories = ['./']; dirsKeys.forEach(dirsKey => { let exitCode; const pluginPath = allPluginDirs[dirsKey]; diff --git a/webpack/assets/javascripts/bundle.js b/webpack/assets/javascripts/bundle.js index 8074352d34..cab68adb40 100644 --- a/webpack/assets/javascripts/bundle.js +++ b/webpack/assets/javascripts/bundle.js @@ -1,6 +1,9 @@ import 'core-js/shim'; import 'regenerator-runtime/runtime'; import Cookies from 'js-cookie'; +import jstz from 'jstz'; +import ipaddr from 'ipaddr.js'; +import diff from 'diff'; import compute from './foreman_compute_resource'; import componentRegistry from './react_app/components/componentRegistry'; @@ -59,3 +62,7 @@ window.tfm = Object.assign(window.tfm || {}, { lookupKeys, Cookies, }); + +window.jstz = jstz; +window.ipaddr = ipaddr; +window.diff = diff; diff --git a/webpack/assets/javascripts/exportAll.js b/webpack/assets/javascripts/exportAll.js index feb2aaa33a..c050fab76d 100644 --- a/webpack/assets/javascripts/exportAll.js +++ b/webpack/assets/javascripts/exportAll.js @@ -20,9 +20,11 @@ function generateExports(directoryPath, exportFileContent = '') { !dirent.name.endsWith('.fixtures.js') && !dirent.name.endsWith('mockRequests.js') && !dirent.name.endsWith('vendor-core.scss') && + !dirent.name.endsWith('jquery.js') && !fileNameWithoutExtension.includes('TestHelper') && !fileNameWithoutExtension.includes('testHelper') && - !fileNameWithoutExtension.includes('APITestSetup') + !fileNameWithoutExtension.includes('APITestSetup') && + !fileNameWithoutExtension.includes('vendor-core.scss') ) { let relativeFilePath = path.relative( __dirname, diff --git a/webpack/assets/javascripts/jquery.js b/webpack/assets/javascripts/jquery.js new file mode 100644 index 0000000000..fef4929489 --- /dev/null +++ b/webpack/assets/javascripts/jquery.js @@ -0,0 +1,14 @@ +// to avoid webpack alias loop +const jquery = require('../../../node_modules/jquery'); + +window.$ = jquery; +window.jQuery = jquery; +window.jquery = jquery; + +module.exports = jquery; + +require('jquery-ujs'); +require('multiselect'); +require('select2'); +require('datatables.net-bs'); +require('dsmorse-gridster/dist/jquery.dsmorse-gridster'); diff --git a/webpack/assets/javascripts/react_app/Root/ReactApp.js b/webpack/assets/javascripts/react_app/Root/ReactApp.js index 4af38571a2..5d3b24edd2 100644 --- a/webpack/assets/javascripts/react_app/Root/ReactApp.js +++ b/webpack/assets/javascripts/react_app/Root/ReactApp.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; -import { ConnectedRouter } from 'connected-react-router'; +import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom'; import { ApolloProvider } from '@apollo/client'; import history from '../history'; import { getForemanContext } from '../Root/Context/ForemanContext'; @@ -21,7 +21,7 @@ const ReactApp = ({ layout, metadata, toasts }) => {
- + @@ -29,7 +29,7 @@ const ReactApp = ({ layout, metadata, toasts }) => { - +
diff --git a/webpack/assets/javascripts/react_app/common/helpers.js b/webpack/assets/javascripts/react_app/common/helpers.js index da7067197e..fe63f1e041 100644 --- a/webpack/assets/javascripts/react_app/common/helpers.js +++ b/webpack/assets/javascripts/react_app/common/helpers.js @@ -204,6 +204,13 @@ export const formatDateTime = date => { // generates an absolute, needed in case of running Foreman from a subpath export const foremanUrl = path => `${window.URL_PREFIX}${path}`; +export const visit = url => { + window.location.href = url; +}; + +export const reloadPage = () => { + window.location.reload(); +}; export default { isoCompatibleDate, bindMethods, @@ -224,4 +231,6 @@ export default { formatDateTime, foremanUrl, getWikiURL, + visit, + reloadPage, }; diff --git a/webpack/assets/javascripts/react_app/common/urlHelpers.js b/webpack/assets/javascripts/react_app/common/urlHelpers.js index 8d15d9dda8..de5e54227f 100644 --- a/webpack/assets/javascripts/react_app/common/urlHelpers.js +++ b/webpack/assets/javascripts/react_app/common/urlHelpers.js @@ -1,6 +1,6 @@ import URI from 'urijs'; -import { visit } from '../../foreman_navigation'; +import { visit } from './helpers'; /** * Build a url from given controller, action and id diff --git a/webpack/assets/javascripts/react_app/common/urlHelpers.test.js b/webpack/assets/javascripts/react_app/common/urlHelpers.test.js index efd5e44616..471143a370 100644 --- a/webpack/assets/javascripts/react_app/common/urlHelpers.test.js +++ b/webpack/assets/javascripts/react_app/common/urlHelpers.test.js @@ -1,5 +1,5 @@ import { mockWindowLocation } from './testHelpers'; -import { visit } from '../../foreman_navigation'; +import { visit } from './helpers'; import { urlBuilder, urlWithSearch, @@ -12,6 +12,7 @@ import { exportURL, } from './urlHelpers'; +jest.mock('./helpers') describe('urlBuilder', () => { const controller = 'testController'; const action = 'testAction'; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js index c362380c2d..fc39b96f1a 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/actions.js @@ -1,7 +1,6 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { visit } from '../../../../foreman_navigation'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; import { openConfirmModal } from '../../ConfirmModal'; import { APIActions } from '../../../redux/API'; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js index c3b4622d4f..07dc6db5f0 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/ActionsBar/index.js @@ -16,10 +16,9 @@ import { BuildIcon, TerminalIcon, } from '@patternfly/react-icons'; -import { visit } from '../../../../foreman_navigation'; import { translate as __ } from '../../../common/I18n'; import { selectKebabItems } from './Selectors'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { cancelBuild, deleteHost, isHostTurnOn } from './actions'; import { useForemanSettings, diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/ReportsTab/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/ReportsTab/index.js index d6e30e523b..b8d2fa8ff8 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/ReportsTab/index.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/ReportsTab/index.js @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import React, { useEffect, useCallback } from 'react'; import { useDispatch, useSelector } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import PropTypes from 'prop-types'; import { Grid, GridItem } from '@patternfly/react-core'; import URI from 'urijs'; @@ -20,7 +20,8 @@ import PermissionDenied from '../../../PermissionDenied'; const ReportsTab = ({ hostName, origin }) => { const dispatch = useDispatch(); - const history = useHistory(); + const navigate = useNavigate(); + const location = useLocation(); const API_KEY = `get-reports-${hostName}`; const { reports, itemCount, response } = useSelector(state => selectAPIResponse(state, API_KEY) @@ -58,7 +59,7 @@ const ReportsTab = ({ hostName, origin }) => { useEffect(() => { fetchReports(); - }, [fetchReports, history.location]); + }, [fetchReports, location]); const onPaginationChange = ({ page, per_page }) => { const { search } = getUrlParams(); @@ -95,7 +96,7 @@ const ReportsTab = ({ hostName, origin }) => { ({ page, per_page, search = '' }) => { const uri = new URI(); uri.search({ page, per_page, search }); - history.push({ search: uri.search() }); + navigate({ search: uri.search() }); }, [history] ); diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/Tabs.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/Tabs.js index edb3e2d189..0e84c74c99 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/Tabs.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/Tabs.js @@ -1,18 +1,19 @@ import React from 'react'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import PropTypes from 'prop-types'; const TabsWithHashHistory = ({ tabs }) => { - const hashHistory = useHistory(); + const navigate = useNavigate(); + const onSelect = (evt, tab) => { - hashHistory.push(`/${tab}`); + navigate(`/${tab}`); }; return React.cloneElement(tabs, { onSelect }); }; -export default TabsWithHashHistory; - TabsWithHashHistory.propTypes = { tabs: PropTypes.node.isRequired, }; + +export default TabsWithHashHistory; diff --git a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js index ff8b129f76..6a973cab43 100644 --- a/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js +++ b/webpack/assets/javascripts/react_app/components/HostDetails/Tabs/TabRouter/index.js @@ -1,7 +1,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import { PageSection } from '@patternfly/react-core'; -import { HashRouter, Route, Redirect, Switch } from 'react-router-dom'; +import { + HashRouter, + Route, + Routes, + Navigate, +} from 'react-router-dom'; import { STATUS } from '../../../../constants'; import Slot from '../../../common/Slot'; import { DEFAULT_TAB } from '../../consts'; @@ -16,15 +21,13 @@ const TabRouter = ({ children, tabs, hostName, response, status, router }) => ( > - - - - + + } /> {tabs.map(tab => ( ( + element={ ( id="host-details-page-tabs" fillID={tab} router={router} - {...props} /> - )} + } /> ))} - + ); diff --git a/webpack/assets/javascripts/react_app/components/HostStatuses/index.js b/webpack/assets/javascripts/react_app/components/HostStatuses/index.js index 3849896ca1..afd5dd7886 100644 --- a/webpack/assets/javascripts/react_app/components/HostStatuses/index.js +++ b/webpack/assets/javascripts/react_app/components/HostStatuses/index.js @@ -1,7 +1,7 @@ import React, { Fragment } from 'react'; import { useSelector } from 'react-redux'; import { PageSection, Grid, GridItem, Title } from '@patternfly/react-core'; -import { foremanUrl } from '../../../foreman_tools'; +import { foremanUrl } from '../../common/helpers'; import { useAPI } from '../../common/hooks/API/APIHooks'; import Status from './Status'; import StatusSkeleton from './Status/StatusSkeleton'; diff --git a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js index e6d04bff2a..be546dc337 100644 --- a/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js +++ b/webpack/assets/javascripts/react_app/components/HostsIndex/BulkActions/bulkDelete.js @@ -1,7 +1,6 @@ import React from 'react'; import { FormattedMessage } from 'react-intl'; -import { visit } from '../../../../foreman_navigation'; -import { foremanUrl } from '../../../common/helpers'; +import { visit, foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; import { openConfirmModal } from '../../ConfirmModal'; import { APIActions } from '../../../redux/API'; diff --git a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js index 0a8e01050f..cca7c5d4c2 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js +++ b/webpack/assets/javascripts/react_app/components/Layout/components/ImpersonateIcon/ImpersonateIconActions.js @@ -1,5 +1,5 @@ import { API } from '../../../../redux/API'; -import { foremanUrl } from '../../../../../foreman_tools'; +import { foremanUrl } from '../../../../common/helpers'; import { addToast } from '../../../ToastsList'; diff --git a/webpack/assets/javascripts/react_app/components/Layout/index.js b/webpack/assets/javascripts/react_app/components/Layout/index.js index 6c48f5147f..1341e628b0 100644 --- a/webpack/assets/javascripts/react_app/components/Layout/index.js +++ b/webpack/assets/javascripts/react_app/components/Layout/index.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { initializeLayout, @@ -49,7 +49,7 @@ const ConnectedLayout = ({ children, data }) => { document.body.classList.add('pf-m-expanded'); } }, [isNavCollapsed]); - const { push: navigate } = useHistory(); + const navigate = useNavigate(); const items = useSelector(state => patternflyMenuItemsSelector(state, currentLocation, currentOrganization) ); diff --git a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/Table/TableIndexHooks.js b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/Table/TableIndexHooks.js index 6fb1075db3..2e908752ab 100644 --- a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/Table/TableIndexHooks.js +++ b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/Table/TableIndexHooks.js @@ -1,6 +1,6 @@ import { useState } from 'react'; import URI from 'urijs'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { useAPI } from '../../../../common/hooks/API/APIHooks'; /** @@ -58,13 +58,13 @@ export const useSetParamsAndApiAndSearch = ({ pushToHistory = true, }) => { const [params, setParams] = useState(defaultParams); - const history = useHistory(); + const navigate = useNavigate(); const setParamsAndAPI = newParams => { // add url edit params to the new params if (pushToHistory) { const uri = new URI(); uri.setSearch(newParams); - history.push({ search: uri.search() }); + navigate({ search: uri.search() }); } setParams(newParams); setAPIOptions({ ...apiOptions, params: newParams }); @@ -74,7 +74,7 @@ export const useSetParamsAndApiAndSearch = ({ if (pushToHistory) { const uri = new URI(); uri.setSearch(newSearch); - history.push({ search: uri.search() }); + navigate({ search: uri.search() }); } updateSearchQuery(newSearch.search); setParamsAndAPI({ ...params, ...newSearch }); diff --git a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js index 002b60c164..bd18dccbca 100644 --- a/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js +++ b/webpack/assets/javascripts/react_app/components/PF4/TableIndexPage/TableIndexPage.js @@ -2,7 +2,7 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { QuestionCircleIcon } from '@patternfly/react-icons'; -import { useHistory } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import { Spinner, Toolbar, @@ -112,9 +112,9 @@ const TableIndexPage = ({ updateParamsByUrl, bookmarksPosition, }) => { - const history = useHistory(); - const { location: { search: historySearch } = {} } = history || {}; - const urlParams = new URLSearchParams(historySearch); + const navigate = useNavigate(); + const location = useLocation(); + const urlParams = new URLSearchParams(location.search); const urlParamsSearch = urlParams.get('search') || ''; const search = updateParamsByUrl ? urlParamsSearch || getURIsearch() : ''; const defaultParams = { diff --git a/webpack/assets/javascripts/react_app/components/Pagination/index.js b/webpack/assets/javascripts/react_app/components/Pagination/index.js index 1db7dfa48e..6a4d31f213 100644 --- a/webpack/assets/javascripts/react_app/components/Pagination/index.js +++ b/webpack/assets/javascripts/react_app/components/Pagination/index.js @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; -import { useHistory } from 'react-router-dom'; +import { useNavigate, useLocation } from 'react-router-dom'; import URI from 'urijs'; import classNames from 'classnames'; import { @@ -12,7 +12,6 @@ import { useForemanSettings } from '../../Root/Context/ForemanContext'; import { getURIpage, getURIperPage, - changeQuery, } from '../../common/urlHelpers'; import './index.scss'; @@ -32,14 +31,15 @@ const Pagination = ({ const { perPage: settingsPerPage = 20 } = useForemanSettings() || {}; const [page, setPage] = useState(propsPage); const [perPage, setPerPage] = useState(propsPerPage || settingsPerPage); - const history = useHistory(); - const { location: { search } = {} } = history || {}; + const navigate = useNavigate(); + const location = useLocation(); useEffect(() => { let nextPage = propsPage; let nextPerPage = propsPerPage; if (updateParamsByUrl) { - if (search !== undefined && search.length) { + const search = location.search; + if (search && search.length) { const params = new URLSearchParams(search); nextPage = Number(params.get('page') || getURIpage()); nextPerPage = Number(params.get('per_page') || getURIperPage()); @@ -52,7 +52,7 @@ const Pagination = ({ current => nextPerPage || propsPerPage || current || settingsPerPage ); setPage(current => nextPage || propsPage || current); - }, [search, propsPage, propsPerPage, settingsPerPage, updateParamsByUrl]); + }, [location.search, propsPage, propsPerPage, settingsPerPage, updateParamsByUrl]); const paginationTitles = { items: __('items'), @@ -95,13 +95,9 @@ const Pagination = ({ const updateSearch = params => { if (!updateParamsByUrl) return; - if (history) { - const uri = new URI(); - uri.setSearch(params); - history.push({ search: uri.search() }); - } else { - changeQuery(params); - } + const uri = new URI(location.pathname); // Ensure path is preserved + uri.setSearch(params); + navigate({ search: uri.search() }); }; const cx = classNames('tfm-pagination', className, { diff --git a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js b/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js index 533ab841ae..fc39637c90 100644 --- a/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js +++ b/webpack/assets/javascripts/react_app/components/common/forms/OrderableSelect/index.js @@ -1,6 +1,6 @@ import React from 'react'; import { DndProvider } from 'react-dnd'; -import HTML5Backend from 'react-dnd-html5-backend'; +import { HTML5Backend } from 'react-dnd-html5-backend'; import OrderableSelect from './OrderableSelect'; diff --git a/webpack/assets/javascripts/react_app/redux/API/API.js b/webpack/assets/javascripts/react_app/redux/API/API.js index cf766e7039..1a21cf3df5 100644 --- a/webpack/assets/javascripts/react_app/redux/API/API.js +++ b/webpack/assets/javascripts/react_app/redux/API/API.js @@ -1,6 +1,6 @@ import axios from 'axios'; import './APITestSetup'; -import { foremanUrl } from '../../../foreman_tools'; +import { foremanUrl } from '../../common/helpers'; const getcsrfToken = () => { const token = document.querySelector('meta[name="csrf-token"]'); diff --git a/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js b/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js index 7b340e4bf9..9373901024 100644 --- a/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js +++ b/webpack/assets/javascripts/react_app/redux/actions/notifications/index.js @@ -10,7 +10,7 @@ import { } from '../../consts'; import * as sessionStorage from '../../../components/notifications/NotificationDrawerSessionStorage'; import { API, get } from '../../API'; -import { reloadPage } from '../../../../foreman_navigation'; +import { reloadPage } from '../../../common/helpers'; import { stopInterval, withInterval, diff --git a/webpack/assets/javascripts/react_app/routes/ForemanSwitcher/index.js b/webpack/assets/javascripts/react_app/routes/ForemanSwitcher/index.js index 8cf9105c4a..acd435f0e7 100644 --- a/webpack/assets/javascripts/react_app/routes/ForemanSwitcher/index.js +++ b/webpack/assets/javascripts/react_app/routes/ForemanSwitcher/index.js @@ -1,19 +1,27 @@ import PropTypes from 'prop-types'; import React from 'react'; import { useSelector, shallowEqual } from 'react-redux'; -import { Switch, Route } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom'; import { fallbackRoute } from '../RoutingService'; import { selectRoutes } from '../RouterSelector'; +const FallbackRoute = () => { + React.useEffect(() => { + fallbackRoute(); + }, []); + + return null; +}; + const ForemanSwitcher = ({ children: coreRoutes }) => { const routes = useSelector(() => selectRoutes(coreRoutes), shallowEqual); return ( - + {routes} - - + } /> + ); }; diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js index c98599ca32..84ea051c9e 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageActions.js @@ -1,4 +1,4 @@ -import { foremanUrl } from '../../../../foreman_tools'; +import { foremanUrl } from '../../../common/helpers'; import { get, post } from '../../../redux/API'; import { diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js index 44849b41ad..e81b3adb28 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/RegistrationCommandsPageHelpers.js @@ -2,7 +2,7 @@ import React from 'react'; import { FormSelectOption } from '@patternfly/react-core'; -import { foremanUrl } from '../../../../foreman_tools'; +import { foremanUrl } from '../../../common/helpers'; import { sprintf, translate as __ } from '../../../common/I18n'; // Form helpers diff --git a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js index c77c78cf51..e76aada4e7 100644 --- a/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js +++ b/webpack/assets/javascripts/react_app/routes/RegistrationCommands/RegistrationCommandsPage/components/Actions.js @@ -5,7 +5,7 @@ import { Link } from 'react-router-dom'; import { ActionGroup, Button } from '@patternfly/react-core'; import { translate as __ } from '../../../../common/I18n'; -import { foremanUrl } from '../../../../../foreman_tools'; +import { foremanUrl } from '../../../../common/helpers'; const Actions = ({ isLoading, isGenerating, handleSubmit, invalidFields }) => ( <> diff --git a/webpack/assets/javascripts/react_app/routes/RoutingService.js b/webpack/assets/javascripts/react_app/routes/RoutingService.js index 9f463dcc07..37fb0e2bbf 100644 --- a/webpack/assets/javascripts/react_app/routes/RoutingService.js +++ b/webpack/assets/javascripts/react_app/routes/RoutingService.js @@ -1,6 +1,6 @@ import { Route } from 'react-router-dom'; import React from 'react'; -import { visit } from '../../foreman_navigation'; +import { visit } from '../common/helpers'; import { addGlobalFill } from '../components/common/Fill/GlobalFill'; let currentPath = window.location.href; @@ -19,24 +19,24 @@ export const registerRoutes = (id, routes) => path={path} key={path} {...routeProps} - render={renderProps => renderRoute(render, renderProps)} + element={} /> ) ); /** - * a Helper function for rendering a route - * @param {Function} renderFn - a component's rendering function - * @param {Object} props - routing props + * Route wrapper component to mimic legacy `render` behavior */ -export const renderRoute = (renderFn, props) => { - const { - location, - location: { pathname, search }, - } = props; +const RouteWrapper = ({ render }) => { + const location = window.location; + const pathname = location.pathname; + const search = location.search; + removeRailsContent(); - location && updatePath(`${pathname}${search}`); - return renderFn(props); + updatePath(`${pathname}${search}`); + + // You may use `useLocation()` here if this becomes a component inside Router context + return render({ location }); }; export const fallbackRoute = () => { diff --git a/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js b/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js index d1f1b7014c..2132cea813 100644 --- a/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js +++ b/webpack/assets/javascripts/react_app/routes/common/EmptyPage/RedirectedEmptyPage.js @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import React from 'react'; import { Button } from '@patternfly/react-core'; -import { useHistory } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { SearchIcon } from '@patternfly/react-icons'; import { translate as __ } from '../../../common/I18n'; -import { visit } from '../../../../foreman_navigation'; +import { visit } from '../../../common/helpers'; import { EmptyStatePattern } from '../../../components/common/EmptyState'; const RedirectedEmptyPage = ({ location: { state = {} } }) => { @@ -18,7 +18,7 @@ const RedirectedEmptyPage = ({ location: { state = {} } }) => { secondayActions, back = defaultState.back, } = state; - const history = useHistory(); + const navigate = useNavigate(); const primaryAction = action && (