feat: add environment configuration files for frontend (#536)
Signed-off-by: Guilherme Caponetto <638737+caponetto@users.noreply.github.com>
This commit is contained in:
parent
42ffd9b0c5
commit
e666e2ebfb
|
|
@ -0,0 +1,4 @@
|
|||
LOGO=logo-light-theme.svg
|
||||
LOGO_DARK=logo-dark-theme.svg
|
||||
FAVICON=favicon.ico
|
||||
PRODUCT_NAME="Notebooks"
|
||||
|
|
@ -1,2 +1,7 @@
|
|||
# Test against prod build hosted by lightweight http server
|
||||
BASE_URL=http://localhost:9001
|
||||
DEPLOYMENT_MODE=standalone
|
||||
POLL_INTERVAL=9999999
|
||||
DIST_DIR=./dist
|
||||
URL_PREFIX=/
|
||||
PUBLIC_PATH=/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
APP_ENV=development
|
||||
DEPLOYMENT_MODE=standalone
|
||||
MOCK_API_ENABLED=true
|
||||
|
|
@ -0,0 +1 @@
|
|||
APP_ENV=production
|
||||
|
|
@ -5,6 +5,5 @@ yarn.lock
|
|||
stats.json
|
||||
coverage
|
||||
.idea
|
||||
.env
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/settings.json
|
||||
|
|
|
|||
|
|
@ -50,11 +50,7 @@ This is the default setup for running the UI locally. Make sure you build the pr
|
|||
npm run start:dev
|
||||
```
|
||||
|
||||
The command above requires the backend to be active in order to serve data. To run the UI independently, without establishing a connection to the backend, use the following command to start the application with a mocked API:
|
||||
|
||||
```bash
|
||||
npm run start:dev:mock
|
||||
```
|
||||
The command above starts the UI with mocked data by default, so you can run the application without requiring a connection to the backend. This behavior can be customized in the `.env.development` file by setting the `MOCK_API_ENABLED` environment variable to `false`.
|
||||
|
||||
### Testing
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
chrome: 110,
|
||||
},
|
||||
useBuiltIns: 'usage',
|
||||
corejs: '3',
|
||||
},
|
||||
],
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
|
|
@ -5,4 +5,6 @@ jovyan
|
|||
millicores
|
||||
workspacekind
|
||||
workspacekinds
|
||||
healthcheck
|
||||
healthcheck
|
||||
pficon
|
||||
svgs
|
||||
|
|
|
|||
|
|
@ -0,0 +1,191 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dotenv = require('dotenv');
|
||||
const dotenvExpand = require('dotenv-expand');
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
|
||||
/**
|
||||
* Determine if the project is standalone or nested.
|
||||
*
|
||||
* @param {string} directory
|
||||
* @returns {boolean}
|
||||
*/
|
||||
const getProjectIsRootDir = (directory) => {
|
||||
const dotenvLocalFile = path.resolve(directory, '.env.local');
|
||||
const dotenvFile = path.resolve(directory, '.env');
|
||||
let localIsRoot;
|
||||
let isRoot;
|
||||
|
||||
if (fs.existsSync(dotenvLocalFile)) {
|
||||
const { IS_PROJECT_ROOT_DIR: DOTENV_LOCAL_ROOT } = dotenv.parse(
|
||||
fs.readFileSync(dotenvLocalFile),
|
||||
);
|
||||
localIsRoot = DOTENV_LOCAL_ROOT;
|
||||
}
|
||||
|
||||
if (fs.existsSync(dotenvFile)) {
|
||||
const { IS_PROJECT_ROOT_DIR: DOTENV_ROOT } = dotenv.parse(fs.readFileSync(dotenvFile));
|
||||
isRoot = DOTENV_ROOT;
|
||||
}
|
||||
|
||||
return localIsRoot !== undefined ? localIsRoot !== 'false' : isRoot !== 'false';
|
||||
};
|
||||
|
||||
/**
|
||||
* Return tsconfig compilerOptions.
|
||||
*
|
||||
* @param {string} directory
|
||||
* @returns {object}
|
||||
*/
|
||||
const getTsCompilerOptions = (directory) => {
|
||||
const tsconfigFile = path.resolve(directory, './tsconfig.json');
|
||||
let tsCompilerOptions = {};
|
||||
|
||||
if (fs.existsSync(tsconfigFile)) {
|
||||
const { compilerOptions = { outDir: './dist', baseUrl: './src' } } = require(tsconfigFile);
|
||||
tsCompilerOptions = compilerOptions;
|
||||
}
|
||||
|
||||
return tsCompilerOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup a webpack dotenv plugin config.
|
||||
*
|
||||
* @param {string} path
|
||||
* @returns {*}
|
||||
*/
|
||||
const setupWebpackDotenvFile = (path) => {
|
||||
const settings = {
|
||||
systemvars: true,
|
||||
silent: true,
|
||||
};
|
||||
|
||||
if (path) {
|
||||
settings.path = path;
|
||||
}
|
||||
|
||||
return new Dotenv(settings);
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup multiple webpack dotenv file parameters.
|
||||
*
|
||||
* @param {string} directory
|
||||
* @param {string} env
|
||||
* @param {boolean} isRoot
|
||||
* @returns {Array}
|
||||
*/
|
||||
const setupWebpackDotenvFilesForEnv = ({ directory, env, isRoot = true }) => {
|
||||
const dotenvWebpackSettings = [];
|
||||
|
||||
if (process.env.CY_MOCK) {
|
||||
dotenvWebpackSettings.push(
|
||||
setupWebpackDotenvFile(path.resolve(directory, `.env.cypress.mock`)),
|
||||
);
|
||||
}
|
||||
|
||||
if (env) {
|
||||
dotenvWebpackSettings.push(
|
||||
setupWebpackDotenvFile(path.resolve(directory, `.env.${env}.local`)),
|
||||
);
|
||||
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, `.env.${env}`)));
|
||||
}
|
||||
|
||||
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env.local')));
|
||||
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '.env')));
|
||||
|
||||
if (!isRoot) {
|
||||
if (env) {
|
||||
dotenvWebpackSettings.push(
|
||||
setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}.local`)),
|
||||
);
|
||||
dotenvWebpackSettings.push(
|
||||
setupWebpackDotenvFile(path.resolve(directory, '..', `.env.${env}`)),
|
||||
);
|
||||
}
|
||||
|
||||
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env.local')));
|
||||
dotenvWebpackSettings.push(setupWebpackDotenvFile(path.resolve(directory, '..', '.env')));
|
||||
}
|
||||
|
||||
return dotenvWebpackSettings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup, and access, a dotenv file and the related set of parameters.
|
||||
*
|
||||
* @param {string} path
|
||||
* @returns {*}
|
||||
*/
|
||||
const setupDotenvFile = (path) => {
|
||||
const dotenvInitial = dotenv.config({ path });
|
||||
dotenvExpand(dotenvInitial);
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup and access local and specific dotenv file parameters.
|
||||
*
|
||||
* @param {string} env
|
||||
*/
|
||||
const setupDotenvFilesForEnv = ({ env }) => {
|
||||
const RELATIVE_DIRNAME = path.resolve(__dirname, '..');
|
||||
const IS_ROOT = getProjectIsRootDir(RELATIVE_DIRNAME);
|
||||
const { baseUrl: TS_BASE_URL, outDir: TS_OUT_DIR } = getTsCompilerOptions(RELATIVE_DIRNAME);
|
||||
|
||||
if (process.env.CY_MOCK) {
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.cypress.mock`));
|
||||
}
|
||||
|
||||
if (!IS_ROOT) {
|
||||
if (env) {
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}.local`));
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', `.env.${env}`));
|
||||
}
|
||||
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env.local'));
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '..', '.env'));
|
||||
}
|
||||
|
||||
if (env) {
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}.local`));
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, `.env.${env}`));
|
||||
}
|
||||
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env.local'));
|
||||
setupDotenvFile(path.resolve(RELATIVE_DIRNAME, '.env'));
|
||||
|
||||
const DEPLOYMENT_MODE = process.env.DEPLOYMENT_MODE || 'kubeflow';
|
||||
const AUTH_METHOD = process.env.AUTH_METHOD || 'internal';
|
||||
const IMAGES_DIRNAME = process.env.IMAGES_DIRNAME || 'images';
|
||||
const PUBLIC_PATH = process.env.PUBLIC_PATH || '/workspaces';
|
||||
const SRC_DIR = path.resolve(RELATIVE_DIRNAME, process.env.SRC_DIR || TS_BASE_URL || 'src');
|
||||
const COMMON_DIR = path.resolve(RELATIVE_DIRNAME, process.env.COMMON_DIR || '../common');
|
||||
const DIST_DIR = path.resolve(RELATIVE_DIRNAME, process.env.DIST_DIR || TS_OUT_DIR || 'dist');
|
||||
const HOST = process.env.HOST || DEPLOYMENT_MODE === 'kubeflow' ? '0.0.0.0' : 'localhost';
|
||||
const PORT = process.env.PORT || '9000';
|
||||
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http';
|
||||
const PROXY_HOST = process.env.PROXY_HOST || 'localhost';
|
||||
const PROXY_PORT = process.env.PROXY_PORT || process.env.PORT || 4000;
|
||||
const DEV_MODE = process.env.DEV_MODE || undefined;
|
||||
const OUTPUT_ONLY = process.env._OUTPUT_ONLY === 'true';
|
||||
|
||||
process.env._RELATIVE_DIRNAME = RELATIVE_DIRNAME;
|
||||
process.env._IS_PROJECT_ROOT_DIR = IS_ROOT;
|
||||
process.env._IMAGES_DIRNAME = IMAGES_DIRNAME;
|
||||
process.env._PUBLIC_PATH = PUBLIC_PATH;
|
||||
process.env._SRC_DIR = SRC_DIR;
|
||||
process.env._COMMON_DIR = COMMON_DIR;
|
||||
process.env._DIST_DIR = DIST_DIR;
|
||||
process.env._HOST = HOST;
|
||||
process.env._PORT = PORT;
|
||||
process.env._PROXY_PROTOCOL = PROXY_PROTOCOL;
|
||||
process.env._PROXY_HOST = PROXY_HOST;
|
||||
process.env._PROXY_PORT = PROXY_PORT;
|
||||
process.env._OUTPUT_ONLY = OUTPUT_ONLY;
|
||||
process.env._DEV_MODE = DEV_MODE;
|
||||
process.env._DEPLOYMENT_MODE = DEPLOYMENT_MODE;
|
||||
process.env._AUTH_METHOD = AUTH_METHOD;
|
||||
};
|
||||
|
||||
module.exports = { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv };
|
||||
|
|
@ -23,5 +23,6 @@ module.exports = {
|
|||
relativeDir,
|
||||
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css',
|
||||
),
|
||||
path.resolve(relativeDir, 'node_modules/@patternfly/react-catalog-view-extension/dist/css'),
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,186 +1,245 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const { EnvironmentPlugin } = require('webpack');
|
||||
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
||||
const IMAGES_DIRNAME = 'images';
|
||||
const relativeDir = path.resolve(__dirname, '..');
|
||||
module.exports = (env) => {
|
||||
return {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(tsx|ts|jsx)?$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
experimentalWatchApi: true,
|
||||
const { setupWebpackDotenvFilesForEnv } = require('./dotenv');
|
||||
const { name } = require('../package.json');
|
||||
|
||||
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||
const IMAGES_DIRNAME = process.env._IMAGES_DIRNAME;
|
||||
const PUBLIC_PATH = process.env._PUBLIC_PATH;
|
||||
const SRC_DIR = process.env._SRC_DIR;
|
||||
const COMMON_DIR = process.env._COMMON_DIR;
|
||||
const DIST_DIR = process.env._DIST_DIR;
|
||||
const OUTPUT_ONLY = process.env._OUTPUT_ONLY;
|
||||
const FAVICON = process.env.FAVICON;
|
||||
const PRODUCT_NAME = process.env.PRODUCT_NAME;
|
||||
const COVERAGE = process.env.COVERAGE;
|
||||
const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE;
|
||||
const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH;
|
||||
|
||||
if (OUTPUT_ONLY !== 'true') {
|
||||
console.info(
|
||||
`\nPrepping files...` +
|
||||
`\n\tSRC DIR: ${SRC_DIR}` +
|
||||
`\n\tOUTPUT DIR: ${DIST_DIR}` +
|
||||
`\n\tPUBLIC PATH: ${PUBLIC_PATH}` +
|
||||
`\n\tBASE_PATH: ${BASE_PATH}\n`,
|
||||
);
|
||||
if (COVERAGE === 'true') {
|
||||
console.info('\nAdding code coverage instrumentation.\n');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (env) => ({
|
||||
entry: {
|
||||
app: path.join(SRC_DIR, 'index.tsx'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(tsx|ts|jsx|js)?$/,
|
||||
exclude: [/node_modules/, /__tests__/, /__mocks__/],
|
||||
include: [SRC_DIR, COMMON_DIR],
|
||||
use: [
|
||||
COVERAGE === 'true' && '@jsdevtools/coverage-istanbul-loader',
|
||||
env === 'development'
|
||||
? { loader: 'swc-loader' }
|
||||
: {
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
||||
// only process modules with this loader
|
||||
// if they live under a 'fonts' or 'pficon' directory
|
||||
include: [
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly/dist/fonts'),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
||||
),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
||||
),
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/fonts'),
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/pficon'),
|
||||
],
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
// Limit at 50k. larger files emitted into separate files
|
||||
limit: 5000,
|
||||
outputPath: 'fonts',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
||||
type: 'asset/resource',
|
||||
// only process modules with this loader
|
||||
// if they live under a 'fonts' or 'pficon' directory
|
||||
include: [
|
||||
path.resolve(relativeDir, 'node_modules/patternfly/dist/fonts'),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
||||
),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
||||
),
|
||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/fonts'),
|
||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/pficon'),
|
||||
],
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
include: (input) => input.indexOf('background-filter.svg') > 1,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
// Limit at 50k. larger files emitted into separate files
|
||||
limit: 5000,
|
||||
outputPath: 'fonts',
|
||||
outputPath: 'svgs',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
// only process SVG modules with this loader if they live under a 'bgimages' directory
|
||||
// this is primarily useful when applying a CSS background using an SVG
|
||||
include: (input) => input.indexOf(IMAGES_DIRNAME) > -1,
|
||||
use: {
|
||||
loader: 'svg-url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
include: (input) => input.indexOf('background-filter.svg') > 1,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 5000,
|
||||
outputPath: 'svgs',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
// only process SVG modules with this loader when they don't live under a 'bgimages',
|
||||
// 'fonts', or 'pficon' directory, those are handled with other loaders
|
||||
include: (input) =>
|
||||
input.indexOf(IMAGES_DIRNAME) === -1 &&
|
||||
input.indexOf('fonts') === -1 &&
|
||||
input.indexOf('background-filter') === -1 &&
|
||||
input.indexOf('pficon') === -1,
|
||||
use: {
|
||||
loader: 'raw-loader',
|
||||
options: {},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
// only process SVG modules with this loader if they live under a 'bgimages' directory
|
||||
// this is primarily useful when applying a CSS background using an SVG
|
||||
include: (input) => input.indexOf(IMAGES_DIRNAME) > -1,
|
||||
use: {
|
||||
loader: 'svg-url-loader',
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|jpeg|png|gif)$/i,
|
||||
include: [
|
||||
SRC_DIR,
|
||||
COMMON_DIR,
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/patternfly'),
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/patternfly/assets/images'),
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly/react-styles/css/assets/images'),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
RELATIVE_DIRNAME,
|
||||
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
],
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 10000,
|
||||
limit: 5000,
|
||||
outputPath: 'images',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
// only process SVG modules with this loader when they don't live under a 'bgimages',
|
||||
// 'fonts', or 'pficon' directory, those are handled with other loaders
|
||||
include: (input) =>
|
||||
input.indexOf(IMAGES_DIRNAME) === -1 &&
|
||||
input.indexOf('fonts') === -1 &&
|
||||
input.indexOf('background-filter') === -1 &&
|
||||
input.indexOf('pficon') === -1,
|
||||
use: {
|
||||
loader: 'raw-loader',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|jpeg|png|gif)$/i,
|
||||
include: [
|
||||
path.resolve(relativeDir, 'src'),
|
||||
path.resolve(relativeDir, 'node_modules/patternfly'),
|
||||
path.resolve(relativeDir, 'node_modules/@patternfly/patternfly/assets/images'),
|
||||
path.resolve(relativeDir, 'node_modules/@patternfly/react-styles/css/assets/images'),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-core/dist/styles/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-core/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-table/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-inline-edit-extension/node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
],
|
||||
type: 'asset/inline',
|
||||
use: [
|
||||
{
|
||||
options: {
|
||||
limit: 5000,
|
||||
outputPath: 'images',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
'style-loader',
|
||||
// Translates CSS into CommonJS
|
||||
'css-loader',
|
||||
// Compiles Sass to CSS
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
include: [
|
||||
path.resolve(
|
||||
relativeDir,
|
||||
'node_modules/@patternfly/react-catalog-view-extension/dist/css/react-catalog-view-extension.css',
|
||||
),
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
path: path.resolve(relativeDir, 'dist'),
|
||||
publicPath: APP_PREFIX,
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(relativeDir, 'src', 'index.html'),
|
||||
}),
|
||||
new Dotenv({
|
||||
systemvars: true,
|
||||
silent: true,
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [{ from: './src/images', to: 'images' }],
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
new EnvironmentPlugin({
|
||||
APP_PREFIX: process.env.APP_PREFIX || '/workspaces',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
// Creates `style` nodes from JS strings
|
||||
'style-loader',
|
||||
// Translates CSS into CommonJS
|
||||
'css-loader',
|
||||
// Compiles Sass to CSS
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.ya?ml$/,
|
||||
use: 'js-yaml-loader',
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx', '.jsx'],
|
||||
plugins: [
|
||||
new TsconfigPathsPlugin({
|
||||
configFile: path.resolve(relativeDir, './tsconfig.json'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
path: DIST_DIR,
|
||||
publicPath: BASE_PATH,
|
||||
uniqueName: name,
|
||||
},
|
||||
plugins: [
|
||||
...setupWebpackDotenvFilesForEnv({
|
||||
directory: RELATIVE_DIRNAME,
|
||||
isRoot: IS_PROJECT_ROOT_DIR,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.join(SRC_DIR, 'index.html'),
|
||||
title: PRODUCT_NAME,
|
||||
favicon: path.join(SRC_DIR, 'images', FAVICON),
|
||||
publicPath: BASE_PATH,
|
||||
base: {
|
||||
href: BASE_PATH,
|
||||
},
|
||||
chunks: ['app'],
|
||||
}),
|
||||
new CopyPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.join(SRC_DIR, 'locales'),
|
||||
to: path.join(DIST_DIR, 'locales'),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'favicons'),
|
||||
to: path.join(DIST_DIR, 'favicons'),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'images'),
|
||||
to: path.join(DIST_DIR, 'images'),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'favicon.ico'),
|
||||
to: path.join(DIST_DIR),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'favicon.png'),
|
||||
to: path.join(DIST_DIR),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'manifest.json'),
|
||||
to: path.join(DIST_DIR),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
{
|
||||
from: path.join(SRC_DIR, 'robots.txt'),
|
||||
to: path.join(DIST_DIR),
|
||||
noErrorOnMissing: true,
|
||||
},
|
||||
],
|
||||
symlinks: false,
|
||||
cacheWithContext: false,
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.js', '.ts', '.tsx', '.jsx'],
|
||||
alias: {
|
||||
'~': path.resolve(SRC_DIR),
|
||||
},
|
||||
};
|
||||
};
|
||||
symlinks: false,
|
||||
cacheWithContext: false,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,60 +1,141 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
const path = require('path');
|
||||
const { EnvironmentPlugin } = require('webpack');
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const { stylePaths } = require('./stylePaths');
|
||||
const HOST = process.env.HOST || 'localhost';
|
||||
const PORT = process.env.PORT || '9000';
|
||||
const PROXY_HOST = process.env.PROXY_HOST || 'localhost';
|
||||
const PROXY_PORT = process.env.PROXY_PORT || '4000';
|
||||
const PROXY_PROTOCOL = process.env.PROXY_PROTOCOL || 'http:';
|
||||
const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED || 'false';
|
||||
const relativeDir = path.resolve(__dirname, '..');
|
||||
const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
||||
const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin');
|
||||
const SpeedMeasurePlugin = require('speed-measure-webpack-plugin');
|
||||
|
||||
module.exports = merge(common('development'), {
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
devServer: {
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
historyApiFallback: {
|
||||
index: APP_PREFIX + '/index.html',
|
||||
const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE });
|
||||
|
||||
setupDotenvFilesForEnv({ env: 'development' });
|
||||
const webpackCommon = require('./webpack.common.js');
|
||||
|
||||
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||
const SRC_DIR = process.env._SRC_DIR;
|
||||
const COMMON_DIR = process.env._COMMON_DIR;
|
||||
const PUBLIC_PATH = process.env._PUBLIC_PATH;
|
||||
const DIST_DIR = process.env._DIST_DIR;
|
||||
const HOST = process.env._HOST;
|
||||
const PORT = process.env._PORT;
|
||||
const PROXY_PROTOCOL = process.env._PROXY_PROTOCOL;
|
||||
const PROXY_HOST = process.env._PROXY_HOST;
|
||||
const PROXY_PORT = process.env._PROXY_PORT;
|
||||
const DEPLOYMENT_MODE = process.env._DEPLOYMENT_MODE;
|
||||
const AUTH_METHOD = process.env._AUTH_METHOD;
|
||||
const BASE_PATH = DEPLOYMENT_MODE === 'kubeflow' ? '/workspaces' : PUBLIC_PATH;
|
||||
|
||||
// Function to generate headers based on deployment mode
|
||||
const getProxyHeaders = () => {
|
||||
if (AUTH_METHOD === 'internal') {
|
||||
return {
|
||||
'kubeflow-userid': 'user@example.com',
|
||||
};
|
||||
}
|
||||
if (AUTH_METHOD === 'user_token') {
|
||||
try {
|
||||
const token = execSync(
|
||||
"kubectl config view --raw --minify --flatten -o jsonpath='{.users[].user.token}'",
|
||||
)
|
||||
.toString()
|
||||
.trim();
|
||||
const username = execSync("kubectl auth whoami -o jsonpath='{.status.userInfo.username}'")
|
||||
.toString()
|
||||
.trim();
|
||||
// eslint-disable-next-line no-console
|
||||
console.info('Logged in as user:', username);
|
||||
return {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'x-forwarded-access-token': token,
|
||||
};
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Failed to get Kubernetes token:', error.message);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
module.exports = smp.wrap(
|
||||
merge(
|
||||
{
|
||||
plugins: [
|
||||
...setupWebpackDotenvFilesForEnv({
|
||||
directory: RELATIVE_DIRNAME,
|
||||
env: 'development',
|
||||
isRoot: IS_PROJECT_ROOT_DIR,
|
||||
}),
|
||||
],
|
||||
},
|
||||
open: [APP_PREFIX],
|
||||
static: {
|
||||
directory: path.resolve(relativeDir, 'dist'),
|
||||
publicPath: APP_PREFIX,
|
||||
},
|
||||
client: {
|
||||
overlay: true,
|
||||
},
|
||||
proxy: [
|
||||
{
|
||||
context: ['/api'],
|
||||
target: {
|
||||
host: PROXY_HOST,
|
||||
protocol: PROXY_PROTOCOL,
|
||||
port: PROXY_PORT,
|
||||
webpackCommon('development'),
|
||||
{
|
||||
mode: 'development',
|
||||
devtool: 'eval-source-map',
|
||||
optimization: {
|
||||
runtimeChunk: 'single',
|
||||
removeEmptyChunks: true,
|
||||
},
|
||||
devServer: {
|
||||
host: HOST,
|
||||
port: PORT,
|
||||
compress: true,
|
||||
historyApiFallback: {
|
||||
index: `${BASE_PATH}/index.html`,
|
||||
},
|
||||
hot: true,
|
||||
open: [BASE_PATH],
|
||||
proxy: [
|
||||
{
|
||||
context: ['/api', '/workspaces/api'],
|
||||
target: {
|
||||
host: PROXY_HOST,
|
||||
protocol: PROXY_PROTOCOL,
|
||||
port: PROXY_PORT,
|
||||
},
|
||||
changeOrigin: true,
|
||||
headers: getProxyHeaders(),
|
||||
},
|
||||
],
|
||||
devMiddleware: {
|
||||
stats: 'errors-only',
|
||||
},
|
||||
client: {
|
||||
overlay: false,
|
||||
},
|
||||
static: {
|
||||
directory: DIST_DIR,
|
||||
publicPath: BASE_PATH,
|
||||
},
|
||||
onListening: (devServer) => {
|
||||
if (devServer) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`\x1b[32m✓ Dashboard available at: \x1b[4mhttp://localhost:${
|
||||
devServer.server.address().port
|
||||
}\x1b[0m`,
|
||||
);
|
||||
}
|
||||
},
|
||||
changeOrigin: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: [...stylePaths],
|
||||
use: ['style-loader', 'css-loader'],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: [
|
||||
SRC_DIR,
|
||||
COMMON_DIR,
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'),
|
||||
],
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new EnvironmentPlugin({
|
||||
WEBPACK_REPLACE__mockApiEnabled: MOCK_API_ENABLED,
|
||||
}),
|
||||
],
|
||||
});
|
||||
plugins: [
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
new ReactRefreshWebpackPlugin({ overlay: false }),
|
||||
],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,44 +1,61 @@
|
|||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
const path = require('path');
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
const { EnvironmentPlugin } = require('webpack');
|
||||
const { stylePaths } = require('./stylePaths');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
const { setupWebpackDotenvFilesForEnv, setupDotenvFilesForEnv } = require('./dotenv');
|
||||
|
||||
const PRODUCTION = process.env.PRODUCTION || 'false';
|
||||
setupDotenvFilesForEnv({ env: 'production' });
|
||||
const webpackCommon = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common('production'), {
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserJSPlugin({}),
|
||||
new CssMinimizerPlugin({
|
||||
minimizerOptions: {
|
||||
preset: ['default', { mergeLonghand: false }],
|
||||
},
|
||||
const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME;
|
||||
const IS_PROJECT_ROOT_DIR = process.env._IS_PROJECT_ROOT_DIR;
|
||||
const SRC_DIR = process.env._SRC_DIR;
|
||||
const COMMON_DIR = process.env._COMMON_DIR;
|
||||
const DIST_DIR = process.env._DIST_DIR;
|
||||
const OUTPUT_ONLY = process.env._OUTPUT_ONLY;
|
||||
|
||||
if (OUTPUT_ONLY !== 'true') {
|
||||
console.info(`Cleaning OUTPUT DIR...\n ${DIST_DIR}\n`);
|
||||
}
|
||||
|
||||
module.exports = merge(
|
||||
{
|
||||
plugins: [
|
||||
...setupWebpackDotenvFilesForEnv({
|
||||
directory: RELATIVE_DIRNAME,
|
||||
env: 'production',
|
||||
isRoot: IS_PROJECT_ROOT_DIR,
|
||||
}),
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[name].bundle.css',
|
||||
}),
|
||||
new EnvironmentPlugin({
|
||||
PRODUCTION,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: [...stylePaths],
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
},
|
||||
webpackCommon('production'),
|
||||
{
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [new TerserJSPlugin(), new CssMinimizerPlugin()],
|
||||
},
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[name].bundle.css',
|
||||
ignoreOrder: true,
|
||||
}),
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
include: [
|
||||
SRC_DIR,
|
||||
COMMON_DIR,
|
||||
path.resolve(RELATIVE_DIRNAME, 'node_modules/@patternfly'),
|
||||
],
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -18,91 +18,88 @@
|
|||
"build:bundle-profile": "webpack --config ./config/webpack.prod.js --profile --json > ./bundle.stats.json",
|
||||
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
|
||||
"build:clean": "rimraf ./dist",
|
||||
"build:prod": "cross-env PRODUCTION=true webpack --config ./config/webpack.prod.js",
|
||||
"build:prod": "webpack --config ./config/webpack.prod.js",
|
||||
"generate:api": "./scripts/generate-api.sh && npm run prettier",
|
||||
"start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js",
|
||||
"start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev",
|
||||
"test": "run-s prettier:check test:lint test:unit test:cypress-ci",
|
||||
"test:cypress-ci": "npx concurrently -P -k -s first \"npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ",
|
||||
"start:dev": "webpack serve --hot --color --config ./config/webpack.dev.js",
|
||||
"test": "run-s prettier:check test:lint test:type-check test:unit test:cypress-ci",
|
||||
"test:cypress-ci": "npx concurrently -P -k -s first \"CY_MOCK=1 npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ",
|
||||
"test:jest": "jest --passWithNoTests",
|
||||
"test:unit": "npm run test:jest -- --silent",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
"test:lint": "eslint --max-warnings 0 --ext .js,.ts,.jsx,.tsx ./src",
|
||||
"test:lint:fix": "eslint --ext .js,.ts,.jsx,.tsx ./src --fix",
|
||||
"test:type-check": "tsc --noEmit",
|
||||
"test:fix": "run-s prettier test:lint:fix",
|
||||
"cypress:open": "cypress open --project src/__tests__/cypress",
|
||||
"cypress:open:mock": "CY_MOCK=1 CY_WS_PORT=9002 npm run cypress:open -- ",
|
||||
"cypress:run": "cypress run -b chrome --project src/__tests__/cypress",
|
||||
"cypress:run:mock": "CY_MOCK=1 npm run cypress:run -- ",
|
||||
"cypress:server:build": "POLL_INTERVAL=9999999 FAST_POLL_INTERVAL=9999999 APP_PREFIX=/ webpack --config ./config/webpack.prod.js",
|
||||
"cypress:server:build": "npm run build",
|
||||
"cypress:server": "serve ./dist -p 9001 -s -L",
|
||||
"prettier": "prettier --ignore-path .gitignore --write \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
||||
"prettier:check": "prettier --ignore-path .gitignore --check \"**/*{.ts,.tsx,.js,.cjs,.jsx,.css,.json}\"",
|
||||
"prepare": "cd ../../ && husky workspaces/frontend/.husky"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cspell/eslint-plugin": "^9.1.2",
|
||||
"@cypress/code-coverage": "^3.13.5",
|
||||
"@mui/icons-material": "^6.3.1",
|
||||
"@mui/icons-material": "^6.4.8",
|
||||
"@mui/material": "^6.3.1",
|
||||
"@mui/types": "^7.2.21",
|
||||
"@testing-library/cypress": "^10.0.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "14.4.3",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||
"@swc/core": "^1.9.1",
|
||||
"@types/chai-subset": "^1.3.5",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
"@types/victory": "^33.1.5",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/dompurify": "^3.2.0",
|
||||
"@types/jest": "^29.5.13",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/showdown": "^2.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "^8.35.0",
|
||||
"chai-subset": "^1.6.0",
|
||||
"concurrently": "^9.1.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.39.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"css-loader": "^6.11.0",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"cypress": "^13.16.1",
|
||||
"cypress-axe": "^1.5.0",
|
||||
"cypress-high-resolution": "^1.0.0",
|
||||
"cypress-mochawesome-reporter": "^3.8.2",
|
||||
"cypress-multi-reporters": "^2.0.4",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-webpack": "^8.1.0",
|
||||
"expect": "^29.7.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.3",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"core-js": "^3.40.0",
|
||||
"css-loader": "^7.1.2",
|
||||
"css-minimizer-webpack-plugin": "^7.0.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"dotenv-expand": "^5.1.0",
|
||||
"dotenv-webpack": "^6.0.0",
|
||||
"expect": "^30.0.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"fork-ts-checker-webpack-plugin": "^9.0.2",
|
||||
"html-webpack-plugin": "^5.6.3",
|
||||
"husky": "^9.1.7",
|
||||
"imagemin": "^8.0.1",
|
||||
"imagemin": "^9.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"junit-report-merger": "^7.0.0",
|
||||
"junit-report-merger": "^7.0.1",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"postcss": "^8.4.38",
|
||||
"prettier": "^3.3.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.3.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"react-refresh": "^0.14.2",
|
||||
"regenerator-runtime": "^0.14.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"sass": "^1.83.4",
|
||||
"sass-loader": "^16.0.4",
|
||||
"serve": "^14.2.1",
|
||||
"style-loader": "^3.3.4",
|
||||
"sass": "^1.87.0",
|
||||
"sass-loader": "^16.0.0",
|
||||
"speed-measure-webpack-plugin": "^1.5.0",
|
||||
"style-loader": "^4.0.0",
|
||||
"svg-url-loader": "^8.0.0",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"ts-jest": "^29.1.4",
|
||||
"ts-loader": "^9.5.1",
|
||||
"swagger-typescript-api": "13.2.7",
|
||||
"swc-loader": "^0.2.6",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"ts-loader": "^9.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "^4.1.0",
|
||||
"tslib": "^2.6.0",
|
||||
"typescript": "^5.4.5",
|
||||
"tslib": "^2.7.0",
|
||||
"typescript": "^5.8.2",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack": "^5.97.1",
|
||||
"webpack-bundle-analyzer": "^4.10.2",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-dev-server": "^5.2.2",
|
||||
"webpack-merge": "^5.10.0"
|
||||
"webpack-cli": "^6.0.1",
|
||||
"webpack-dev-server": "^5.2.0",
|
||||
"webpack-merge": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@patternfly/patternfly": "^6.3.1",
|
||||
|
|
@ -112,35 +109,58 @@
|
|||
"@patternfly/react-icons": "^6.3.1",
|
||||
"@patternfly/react-styles": "^6.3.1",
|
||||
"@patternfly/react-table": "^6.3.1",
|
||||
"@patternfly/react-templates": "^6.3.1",
|
||||
"@patternfly/react-tokens": "^6.3.1",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"eslint-plugin-local-rules": "^3.0.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-router": "^6.26.2",
|
||||
"sirv-cli": "^2.0.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.8.1",
|
||||
"@typescript-eslint/parser": "^8.12.2",
|
||||
"axios": "^1.10.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"classnames": "^2.2.6",
|
||||
"dompurify": "^3.2.4",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react-router": "^7.5.2",
|
||||
"react-router-dom": "^7.6.1",
|
||||
"sass": "^1.83.0",
|
||||
"showdown": "^2.1.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@babel/preset-typescript": "^7.21.5",
|
||||
"@cspell/eslint-plugin": "^9.1.2",
|
||||
"@cypress/code-coverage": "^3.14.1",
|
||||
"@cypress/webpack-preprocessor": "^6.0.4",
|
||||
"@testing-library/cypress": "^10.0.3",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.6.2",
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "14.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.31.1",
|
||||
"@typescript-eslint/parser": "^8.31.1",
|
||||
"cypress": "^14.4.1",
|
||||
"cypress-axe": "^1.6.0",
|
||||
"cypress-high-resolution": "^1.0.0",
|
||||
"cypress-mochawesome-reporter": "^3.8.2",
|
||||
"cypress-multi-reporters": "^2.0.5",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-import-resolver-node": "^0.3.7",
|
||||
"eslint-import-resolver-typescript": "^3.5.3",
|
||||
"eslint-import-resolver-typescript": "^3.8.3",
|
||||
"eslint-plugin-cypress": "^3.3.0",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||
"eslint-plugin-local-rules": "^3.0.2",
|
||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.5.2",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-react": "^7.37.2",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"swagger-typescript-api": "^13.2.7"
|
||||
"eslint-plugin-no-relative-import-paths": "^1.6.1",
|
||||
"eslint-plugin-prettier": "^5.4.0",
|
||||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"serve": "^14.2.4",
|
||||
"ts-jest": "^29.4.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,16 @@ import { defineConfig } from 'cypress';
|
|||
import coverage from '@cypress/code-coverage/task';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore no types available
|
||||
import webpack from '@cypress/webpack-preprocessor';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore no types available
|
||||
import cypressHighResolution from 'cypress-high-resolution';
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore no types available
|
||||
import { beforeRunHook, afterRunHook } from 'cypress-mochawesome-reporter/lib';
|
||||
import { mergeFiles } from 'junit-report-merger';
|
||||
import { env, BASE_URL } from '~/__tests__/cypress/cypress/utils/testConfig';
|
||||
import webpackConfig from './cypress/webpack.config';
|
||||
|
||||
const resultsDir = `${env.CY_RESULTS_DIR || 'results'}/${env.CY_MOCK ? 'mocked' : 'e2e'}`;
|
||||
|
||||
|
|
@ -41,7 +45,6 @@ export default defineConfig({
|
|||
env: {
|
||||
MOCK: !!env.CY_MOCK,
|
||||
coverage: !!env.CY_COVERAGE,
|
||||
APP_PREFIX: env.APP_PREFIX || '/workspaces',
|
||||
codeCoverage: {
|
||||
exclude: [path.resolve(__dirname, '../../third_party/**')],
|
||||
},
|
||||
|
|
@ -49,12 +52,20 @@ export default defineConfig({
|
|||
},
|
||||
defaultCommandTimeout: 10000,
|
||||
e2e: {
|
||||
baseUrl: env.CY_MOCK ? BASE_URL || 'http://localhost:9001' : 'http://localhost:9000',
|
||||
baseUrl: BASE_URL,
|
||||
specPattern: env.CY_MOCK ? `cypress/tests/mocked/**/*.cy.ts` : `cypress/tests/e2e/**/*.cy.ts`,
|
||||
experimentalInteractiveRunEvents: true,
|
||||
setupNodeEvents(on, config) {
|
||||
cypressHighResolution(on, config);
|
||||
coverage(on, config);
|
||||
|
||||
// Configure webpack preprocessor with custom webpack config
|
||||
const options = {
|
||||
webpackOptions: webpackConfig,
|
||||
watchOptions: {},
|
||||
};
|
||||
on('file:preprocessor', webpack(options));
|
||||
|
||||
on('task', {
|
||||
readJSON(filePath: string) {
|
||||
const absPath = path.resolve(__dirname, filePath);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ describe('Application', () => {
|
|||
cy.intercept('GET', `/api/v1/workspaces/${mockNamespaces[0].name}`, {
|
||||
body: mockBFFResponse({ mockWorkspace1 }),
|
||||
}).as('getWorkspaces');
|
||||
cy.visit('/workspaces');
|
||||
cy.visit('/');
|
||||
cy.wait('@getNamespaces');
|
||||
cy.wait('@getWorkspaces');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ describe('WorkspaceDetailsActivity Component', () => {
|
|||
cy.intercept('GET', 'api/v1/workspaces', {
|
||||
body: mockBFFResponse(mockWorkspaces),
|
||||
}).as('getWorkspaces');
|
||||
cy.visit('/workspaces');
|
||||
cy.visit('/');
|
||||
});
|
||||
|
||||
// This tests depends on the mocked workspaces data at home page, needs revisit once workspace data fetched from BE
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
import path from 'path';
|
||||
|
||||
const webpackConfig = {
|
||||
mode: 'development' as const,
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
||||
alias: {
|
||||
'~': path.resolve(__dirname, '../../../'),
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(tsx|ts|jsx|js)?$/,
|
||||
exclude: [/node_modules/],
|
||||
use: [
|
||||
{
|
||||
loader: 'ts-loader',
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.(svg|ttf|eot|woff|woff2)$/,
|
||||
// Handle fonts from PatternFly and other sources
|
||||
include: [
|
||||
path.resolve(__dirname, '../../../node_modules/patternfly/dist/fonts'),
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/@patternfly/react-core/dist/styles/assets/fonts',
|
||||
),
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/@patternfly/react-core/dist/styles/assets/pficon',
|
||||
),
|
||||
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/fonts'),
|
||||
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/pficon'),
|
||||
],
|
||||
use: {
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
limit: 5000,
|
||||
outputPath: 'fonts',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
include: (input: string): boolean => input.indexOf('background-filter.svg') > 1,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 5000,
|
||||
outputPath: 'svgs',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
// Handle SVG files
|
||||
include: (input: string): boolean =>
|
||||
input.indexOf('images') > -1 &&
|
||||
input.indexOf('fonts') === -1 &&
|
||||
input.indexOf('background-filter') === -1 &&
|
||||
input.indexOf('pficon') === -1,
|
||||
use: {
|
||||
loader: 'raw-loader',
|
||||
options: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(jpg|jpeg|png|gif)$/i,
|
||||
include: [
|
||||
path.resolve(__dirname, '../../'),
|
||||
path.resolve(__dirname, '../../../node_modules/patternfly'),
|
||||
path.resolve(__dirname, '../../../node_modules/@patternfly/patternfly/assets/images'),
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/@patternfly/react-styles/css/assets/images',
|
||||
),
|
||||
path.resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/@patternfly/react-core/dist/styles/assets/images',
|
||||
),
|
||||
],
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 5000,
|
||||
outputPath: 'images',
|
||||
name: '[name].[ext]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export default webpackConfig;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { TextEncoder } from 'util';
|
||||
import { TextEncoder as UtilTextEncoder } from 'util';
|
||||
import { JestAssertionError } from 'expect';
|
||||
import 'core-js/actual/array/to-sorted';
|
||||
import {
|
||||
|
|
@ -7,7 +7,10 @@ import {
|
|||
createComparativeValue,
|
||||
} from '~/__tests__/unit/testUtils/hooks';
|
||||
|
||||
global.TextEncoder = TextEncoder;
|
||||
// Ensure TextEncoder is available in the JSDOM environment for tests.
|
||||
// Node's util.TextEncoder has slightly different TS types than DOM's, so cast to any.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(globalThis as any).TextEncoder = UtilTextEncoder;
|
||||
|
||||
const tryExpect = (expectFn: () => void) => {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -22,14 +22,15 @@ import { BarsIcon } from '@patternfly/react-icons/dist/esm/icons/bars-icon';
|
|||
import ErrorBoundary from '~/app/error/ErrorBoundary';
|
||||
import NamespaceSelector from '~/shared/components/NamespaceSelector';
|
||||
import logoDarkTheme from '~/images/logo-dark-theme.svg';
|
||||
import { DEPLOYMENT_MODE, isMUITheme } from '~/shared/utilities/const';
|
||||
import { DeploymentMode, Theme } from '~/shared/utilities/types';
|
||||
import { NamespaceContextProvider } from './context/NamespaceContextProvider';
|
||||
import AppRoutes from './AppRoutes';
|
||||
import NavSidebar from './NavSidebar';
|
||||
import { NotebookContextProvider } from './context/NotebookContext';
|
||||
import { isMUITheme, Theme } from './const';
|
||||
import { BrowserStorageContextProvider } from './context/BrowserStorageContext';
|
||||
|
||||
const isStandalone = process.env.PRODUCTION !== 'true';
|
||||
const isStandalone = DEPLOYMENT_MODE === DeploymentMode.Standalone;
|
||||
|
||||
const App: React.FC = () => {
|
||||
useEffect(() => {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,10 @@ const AppRoutes: React.FC = () => {
|
|||
<Route path={AppRoutePaths.workspaceKinds} element={<WorkspaceKinds />} />
|
||||
<Route path={AppRoutePaths.workspaceKindCreate} element={<WorkspaceKindForm />} />
|
||||
<Route path={AppRoutePaths.workspaceKindEdit} element={<WorkspaceKindForm />} />
|
||||
<Route path="/" element={<Navigate to={AppRoutePaths.workspaces} replace />} />
|
||||
<Route
|
||||
path={AppRoutePaths.root}
|
||||
element={<Navigate to={AppRoutePaths.workspaces} replace />}
|
||||
/>
|
||||
<Route path="*" element={<NotFound />} />
|
||||
{
|
||||
// TODO: Remove the linter skip when we implement authentication
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import {
|
|||
} from '@patternfly/react-core/dist/esm/components/Nav';
|
||||
import { PageSidebar, PageSidebarBody } from '@patternfly/react-core/dist/esm/components/Page';
|
||||
import { useTypedLocation } from '~/app/routerHelper';
|
||||
import { isMUITheme, LOGO_LIGHT, URL_PREFIX } from '~/shared/utilities/const';
|
||||
import { useNavData, isNavDataGroup, NavDataHref, NavDataGroup } from './AppRoutes';
|
||||
import { APP_PREFIX, isMUITheme, LOGO_LIGHT } from './const';
|
||||
|
||||
const NavHref: React.FC<{ item: NavDataHref }> = ({ item }) => {
|
||||
const location = useTypedLocation();
|
||||
|
|
@ -61,7 +61,7 @@ const NavSidebar: React.FC = () => {
|
|||
<NavItem>
|
||||
<Brand
|
||||
className="kubeflow_brand"
|
||||
src={`${window.location.origin}${APP_PREFIX}/images/${LOGO_LIGHT}`}
|
||||
src={`${window.location.origin}${URL_PREFIX}/images/${LOGO_LIGHT}`}
|
||||
alt="Kubeflow Logo"
|
||||
/>
|
||||
</NavItem>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import {
|
|||
SearchInputProps,
|
||||
} from '@patternfly/react-core/dist/esm/components/SearchInput';
|
||||
import { TextInput } from '@patternfly/react-core/dist/esm/components/TextInput';
|
||||
import FormFieldset from 'app/components/FormFieldset';
|
||||
import { isMUITheme } from 'app/const';
|
||||
import FormFieldset from '~/app/components/FormFieldset';
|
||||
import { isMUITheme } from '~/shared/utilities/const';
|
||||
|
||||
type ThemeAwareSearchInputProps = Omit<SearchInputProps, 'onChange' | 'onClear'> & {
|
||||
onChange: (value: string) => void; // Simplified onChange signature
|
||||
|
|
|
|||
|
|
@ -504,12 +504,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
|
|||
})}
|
||||
{canCreateWorkspaces && (
|
||||
<ToolbarItem>
|
||||
<Button
|
||||
size="lg"
|
||||
variant="primary"
|
||||
ouiaId="Primary"
|
||||
onClick={createWorkspace}
|
||||
>
|
||||
<Button variant="primary" ouiaId="Primary" onClick={createWorkspace}>
|
||||
Create workspace
|
||||
</Button>
|
||||
</ToolbarItem>
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
export const BFF_API_VERSION = 'v1';
|
||||
|
||||
export enum Theme {
|
||||
Default = 'default-theme',
|
||||
MUI = 'mui-theme',
|
||||
// Future themes can be added here
|
||||
}
|
||||
|
||||
export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI;
|
||||
const STYLE_THEME = process.env.STYLE_THEME || Theme.MUI;
|
||||
|
||||
export const LOGO_LIGHT = process.env.LOGO || 'logo.svg';
|
||||
|
||||
export const DEFAULT_POLLING_RATE_MS = 10000;
|
||||
|
||||
export const APP_PREFIX = process.env.APP_PREFIX || '/workspaces';
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import React, { ReactNode, useMemo } from 'react';
|
||||
import { APP_PREFIX, BFF_API_VERSION } from '~/app/const';
|
||||
import EnsureAPIAvailability from '~/app/EnsureAPIAvailability';
|
||||
import { BFF_API_PREFIX, BFF_API_VERSION } from '~/shared/utilities/const';
|
||||
import useNotebookAPIState, { NotebookAPIState } from './useNotebookAPIState';
|
||||
|
||||
export type NotebookContextType = {
|
||||
|
|
@ -19,8 +19,8 @@ interface NotebookContextProviderProps {
|
|||
}
|
||||
|
||||
export const NotebookContextProvider: React.FC<NotebookContextProviderProps> = ({ children }) => {
|
||||
// Remove trailing slash from APP_PREFIX to avoid double slashes
|
||||
const cleanPrefix = APP_PREFIX.replace(/\/$/, '');
|
||||
// Remove trailing slash from BFF_API_PREFIX to avoid double slashes
|
||||
const cleanPrefix = BFF_API_PREFIX.replace(/\/$/, '');
|
||||
const hostPath = `${cleanPrefix}/api/${BFF_API_VERSION}`;
|
||||
|
||||
const [apiState, refreshAPIState] = useNotebookAPIState(hostPath);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ import { NotebookApis, notebookApisImpl } from '~/shared/api/notebookApi';
|
|||
import { APIState } from '~/shared/api/types';
|
||||
import useAPIState from '~/shared/api/useAPIState';
|
||||
import { mockNotebookApisImpl } from '~/shared/mock/mockNotebookApis';
|
||||
import { MOCK_API_ENABLED } from '~/shared/utilities/const';
|
||||
|
||||
export type NotebookAPIState = APIState<NotebookApis>;
|
||||
|
||||
const MOCK_API_ENABLED = process.env.WEBPACK_REPLACE__mockApiEnabled === 'true';
|
||||
|
||||
const useNotebookAPIState = (
|
||||
hostPath: string | null,
|
||||
): [apiState: NotebookAPIState, refreshAPIState: () => void] => {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ import useGenericObjectState from '~/app/hooks/useGenericObjectState';
|
|||
import { useNotebookAPI } from '~/app/hooks/useNotebookAPI';
|
||||
import { WorkspaceKindFormData } from '~/app/types';
|
||||
import { safeApiCall } from '~/shared/api/apiUtils';
|
||||
import { CONTENT_TYPE_KEY, ContentType } from '~/shared/utilities/const';
|
||||
import { CONTENT_TYPE_KEY } from '~/shared/utilities/const';
|
||||
import { ApiValidationError, WorkspacekindsWorkspaceKind } from '~/generated/data-contracts';
|
||||
import { ContentType } from '~/shared/utilities/types';
|
||||
import { WorkspaceKindFileUpload } from './fileUpload/WorkspaceKindFileUpload';
|
||||
import { WorkspaceKindFormProperties } from './properties/WorkspaceKindFormProperties';
|
||||
import { WorkspaceKindFormImage } from './image/WorkspaceKindFormImage';
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export const WorkspaceKindDetailsImages: React.FunctionComponent<WorkspaceDetail
|
|||
workspaceCount:
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
workspaceCountPerKind[workspaceKind.name]
|
||||
? workspaceCountPerKind[workspaceKind.name].countByImage[image.id] ?? 0
|
||||
? (workspaceCountPerKind[workspaceKind.name].countByImage[image.id] ?? 0)
|
||||
: 0,
|
||||
}))}
|
||||
tableKind="image"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const WorkspaceKindDetailsPodConfigs: React.FunctionComponent<
|
|||
kindName: workspaceKind.name,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
workspaceCount: workspaceCountPerKind[workspaceKind.name]
|
||||
? workspaceCountPerKind[workspaceKind.name].countByPodConfig[podConfig.id] ?? 0
|
||||
? (workspaceCountPerKind[workspaceKind.name].countByPodConfig[podConfig.id] ?? 0)
|
||||
: 0,
|
||||
workspaceCountRouteState: {
|
||||
podConfigId: podConfig.id,
|
||||
|
|
|
|||
|
|
@ -8,11 +8,11 @@ import { useTypedLocation, useTypedNavigate, useTypedParams } from '~/app/router
|
|||
import WorkspaceTable, { WorkspaceTableRef } from '~/app/components/WorkspaceTable';
|
||||
import { useWorkspacesByKind } from '~/app/hooks/useWorkspaces';
|
||||
import WorkspaceKindSummaryExpandableCard from '~/app/pages/WorkspaceKinds/summary/WorkspaceKindSummaryExpandableCard';
|
||||
import { DEFAULT_POLLING_RATE_MS } from '~/app/const';
|
||||
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
||||
import { LoadError } from '~/app/components/LoadError';
|
||||
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
||||
import { usePolling } from '~/app/hooks/usePolling';
|
||||
import { POLL_INTERVAL } from '~/shared/utilities/const';
|
||||
|
||||
const WorkspaceKindSummary: React.FC = () => {
|
||||
const [isSummaryExpanded, setIsSummaryExpanded] = useState(true);
|
||||
|
|
@ -30,7 +30,7 @@ const WorkspaceKindSummary: React.FC = () => {
|
|||
podConfigId,
|
||||
});
|
||||
|
||||
usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS);
|
||||
usePolling(refreshWorkspaces, POLL_INTERVAL);
|
||||
|
||||
const tableRowActions = useWorkspaceRowActions([{ id: 'viewDetails' }]);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ import { Stack, StackItem } from '@patternfly/react-core/dist/esm/layouts/Stack'
|
|||
import WorkspaceTable from '~/app/components/WorkspaceTable';
|
||||
import { useNamespaceContext } from '~/app/context/NamespaceContextProvider';
|
||||
import { useWorkspacesByNamespace } from '~/app/hooks/useWorkspaces';
|
||||
import { DEFAULT_POLLING_RATE_MS } from '~/app/const';
|
||||
import { LoadingSpinner } from '~/app/components/LoadingSpinner';
|
||||
import { LoadError } from '~/app/components/LoadError';
|
||||
import { useWorkspaceRowActions } from '~/app/hooks/useWorkspaceRowActions';
|
||||
import { usePolling } from '~/app/hooks/usePolling';
|
||||
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
|
||||
import { POLL_INTERVAL } from '~/shared/utilities/const';
|
||||
|
||||
export const Workspaces: React.FunctionComponent = () => {
|
||||
const { selectedNamespace } = useNamespaceContext();
|
||||
|
|
@ -18,7 +18,7 @@ export const Workspaces: React.FunctionComponent = () => {
|
|||
const [workspaces, workspacesLoaded, workspacesLoadError, refreshWorkspaces] =
|
||||
useWorkspacesByNamespace(selectedNamespace);
|
||||
|
||||
usePolling(refreshWorkspaces, DEFAULT_POLLING_RATE_MS);
|
||||
usePolling(refreshWorkspaces, POLL_INTERVAL);
|
||||
|
||||
const tableRowActions = useWorkspaceRowActions([
|
||||
{ id: 'viewDetails' },
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ type NavigateOptions<T extends AppRouteKey> = CommonNavigateOptions &
|
|||
* Go to my route
|
||||
* </Link>
|
||||
*/
|
||||
export function buildPath<T extends AppRouteKey>(to: T, params: RouteParamsMap[T]): string {
|
||||
export function buildPath<T extends AppRouteKey>(to: T, params?: RouteParamsMap[T]): string {
|
||||
return generatePath(AppRoutePaths[to], params as RouteParamsMap[T]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
|
@ -2,15 +2,15 @@ import React from 'react';
|
|||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
import { ThemeProvider, createTheme } from '@mui/material/styles';
|
||||
import { URL_PREFIX } from '~/shared/utilities/const';
|
||||
import App from './app/App';
|
||||
import { APP_PREFIX } from './app/const';
|
||||
|
||||
const theme = createTheme({ cssVariables: true });
|
||||
const root = ReactDOM.createRoot(document.getElementById('root')!);
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<Router basename={APP_PREFIX}>
|
||||
<Router basename={URL_PREFIX}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
|
|
|
|||
|
|
@ -158,3 +158,21 @@ type AtLeastOne<T, U = { [K in keyof T]: Pick<T, K> }> = Partial<T> & U[keyof U]
|
|||
* ```
|
||||
*/
|
||||
export type ExactlyOne<T> = AtMostOne<T> & AtLeastOne<T>;
|
||||
|
||||
export const asEnumMember = <T extends object>(
|
||||
member: T[keyof T] | string | number | undefined | null,
|
||||
e: T,
|
||||
): T[keyof T] | null => (isEnumMember(member, e) ? member : null);
|
||||
|
||||
export const isEnumMember = <T extends object>(
|
||||
member: T[keyof T] | string | number | undefined | unknown | null,
|
||||
e: T,
|
||||
): member is T[keyof T] => {
|
||||
if (member != null) {
|
||||
return Object.entries(e)
|
||||
.filter(([key]) => Number.isNaN(Number(key)))
|
||||
.map(([, value]) => value)
|
||||
.includes(member);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import { asEnumMember } from '~/shared/typeHelpers';
|
||||
import { DeploymentMode, Theme } from '~/shared/utilities/types';
|
||||
|
||||
export const STYLE_THEME = asEnumMember(process.env.STYLE_THEME, Theme) || Theme.MUI;
|
||||
export const DEPLOYMENT_MODE =
|
||||
asEnumMember(process.env.DEPLOYMENT_MODE, DeploymentMode) || DeploymentMode.Kubeflow;
|
||||
export const DEV_MODE = process.env.APP_ENV === 'development';
|
||||
export const AUTH_HEADER = process.env.AUTH_HEADER || 'kubeflow-userid';
|
||||
export const POLL_INTERVAL = process.env.POLL_INTERVAL
|
||||
? parseInt(process.env.POLL_INTERVAL)
|
||||
: 30000;
|
||||
export const LOGO_LIGHT = process.env.LOGO || 'logo-light-theme.svg';
|
||||
export const URL_PREFIX = process.env.URL_PREFIX ?? '/workspaces';
|
||||
export const BFF_API_PREFIX = process.env.BFF_API_PREFIX ?? '/';
|
||||
export const BFF_API_VERSION = 'v1';
|
||||
export const MOCK_API_ENABLED = process.env.MOCK_API_ENABLED === 'true';
|
||||
|
||||
export const CONTENT_TYPE_KEY = 'Content-Type';
|
||||
|
||||
export enum ContentType {
|
||||
YAML = 'application/yaml',
|
||||
}
|
||||
export const isMUITheme = (): boolean => STYLE_THEME === Theme.MUI;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
export enum DeploymentMode {
|
||||
Standalone = 'standalone',
|
||||
Kubeflow = 'kubeflow',
|
||||
}
|
||||
|
||||
export enum Theme {
|
||||
Default = 'default-theme',
|
||||
MUI = 'mui-theme',
|
||||
// Future themes can be added here
|
||||
}
|
||||
|
||||
export enum ContentType {
|
||||
YAML = 'application/yaml',
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./src",
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"module": "esnext",
|
||||
"target": "ES6",
|
||||
"lib": ["es6", "dom"],
|
||||
"target": "es2021",
|
||||
"lib": ["es2021", "dom", "ES2023.Array"],
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"downlevelIteration": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
|
|
@ -18,12 +17,16 @@
|
|||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"baseUrl": "./src",
|
||||
"paths": {
|
||||
"~/*": ["./*"]
|
||||
},
|
||||
"importHelpers": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": true,
|
||||
"noErrorTruncation": true,
|
||||
"noEmit": true,
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", "**/*.jsx", "**/*.js"],
|
||||
"exclude": ["node_modules", "src/__tests__/cypress"]
|
||||
"exclude": ["node_modules", "dist", "public-cypress", "src/__tests__/cypress"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue