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 && (
{
const backButton = back && (
history.goBack()}
+ onClick={() => navigate(-1)}
variant="link"
>
{__('Return to the last page')}
diff --git a/webpack/core_test_setup.js b/webpack/core_test_setup.js
index 8a84e98556..3f2c120c2c 100644
--- a/webpack/core_test_setup.js
+++ b/webpack/core_test_setup.js
@@ -26,3 +26,14 @@ jest.mock('./assets/javascripts/foreman_tools', () => ({
foremanUrl: url => url,
}));
jest.mock('./assets/javascripts/foreman_navigation');
+
+jest.mock('./assets/javascripts/react_app/common/helpers', () => {
+ const helpers = jest.requireActual(
+ './assets/javascripts/react_app/common/helpers'
+ );
+ return {
+ ...helpers,
+ visit: jest.fn(),
+ reloadPage: jest.fn(),
+ };
+});
diff --git a/webpack/jest.config.js b/webpack/jest.config.js
index 4e0f1812c3..3ffc174d89 100644
--- a/webpack/jest.config.js
+++ b/webpack/jest.config.js
@@ -5,19 +5,10 @@ const path = require('path');
const nodeModules = path.resolve(__dirname, '..', 'node_modules');
const packageJsonPath = path.resolve(__dirname, '..', 'package.json');
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
-const vendorCorePackageJsonPath = path.resolve(
- nodeModules,
- '@theforeman/vendor-core',
- 'package.json'
-);
-const vendorCorePackageJson = JSON.parse(
- fs.readFileSync(vendorCorePackageJsonPath, 'utf8')
-);
const dependencies = {
...packageJson.dependencies,
...packageJson.devDependencies,
- ...vendorCorePackageJson.dependencies,
'@apollo/client/testing': '@apollo/client/testing',
}; // Use shared dependencies from foreman node_modules and not plugin node_modules to avoid jest errors due to multiple instances of same package
@@ -52,12 +43,7 @@ module.exports = {
'.+fixtures.+',
'foreman/webpack', // dont test foreman core in plugins
],
- moduleDirectories: [
- `node_modules`,
- `/node_modules/@theforeman/vendor-core/node_modules`,
- `node_modules/@theforeman/vendor-core/node_modules`,
- '/node_modules',
- ],
+ moduleDirectories: [`node_modules`, '/node_modules'],
transform: {
'^.+\\.js?$': 'babel-jest',
'\\.(gql|graphql)$': require.resolve('jest-transform-graphql'), // for graphql-tag
@@ -82,5 +68,4 @@ module.exports = {
__testing__: true,
URL_PREFIX: '',
},
- resolver: require.resolve('./resolveNodeModule'),
};
diff --git a/webpack/resolveNodeModule.js b/webpack/resolveNodeModule.js
deleted file mode 100644
index 30f3d22b31..0000000000
--- a/webpack/resolveNodeModule.js
+++ /dev/null
@@ -1,62 +0,0 @@
-const { modules } = require('@theforeman/vendor-core');
-
-const isRequestedByVendorCore = currentFileDirectory =>
- (currentFileDirectory.includes('foreman-js/packages/vendor-core') ||
- currentFileDirectory.includes('@theforeman/vendor-core')) &&
- !currentFileDirectory.includes(
- 'foreman-js/packages/vendor-core/node_modules'
- ) &&
- !currentFileDirectory.includes('@theforeman/vendor-core/node_modules');
-
-const getModuleToResolve = ({ sourcePath, currentFileDirectory }) => {
- // map the custom foreman js module to the correct path
- const requestPath = sourcePath === '.' ? './index' : sourcePath;
- const sourcePathSplit = sourcePath.split('/');
- const name = sourcePathSplit[sourcePathSplit.length - 1];
- const vendorModule = modules.find(m => m.name === name);
- const requestedByVendorCore = isRequestedByVendorCore(currentFileDirectory);
-
- const shouldResolveCustomVendorModule = modules.find(
- m => m.name === name && m.hasCustomPath && !requestedByVendorCore
- );
-
- return shouldResolveCustomVendorModule ? vendorModule.path : requestPath;
-};
-
-/**
- * resolve a import/require of a node module
- * this method should be calld by the jest-resolver
- * to resolve every require statement
- * This cannot be done with changing the paths with moduleNameMapper
- */
-
-const resolveNodeModule = (sourcePath, currentFile) => {
- const { basedir, rootDir } = currentFile;
- const moduleToResolve = getModuleToResolve({
- sourcePath,
- currentFileDirectory: basedir,
- });
- if (sourcePath.includes('theforeman_test_dependencies')) {
- // eslint-disable-next-line no-console
- console.warn(
- 'import from @theforeman/test is deprecated, please remove the package and import from enzyme, axios-mock-adapter, foremanReact/testHelpers, foremanReact/common/IntegrationTestHelper directly instead.'
- );
- }
- let results;
- try {
- results = require.resolve(moduleToResolve, {
- paths: [basedir],
- });
- } catch (error) {
- results = currentFile.defaultResolver(moduleToResolve, currentFile);
- }
-
- return rootDir
- ? results.replace(
- /.*\/foreman-js\/packages/,
- `${rootDir}/node_modules/@theforeman`
- )
- : results;
-};
-
-module.exports = resolveNodeModule;