@rancher/shell 3.0.0-rc.1 → 3.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/assets/styles/base/_variables.scss +1 -0
  2. package/assets/styles/global/_layout.scss +3 -3
  3. package/assets/styles/global/_select.scss +1 -1
  4. package/assets/styles/global/_tooltip.scss +37 -71
  5. package/components/ActionDropdown.vue +9 -13
  6. package/components/ActionMenu.vue +1 -1
  7. package/components/ButtonDropdown.vue +9 -8
  8. package/components/CruResource.vue +4 -2
  9. package/components/InputOrDisplay.vue +21 -33
  10. package/components/LocaleSelector.vue +1 -1
  11. package/components/Questions/__tests__/Boolean.test.ts +1 -2
  12. package/components/Questions/__tests__/Float.test.ts +1 -2
  13. package/components/Questions/__tests__/Int.test.ts +1 -2
  14. package/components/Questions/__tests__/String.test.ts +1 -2
  15. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  16. package/components/Questions/__tests__/utils/questions-defaults.ts +2 -2
  17. package/components/SideNav.vue +3 -3
  18. package/components/__tests__/CodeMirror.test.ts +91 -94
  19. package/components/__tests__/ConsumptionGauge.test.ts +2 -2
  20. package/components/__tests__/NamespaceFilter.test.ts +10 -7
  21. package/components/auth/AllowedPrincipals.vue +2 -2
  22. package/components/auth/RoleDetailEdit.vue +13 -17
  23. package/components/auth/SelectPrincipal.vue +1 -1
  24. package/components/fleet/FleetStatus.vue +13 -14
  25. package/components/form/ArrayList.vue +1 -1
  26. package/components/form/ArrayListGrouped.vue +18 -5
  27. package/components/form/LabeledSelect.vue +16 -11
  28. package/components/form/Select.vue +17 -1
  29. package/components/form/__tests__/Command.test.ts +6 -5
  30. package/components/form/__tests__/Taints.test.ts +9 -9
  31. package/components/formatter/AppSummaryGraph.vue +1 -1
  32. package/components/formatter/FleetSummaryGraph.vue +1 -1
  33. package/components/formatter/MachineSummaryGraph.vue +1 -1
  34. package/components/formatter/Scale.vue +1 -1
  35. package/components/formatter/Weight.vue +1 -1
  36. package/components/nav/Header.vue +31 -14
  37. package/components/nav/NamespaceFilter.vue +1 -1
  38. package/components/nav/TopLevelMenu.vue +7 -6
  39. package/components/nav/WindowManager/ContainerLogs.vue +1 -1
  40. package/components/nav/WindowManager/ContainerShell.vue +7 -2
  41. package/components/nav/WindowManager/__tests__/ContainerLogs.test.ts +195 -192
  42. package/components/nav/WindowManager/__tests__/ContainerShell.test.ts +23 -19
  43. package/core/plugin-routes.ts +42 -29
  44. package/core/plugins-loader.js +2 -0
  45. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +16 -8
  46. package/detail/helm.cattle.io.projecthelmchart.vue +26 -27
  47. package/edit/__tests__/namespace.test.ts +7 -9
  48. package/edit/__tests__/service.test.ts +14 -2
  49. package/edit/auth/__tests__/azuread.test.ts +10 -11
  50. package/edit/auth/azuread.vue +1 -1
  51. package/edit/fleet.cattle.io.clustergroup.vue +3 -3
  52. package/edit/management.cattle.io.fleetworkspace.vue +3 -3
  53. package/edit/management.cattle.io.node.vue +3 -2
  54. package/edit/management.cattle.io.podsecurityadmissionconfigurationtemplate.vue +3 -2
  55. package/edit/namespace.vue +3 -1
  56. package/edit/networking.k8s.io.ingress/index.vue +3 -2
  57. package/edit/networking.k8s.io.networkpolicy/index.vue +2 -2
  58. package/edit/node.vue +3 -3
  59. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +9 -4
  60. package/edit/persistentvolume/index.vue +3 -3
  61. package/edit/persistentvolumeclaim.vue +3 -3
  62. package/edit/policy.poddisruptionbudget.vue +3 -3
  63. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +5 -6
  64. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +13 -6
  65. package/edit/provisioning.cattle.io.cluster/rke2.vue +7 -1
  66. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +6 -7
  67. package/edit/service.vue +2 -2
  68. package/edit/serviceaccount.vue +3 -3
  69. package/edit/storage.k8s.io.storageclass/index.vue +3 -3
  70. package/edit/workload/Job.vue +2 -2
  71. package/edit/workload/__tests__/Job.test.ts +5 -5
  72. package/edit/workload/index.vue +2 -2
  73. package/edit/workload/storage/Mount.vue +7 -4
  74. package/edit/workload/storage/__tests__/Mount.test.ts +6 -2
  75. package/edit/workload/storage/index.vue +10 -23
  76. package/initialize/entry-helpers.js +0 -5
  77. package/mixins/page-actions.js +1 -1
  78. package/package.json +1 -1
  79. package/pages/c/_cluster/istio/index.vue +2 -2
  80. package/pages/c/_cluster/longhorn/__tests__/longhorn.index.test.ts +3 -2
  81. package/pages/c/_cluster/monitoring/index.vue +1 -1
  82. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +7 -29
  83. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -0
  84. package/vue.config.js +409 -391
  85. package/plugins/clean-html-directive.js +0 -10
  86. package/plugins/clean-tooltip-directive.js +0 -9
  87. package/plugins/int-number.js +0 -9
  88. package/plugins/positive-int-number.js +0 -9
  89. package/plugins/trim-whitespace.js +0 -10
package/vue.config.js CHANGED
@@ -4,9 +4,12 @@ const webpack = require('webpack');
4
4
  const { generateDynamicTypeImport } = require('./pkg/auto-import');
5
5
  const CopyWebpackPlugin = require('copy-webpack-plugin');
6
6
  const serverMiddlewares = require('./server/server-middleware.js');
7
- const configHelper = require('./vue-config-helper.js');
7
+ const {
8
+ dev, devPorts, api, proxyWsOpts, proxyOpts, proxyMetaOpts, proxyPrimeOpts
9
+ } = require('./vue-config-helper.js');
8
10
  const har = require('./server/har-file');
9
11
  const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
12
+ const VirtualModulesPlugin = require('webpack-virtual-modules');
10
13
 
11
14
  // Suppress info level logging messages from http-proxy-middleware
12
15
  // This hides all of the "[HPM Proxy created] ..." messages
@@ -16,33 +19,24 @@ console.info = () => {}; // eslint-disable-line no-console
16
19
 
17
20
  const { createProxyMiddleware } = require('http-proxy-middleware');
18
21
 
22
+ // TODO: Add explanation of this logic
19
23
  console.info = oldInfoLogger; // eslint-disable-line no-console
20
24
 
21
25
  // This is currently hardcoded to avoid importing the TS
22
26
  // const { STANDARD } = require('./config/private-label');
23
27
  const STANDARD = 1;
24
28
 
25
- const dev = configHelper.dev;
26
- const devPorts = configHelper.devPorts;
27
-
28
29
  // human readable version used on rancher dashboard about page
29
30
  const dashboardVersion = process.env.DASHBOARD_VERSION;
30
31
 
31
32
  const pl = process.env.PL || STANDARD;
32
33
  const commit = process.env.COMMIT || 'head';
33
34
  const perfTest = (process.env.PERF_TEST === 'true'); // Enable performance testing when in dev
34
- const instrumentCode = (process.env.TEST_INSTRUMENT === 'true'); // Instrument code for code coverage in e2e tests
35
-
36
- const api = configHelper.api;
37
- // ===============================================================================================
38
- // Nuxt configuration
39
- // ===============================================================================================
40
35
 
41
- // Expose a function that can be used by an app to provide a nuxt configuration for building an application
42
- // This takes the directory of the application as tehfirst argument so that we can derive folder locations
43
- // from it, rather than from the location of this file
44
- module.exports = function(dir, _appConfig) {
45
- // Paths to the shell folder when it is included as a node dependency
36
+ /**
37
+ * Paths to the shell folder when it is included as a node dependency
38
+ */
39
+ const getShellPaths = (dir) => {
46
40
  let SHELL_ABS = path.join(dir, 'node_modules/@rancher/shell');
47
41
  let COMPONENTS_DIR = path.join(SHELL_ABS, 'rancher-components');
48
42
 
@@ -59,274 +53,455 @@ module.exports = function(dir, _appConfig) {
59
53
 
60
54
  // If we have a local folder named 'shell' then use that rather than the one in node_modules
61
55
  // This will be the case in the main dashboard repository.
62
- if (fs.existsSync(path.resolve(dir, 'shell'))) {
63
- SHELL_ABS = path.resolve(dir, 'shell');
56
+ if (fs.existsSync(path.join(dir, 'shell'))) {
57
+ SHELL_ABS = path.join(dir, 'shell');
64
58
  COMPONENTS_DIR = path.join(dir, 'pkg', 'rancher-components', 'src', 'components');
65
59
  }
66
60
 
67
- // ===============================================================================================
68
- // Functions for the UI Pluginas
69
- // ===============================================================================================
61
+ return { SHELL_ABS, COMPONENTS_DIR };
62
+ };
70
63
 
71
- const appConfig = _appConfig || {};
72
- const excludes = appConfig.excludes || [];
64
+ const getProxyConfig = (proxyConfig) => ({
65
+ ...proxyConfig,
66
+ '/k8s': proxyWsOpts(api), // Straight to a remote cluster (/k8s/clusters/<id>/)
67
+ '/pp': proxyWsOpts(api), // For (epinio) standalone API
68
+ '/api': proxyWsOpts(api), // Management k8s API
69
+ '/apis': proxyWsOpts(api), // Management k8s API
70
+ '/v1': proxyWsOpts(api), // Management Steve API
71
+ '/v3': proxyWsOpts(api), // Rancher API
72
+ '/v3-public': proxyOpts(api), // Rancher Unauthed API
73
+ '/api-ui': proxyOpts(api), // Browser API UI
74
+ '/meta': proxyMetaOpts(api), // Browser API UI
75
+ '/v1-*': proxyOpts(api), // SAML, KDM, etc
76
+ '/rancherversion': proxyPrimeOpts(api), // Rancher version endpoint
77
+ // These are for Ember embedding
78
+ '/c/*/edit': proxyOpts('https://127.0.0.1:8000'), // Can't proxy all of /c because that's used by Vue too
79
+ '/k/': proxyOpts('https://127.0.0.1:8000'),
80
+ '/g/': proxyOpts('https://127.0.0.1:8000'),
81
+ '/n/': proxyOpts('https://127.0.0.1:8000'),
82
+ '/p/': proxyOpts('https://127.0.0.1:8000'),
83
+ '/assets': proxyOpts('https://127.0.0.1:8000'),
84
+ '/translations': proxyOpts('https://127.0.0.1:8000'),
85
+ '/engines-dist': proxyOpts('https://127.0.0.1:8000'),
86
+ });
87
+
88
+ /**
89
+ * @pkg imports must be resolved to the package that it importing them - this allows a package to use @pkg as an alias
90
+ * to the root of that particular package
91
+ */
92
+ const getPackageImport = (dir) => new webpack.NormalModuleReplacementPlugin(/^@pkg/, (resource) => {
93
+ const ctx = resource.context.split('/');
94
+ // Find 'pkg' folder in the context
95
+ const index = ctx.findIndex((s) => s === 'pkg');
96
+
97
+ if (index !== -1 && (index + 1) < ctx.length) {
98
+ const pkg = ctx[index + 1];
99
+ let p = path.resolve(dir, 'pkg', pkg, resource.request.substr(5));
100
+
101
+ if (resource.request.startsWith(`@pkg/${ pkg }`)) {
102
+ p = path.resolve(dir, 'pkg', resource.request.substr(5));
103
+ }
73
104
 
74
- const watcherIgnores = [
75
- /node_modules/,
76
- /dist-pkg/,
77
- /scripts\/standalone/
105
+ resource.request = p;
106
+ }
107
+ });
108
+
109
+ /**
110
+ * Instrument code for code coverage in e2e tests
111
+ */
112
+ const instrumentCode = () => {
113
+ const instrumentedCode = (process.env.TEST_INSTRUMENT === 'true');
114
+
115
+ // Instrument code for tests
116
+ const babelPlugins = [
117
+ // TODO: Browser support; also add explanation to this TODO
118
+ // ['@babel/plugin-transform-modules-commonjs'],
119
+ ['@babel/plugin-proposal-private-property-in-object', { loose: true }],
120
+ ['@babel/plugin-proposal-class-properties', { loose: true }]
78
121
  ];
79
122
 
80
- // Find any UI packages in node_modules
81
- const NM = path.join(dir, 'node_modules');
82
- const pkg = require(path.join(dir, 'package.json'));
83
- const nmPackages = {};
123
+ if (instrumentedCode) {
124
+ babelPlugins.push([
125
+ 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
126
+ ]);
84
127
 
85
- if (pkg && pkg.dependencies) {
86
- Object.keys(pkg.dependencies).forEach((pkg) => {
87
- const f = require(path.join(NM, pkg, 'package.json'));
128
+ console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
129
+ }
130
+ };
88
131
 
89
- // The package.json must have the 'rancher' property to mark it as a UI package
90
- if (f.rancher) {
91
- const id = `${ f.name }-${ f.version }`;
132
+ const getLoaders = (SHELL_ABS) => [
133
+ // Ensure there is a fallback for browsers that don't support web workers
134
+ {
135
+ test: /web-worker.[a-z-]+.js/i,
136
+ loader: 'worker-loader',
137
+ options: { inline: 'fallback' },
138
+ },
139
+ // Handler for csv files (e.g. ec2 instance data)
140
+ {
141
+ test: /\.csv$/i,
142
+ loader: 'csv-loader',
143
+ options: {
144
+ dynamicTyping: true,
145
+ header: true,
146
+ skipEmptyLines: true
147
+ },
148
+ },
149
+ // Handler for yaml files (used for i18n files, for example)
150
+ {
151
+ test: /\.ya?ml$/i,
152
+ loader: 'js-yaml-loader',
153
+ options: { name: '[path][name].[ext]' },
154
+ },
155
+ {
156
+ test: /\.m?[tj]sx?$/,
157
+ // This excludes no modules except for node_modules/@rancher/... so that plugins can properly compile
158
+ // when referencing @rancher/shell
159
+ exclude: /node_modules\/(?!(@rancher)\/).*/,
160
+ use: [
161
+ {
162
+ loader: 'cache-loader',
163
+ options: {
164
+ cacheDirectory: 'node_modules/.cache/babel-loader',
165
+ cacheIdentifier: 'e93f32da'
166
+ }
167
+ },
168
+ ]
169
+ },
170
+ {
171
+ test: /\.tsx?$/,
172
+ use: [
173
+ {
174
+ loader: 'cache-loader',
175
+ options: {
176
+ cacheDirectory: 'node_modules/.cache/ts-loader',
177
+ cacheIdentifier: '3596741e'
178
+ }
179
+ },
180
+ {
181
+ loader: 'ts-loader',
182
+ options: {
183
+ transpileOnly: true,
184
+ happyPackMode: false,
185
+ appendTsxSuffixTo: [
186
+ '\\.vue$'
187
+ ],
188
+ configFile: path.join(SHELL_ABS, 'tsconfig.json')
189
+ }
190
+ }
191
+ ]
192
+ },
193
+ // Prevent warning in log with the md files in the content folder
194
+ {
195
+ test: /\.md$/,
196
+ use: [
197
+ {
198
+ loader: 'frontmatter-markdown-loader',
199
+ options: { mode: ['body'] }
200
+ }
201
+ ]
202
+ },
203
+ ];
92
204
 
93
- nmPackages[id] = f.main;
205
+ const getDevServerConfig = (proxy) => {
206
+ const harFile = process.env.HAR_FILE;
207
+ // HAR File support - load network responses from the specified .har file and use those rather than communicating to the Rancher server
208
+ const harData = harFile ? har.loadFile(harFile, devPorts ? 8005 : 80, '') : undefined;
209
+
210
+ return {
211
+ client: { webSocketURL: { hostname: '0.0.0.0', port: devPorts ? 8005 : 80 } },
212
+ server: {
213
+ type: 'https',
214
+ options: {
215
+ key: fs.readFileSync(path.resolve(__dirname, 'server/server.key')),
216
+ cert: fs.readFileSync(path.resolve(__dirname, 'server/server.crt'))
94
217
  }
95
- });
96
- }
218
+ },
219
+ port: (devPorts ? 8005 : 80),
220
+ host: '0.0.0.0',
221
+ setupMiddlewares(middlewares, devServer) {
222
+ const socketProxies = {};
97
223
 
98
- function includePkg(name) {
99
- if (name.startsWith('.') || name === 'node_modules') {
100
- return false;
101
- }
224
+ if (!devServer) {
225
+ // eslint-disable-next-line no-console
226
+ console.error('webpack-dev-server is not defined');
102
227
 
103
- return !excludes || (excludes && !excludes.includes(name));
104
- }
228
+ return middlewares;
229
+ }
105
230
 
106
- // For each package in the pkg folder that is being compiled into the application,
107
- // Add in the code to automatically import the types from that package
108
- // This imports models, edit, detail, list etc
109
- // When built as a UI package, shell/pkg/vue.config.js does the same thing
110
- const autoImportTypes = {};
111
- const VirtualModulesPlugin = require('webpack-virtual-modules');
112
- let reqs = '';
113
- const pkgFolder = path.relative(dir, './pkg');
231
+ const app = devServer.app;
114
232
 
115
- if (fs.existsSync(pkgFolder)) {
116
- const items = fs.readdirSync(path.relative(dir, './pkg'));
233
+ // Close down quickly in response to CTRL + C
234
+ process.once('SIGINT', () => {
235
+ devServer.close();
236
+ console.log('\n'); // eslint-disable-line no-console
237
+ process.exit(1);
238
+ });
117
239
 
118
- // Ignore hidden folders
119
- items.filter((name) => !name.startsWith('.')).forEach((name) => {
120
- const f = require(path.join(dir, 'pkg', name, 'package.json'));
240
+ app.use(serverMiddlewares);
121
241
 
122
- // Package file must have rancher field to be a plugin
123
- if (includePkg(name) && f.rancher) {
124
- reqs += `$plugin.initPlugin('${ name }', require(\'~/pkg/${ name }\')); `;
125
- }
242
+ if (harData) {
243
+ console.log('Installing HAR file middleware'); // eslint-disable-line no-console
244
+ app.use(har.harProxy(harData, process.env.HAR_DIR));
126
245
 
127
- autoImportTypes[`node_modules/@rancher/auto-import/${ name }`] = generateDynamicTypeImport(`@pkg/${ name }`, path.join(dir, `pkg/${ name }`));
128
- });
129
- }
246
+ devServer.webSocketProxies.push({
247
+ upgrade(req, socket, head) {
248
+ const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade'];
130
249
 
131
- Object.keys(nmPackages).forEach((m) => {
132
- reqs += `$plugin.loadAsync('${ m }', '/pkg/${ m }/${ nmPackages[m] }');`;
133
- });
250
+ socket.write(`${ responseHeaders.join('\r\n') }\r\n\r\n`);
251
+ }
252
+ });
253
+ }
134
254
 
135
- // Generate a virtual module '@rancher/dyanmic.js` which imports all of the packages that should be built into the application
136
- // This is imported in 'shell/extensions/extension-loader.js` which ensures the all code for plugins to be included is imported in the application
137
- const virtualModules = new VirtualModulesPlugin({ 'node_modules/@rancher/dynamic.js': `export default function ($plugin) { ${ reqs } };` });
138
- const autoImport = new webpack.NormalModuleReplacementPlugin(/^@rancher\/auto-import$/, (resource) => {
139
- const ctx = resource.context.split('/');
140
- const pkg = ctx[ctx.length - 1];
255
+ Object.keys(proxy).forEach((p) => {
256
+ const px = createProxyMiddleware({
257
+ ...proxy[p],
258
+ ws: false // We will handle the web socket upgrade
259
+ });
141
260
 
142
- resource.request = `@rancher/auto-import/${ pkg }`;
143
- });
261
+ if (proxy[p].ws) {
262
+ socketProxies[p] = px;
263
+ }
264
+ app.use(p, px);
265
+ });
144
266
 
145
- // @pkg imports must be resolved to the package that it importing them - this allows a package to use @pkg as an alis
146
- // to the root of that particular package
147
- const pkgImport = new webpack.NormalModuleReplacementPlugin(/^@pkg/, (resource) => {
148
- const ctx = resource.context.split('/');
149
- // Find 'pkg' folder in the contxt
150
- const index = ctx.findIndex((s) => s === 'pkg');
267
+ // TODO: Verify after migration completed
268
+ devServer.webSocketProxies.push({
269
+ upgrade(req, socket, head) {
270
+ const path = Object.keys(socketProxies).find((path) => req.url.startsWith(path));
151
271
 
152
- if (index !== -1 && (index + 1) < ctx.length) {
153
- const pkg = ctx[index + 1];
154
- let p = path.resolve(dir, 'pkg', pkg, resource.request.substr(5));
272
+ if (path) {
273
+ const proxy = socketProxies[path];
155
274
 
156
- if (resource.request.startsWith(`@pkg/${ pkg }`)) {
157
- p = path.resolve(dir, 'pkg', resource.request.substr(5));
158
- }
275
+ if (proxy.upgrade) {
276
+ proxy.upgrade(req, socket, head);
277
+ } else {
278
+ console.log(`Upgrade for Proxy is not defined. Cannot upgrade Web socket for ${ req.url }`); // eslint-disable-line no-console
279
+ }
280
+ } else {
281
+ console.log(`Unknown Web socket upgrade request for ${ req.url }`); // eslint-disable-line no-console
282
+ }
283
+ }
284
+ });
159
285
 
160
- resource.request = p;
286
+ return middlewares;
161
287
  }
162
- });
288
+ };
289
+ };
163
290
 
164
- // ===============================================================================================
165
- // Dashboard nuxt configuration
166
- // ===============================================================================================
291
+ /**
292
+ * Generate a virtual module '@rancher/dynamic.js` which imports all of the packages that should be built into the application
293
+ * This is imported in 'shell/extensions/extension-loader.js` which ensures the all code for plugins to be included is imported in the application
294
+ */
295
+ const getVirtualModules = (dir, includePkg) => {
296
+ // Find any UI packages in node_modules
297
+ const modulePaths = path.join(dir, 'node_modules');
298
+ const requiredPackages = require(path.join(dir, 'package.json'));
299
+ const librariesIndex = {};
167
300
 
168
- require('events').EventEmitter.defaultMaxListeners = 50;
169
- require('dotenv').config();
301
+ if (requiredPackages && requiredPackages.dependencies) {
302
+ Object.keys(requiredPackages.dependencies).forEach((requiredPackage) => {
303
+ const library = require(path.join(modulePaths, requiredPackage, 'package.json'));
170
304
 
171
- let routerBasePath = '/';
172
- let resourceBase = '';
173
- let outputDir = 'dist';
305
+ // The package.json must have the 'rancher' property to mark it as a UI package
306
+ if (library.rancher) {
307
+ const id = `${ library.name }-${ library.version }`;
174
308
 
175
- if ( typeof process.env.ROUTER_BASE !== 'undefined' ) {
176
- routerBasePath = process.env.ROUTER_BASE;
309
+ librariesIndex[id] = library.main;
310
+ }
311
+ });
177
312
  }
178
313
 
179
- if ( typeof process.env.RESOURCE_BASE !== 'undefined' ) {
180
- resourceBase = process.env.RESOURCE_BASE;
181
- }
314
+ let reqs = '';
315
+ const pkgFolder = path.relative(dir, './pkg');
182
316
 
183
- if ( typeof process.env.OUTPUT_DIR !== 'undefined' ) {
184
- outputDir = process.env.OUTPUT_DIR;
317
+ if (fs.existsSync(pkgFolder)) {
318
+ fs.readdirSync(pkgFolder)
319
+ .filter((name) => !name.startsWith('.')) // Ignore hidden folders
320
+ .forEach((name) => {
321
+ const library = require(path.join(dir, 'pkg', name, 'package.json'));
322
+
323
+ // Package file must have rancher field to be a plugin
324
+ if (includePkg(name) && library.rancher) {
325
+ reqs += `$plugin.initPlugin('${ name }', require(\'~/pkg/${ name }\')); `;
326
+ }
327
+ });
185
328
  }
186
329
 
187
- if ( resourceBase && !resourceBase.endsWith('/') ) {
188
- resourceBase += '/';
330
+ Object.keys(librariesIndex).forEach((i) => {
331
+ reqs += `$plugin.loadAsync('${ i }', '/pkg/${ i }/${ librariesIndex[i] }');`;
332
+ });
333
+
334
+ return new VirtualModulesPlugin({ 'node_modules/@rancher/dynamic.js': `export default function ($plugin) { ${ reqs } };` });
335
+ };
336
+
337
+ const getAutoImport = () => new webpack.NormalModuleReplacementPlugin(/^@rancher\/auto-import$/, (resource) => {
338
+ const ctx = resource.context.split('/');
339
+ const pkg = ctx[ctx.length - 1];
340
+
341
+ resource.request = `@rancher/auto-import/${ pkg }`;
342
+ });
343
+
344
+ /**
345
+ * For each package in the pkg folder that is being compiled into the application,
346
+ * Add in the code to automatically import the types from that package
347
+ * This imports models, edit, detail, list etc
348
+ * When built as a UI package, shell/pkg/vue.config.js does the same thing
349
+ */
350
+ const getVirtualModulesAutoImport = (dir) => {
351
+ const autoImportTypes = {};
352
+ const pkgFolder = path.relative(dir, './pkg');
353
+
354
+ if (fs.existsSync(pkgFolder)) {
355
+ fs.readdirSync(pkgFolder)
356
+ .filter((name) => !name.startsWith('.')) // Ignore hidden folders
357
+ .forEach((name) => {
358
+ autoImportTypes[`node_modules/@rancher/auto-import/${ name }`] = generateDynamicTypeImport(`@pkg/${ name }`, path.join(dir, `pkg/${ name }`));
359
+ });
189
360
  }
190
361
 
362
+ return new VirtualModulesPlugin(autoImportTypes);
363
+ };
364
+
365
+ /**
366
+ * DefinePlugin does string replacement within our code. We may want to consider replacing it with something else. In code we'll see something like
367
+ * process.env.commit even though process and env aren't even defined objects. This could cause people to be mislead.
368
+ */
369
+ const createEnvVariablesPlugin = (routerBasePath, rancherEnv) => new webpack.DefinePlugin({
370
+ 'process.env.commit': JSON.stringify(commit),
371
+ 'process.env.version': JSON.stringify(dashboardVersion),
372
+ 'process.env.dev': JSON.stringify(dev),
373
+ 'process.env.pl': JSON.stringify(pl),
374
+ 'process.env.perfTest': JSON.stringify(perfTest),
375
+ 'process.env.loginLocaleSelector': JSON.stringify(process.env.LOGIN_LOCALE_SELECTOR || 'true'),
376
+ 'process.env.excludeOperatorPkg': JSON.stringify(process.env.EXCLUDE_OPERATOR_PKG || 'false'),
377
+ 'process.env.rancherEnv': JSON.stringify(rancherEnv),
378
+ 'process.env.harvesterPkgUrl': JSON.stringify(process.env.HARVESTER_PKG_URL),
379
+ 'process.env.api': JSON.stringify(api),
380
+ // Store the Router Base as env variable that we can use in `shell/config/router.js`
381
+ 'process.env.routerBase': JSON.stringify(routerBasePath),
382
+ __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
383
+ });
384
+
385
+ /**
386
+ * Ensure we process files in the @rancher/shell folder
387
+ */
388
+ const processShellFiles = (config, SHELL_ABS) => {
389
+ config.module.rules.forEach((rule) => {
390
+ if ('test.js'.match(rule.test)) {
391
+ if (rule.exclude) {
392
+ const orig = rule.exclude;
393
+
394
+ rule.exclude = function(modulePath) {
395
+ if (modulePath.indexOf(SHELL_ABS) === 0 || typeof orig !== 'function') {
396
+ return false;
397
+ }
398
+
399
+ return orig(modulePath);
400
+ };
401
+ }
402
+ }
403
+ });
404
+ };
405
+
406
+ /**
407
+ * Update vue-loader to set whitespace to 'preserve'
408
+ * This was the setting with nuxt, but is not the default with vue cli
409
+ * Need to find the vue loader in the webpack config and update the setting
410
+ */
411
+ const preserveWhitespace = (config) => {
412
+ config.module.rules.forEach((loader) => {
413
+ if (loader.use) {
414
+ loader.use.forEach((use) => {
415
+ if (use.loader.includes('vue-loader')) {
416
+ use.options.compilerOptions = { ...use.options.compilerOptions, whitespace: 'preserve' };
417
+ }
418
+ });
419
+ }
420
+ });
421
+ };
422
+
423
+ const printLogs = (dev, dashboardVersion, resourceBase, routerBasePath, pl, rancherEnv) => {
191
424
  console.log(`Build: ${ dev ? 'Development' : 'Production' }`); // eslint-disable-line no-console
192
425
 
193
- if ( !dev ) {
426
+ if (!dev) {
194
427
  console.log(`Version: ${ dashboardVersion }`); // eslint-disable-line no-console
195
428
  }
196
429
 
197
- if ( resourceBase ) {
430
+ if (resourceBase) {
198
431
  console.log(`Resource Base URL: ${ resourceBase }`); // eslint-disable-line no-console
199
432
  }
200
433
 
201
- if ( routerBasePath !== '/' ) {
434
+ if (routerBasePath !== '/') {
202
435
  console.log(`Router Base Path: ${ routerBasePath }`); // eslint-disable-line no-console
203
436
  }
204
437
 
205
- if ( pl !== STANDARD ) {
438
+ if (pl !== STANDARD) {
206
439
  console.log(`PL: ${ pl }`); // eslint-disable-line no-console
207
440
  }
208
- const rancherEnv = process.env.RANCHER_ENV || 'web';
209
-
210
- const loginLocaleSelector = process.env.LOGIN_LOCALE_SELECTOR || 'true';
211
- const excludeOperatorPkg = process.env.EXCLUDE_OPERATOR_PKG || 'false';
212
441
 
213
442
  console.log(`API: '${ api }'. Env: '${ rancherEnv }'`); // eslint-disable-line no-console
214
- const proxy = {
215
- ...appConfig.proxies,
216
- '/k8s': configHelper.proxyWsOpts(api), // Straight to a remote cluster (/k8s/clusters/<id>/)
217
- '/pp': configHelper.proxyWsOpts(api), // For (epinio) standalone API
218
- '/api': configHelper.proxyWsOpts(api), // Management k8s API
219
- '/apis': configHelper.proxyWsOpts(api), // Management k8s API
220
- '/v1': configHelper.proxyWsOpts(api), // Management Steve API
221
- '/v3': configHelper.proxyWsOpts(api), // Rancher API
222
- '/v3-public': configHelper.proxyOpts(api), // Rancher Unauthed API
223
- '/api-ui': configHelper.proxyOpts(api), // Browser API UI
224
- '/meta': configHelper.proxyMetaOpts(api), // Browser API UI
225
- '/v1-*': configHelper.proxyOpts(api), // SAML, KDM, etc
226
- '/rancherversion': configHelper.proxyPrimeOpts(api), // Rancher version endpoint
227
- // These are for Ember embedding
228
- '/c/*/edit': configHelper.proxyOpts('https://127.0.0.1:8000'), // Can't proxy all of /c because that's used by Vue too
229
- '/k/': configHelper.proxyOpts('https://127.0.0.1:8000'),
230
- '/g/': configHelper.proxyOpts('https://127.0.0.1:8000'),
231
- '/n/': configHelper.proxyOpts('https://127.0.0.1:8000'),
232
- '/p/': configHelper.proxyOpts('https://127.0.0.1:8000'),
233
- '/assets': configHelper.proxyOpts('https://127.0.0.1:8000'),
234
- '/translations': configHelper.proxyOpts('https://127.0.0.1:8000'),
235
- '/engines-dist': configHelper.proxyOpts('https://127.0.0.1:8000'),
236
- };
237
-
238
- // HAR File support - load network responses from the specified .har file and use those rather than communicating to the Rancher server
239
- const harFile = process.env.HAR_FILE;
240
- let harData;
241
-
242
- if (harFile) {
243
- harData = har.loadFile(harFile, devPorts ? 8005 : 80, ''); // eslint-disable-line no-console
244
- }
245
-
246
- const config = {
247
- // Vue server
248
- devServer: {
249
- client: { webSocketURL: { hostname: '0.0.0.0', port: devPorts ? 8005 : 80 } },
250
- server: {
251
- type: 'https',
252
- options: {
253
- key: fs.readFileSync(path.resolve(__dirname, 'server/server.key')),
254
- cert: fs.readFileSync(path.resolve(__dirname, 'server/server.crt'))
255
- }
256
- },
257
- port: (devPorts ? 8005 : 80),
258
- host: '0.0.0.0',
259
- setupMiddlewares(middlewares, devServer) {
260
- const socketProxies = {};
261
-
262
- if (!devServer) {
263
- // eslint-disable-next-line no-console
264
- console.error('webpack-dev-server is not defined');
265
-
266
- return middlewares;
267
- }
268
-
269
- const app = devServer.app;
270
-
271
- // Close down quickly in response to CTRL + C
272
- process.once('SIGINT', () => {
273
- devServer.close();
274
- console.log('\n'); // eslint-disable-line no-console
275
- process.exit(1);
276
- });
443
+ };
277
444
 
278
- app.use(serverMiddlewares);
445
+ /**
446
+ * Add ignored paths based on env var configuration and known cases
447
+ * TODO: Verify after migration completed
448
+ * In Webpack5 only RegExp, string and [string] types are accepted
449
+ * https://webpack.js.org/configuration/watch/#watchoptionsignored
450
+ * Example conversion:
451
+ * - as list: [/.shell/, /dist-pkg/, /scripts\/standalone/, /\/pkg.test-pkg/, /\/pkg.harvester/]
452
+ * - as chained regex rule: /.shell|dist-pkg|scripts\/standalone|\/pkg.test-pkg|\/pkg.harvester/
453
+ */
454
+ const getWatcherIgnored = (excludes) => {
455
+ const paths = [
456
+ /node_modules/,
457
+ /dist-pkg/,
458
+ /scripts\/standalone/,
459
+ ];
460
+ const pathExcludedPkg = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }`));
461
+ const pathsCombined = [...paths, ...pathExcludedPkg];
462
+ const regexCombined = new RegExp(pathsCombined.map(({ source }) => source).join('|'));
279
463
 
280
- if (harData) {
281
- console.log('Installing HAR file middleware'); // eslint-disable-line no-console
282
- app.use(har.harProxy(harData, process.env.HAR_DIR));
464
+ return regexCombined;
465
+ };
283
466
 
284
- devServer.webSocketProxies.push({
285
- upgrade(req, socket, head) {
286
- const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade'];
467
+ /**
468
+ * Expose a function that can be used by an app to provide a nuxt configuration for building an application
469
+ * This takes the directory of the application as the first argument so that we can derive folder locations
470
+ * from it, rather than from the location of this file
471
+ */
472
+ module.exports = function(dir, _appConfig) {
473
+ require('events').EventEmitter.defaultMaxListeners = 20;
474
+ require('dotenv').config();
287
475
 
288
- socket.write(`${ responseHeaders.join('\r\n') }\r\n\r\n`);
289
- }
290
- });
291
- }
476
+ const { SHELL_ABS, COMPONENTS_DIR } = getShellPaths(dir);
477
+ const appConfig = _appConfig || {};
478
+ const excludes = appConfig.excludes || [];
292
479
 
293
- Object.keys(proxy).forEach((p) => {
294
- const px = createProxyMiddleware({
295
- ...proxy[p],
296
- ws: false // We will handle the web socket upgrade
297
- });
480
+ const includePkg = (name) => {
481
+ if (name.startsWith('.') || name === 'node_modules') {
482
+ return false;
483
+ }
298
484
 
299
- if (proxy[p].ws) {
300
- socketProxies[p] = px;
301
- }
302
- app.use(p, px);
303
- });
485
+ return !excludes || (excludes && !excludes.includes(name));
486
+ };
304
487
 
305
- // TODO: Verify after migration completed
306
- devServer.webSocketProxies.push({
307
- upgrade(req, socket, head) {
308
- const path = Object.keys(socketProxies).find((path) => req.url.startsWith(path));
488
+ const routerBasePath = process.env.ROUTER_BASE ?? '/';
489
+ let resourceBase = process.env.RESOURCE_BASE ?? '';
490
+ const outputDir = process.env.OUTPUT_DIR ?? 'dist';
491
+ const rancherEnv = process.env.RANCHER_ENV || 'web';
309
492
 
310
- if (path) {
311
- const proxy = socketProxies[path];
493
+ if ( resourceBase && !resourceBase.endsWith('/') ) {
494
+ resourceBase += '/';
495
+ }
312
496
 
313
- if (proxy.upgrade) {
314
- proxy.upgrade(req, socket, head);
315
- } else {
316
- console.log(`Upgrade for Proxy is not defined. Cannot upgrade Web socket for ${ req.url }`); // eslint-disable-line no-console
317
- }
318
- } else {
319
- console.log(`Unknown Web socket upgrade request for ${ req.url }`); // eslint-disable-line no-console
320
- }
321
- }
322
- });
497
+ printLogs(dev, dashboardVersion, resourceBase, routerBasePath, pl, rancherEnv);
323
498
 
324
- return middlewares;
325
- }
326
- },
327
- transpileDependencies: true,
328
- publicPath: resourceBase || undefined,
329
- css: {
499
+ const proxy = getProxyConfig(appConfig.proxy);
500
+ const config = {
501
+ // Vue server
502
+ devServer: getDevServerConfig(proxy),
503
+ publicPath: resourceBase || undefined,
504
+ css: {
330
505
  extract: false, // inline css styles instead of including with `<links`
331
506
  loaderOptions: {
332
507
  sass: {
@@ -340,16 +515,13 @@ module.exports = function(dir, _appConfig) {
340
515
  }
341
516
  }
342
517
  },
343
-
344
518
  outputDir,
345
-
346
519
  pages: {
347
520
  index: {
348
521
  entry: path.join(SHELL_ABS, '/initialize/entry.js'),
349
522
  template: path.join(SHELL_ABS, '/public/index.html')
350
523
  }
351
524
  },
352
-
353
525
  configureWebpack(config) {
354
526
  // TODO VUE3: We may want to look into what we want the value to actually be. For the time being this was causing a warning in our CLI because it would set process.env.NODE_ENV to 'development' even thought it was
355
527
  // already set to 'dev' and we're using 'dev' in other locations so I don't think we want to do that. Config details found here: https://webpack.js.org/configuration/optimization/#optimizationnodeenv.
@@ -364,57 +536,21 @@ module.exports = function(dir, _appConfig) {
364
536
  config.resolve.alias['@components'] = COMPONENTS_DIR;
365
537
  config.resolve.alias['vue$'] = dev ? path.resolve(process.cwd(), 'node_modules', 'vue') : 'vue';
366
538
  config.resolve.modules.push(__dirname);
367
- config.plugins.push(virtualModules);
368
- config.plugins.push(autoImport);
369
- config.plugins.push(new VirtualModulesPlugin(autoImportTypes));
370
- config.plugins.push(pkgImport);
539
+ config.plugins.push(getVirtualModules(dir, includePkg));
540
+ config.plugins.push(getAutoImport());
541
+ config.plugins.push(getVirtualModulesAutoImport(dir));
542
+ config.plugins.push(getPackageImport(dir));
543
+ config.plugins.push(createEnvVariablesPlugin(routerBasePath, rancherEnv));
371
544
  config.plugins.push(new NodePolyfillPlugin()); // required from Webpack 5 to polyfill node modules
372
- // DefinePlugin does string replacement within our code. We may want to consider replacing it with something else. In code we'll see something like
373
- // process.env.commit even though process and env aren't even defined objects. This could cause people to be mislead.
374
- config.plugins.push(new webpack.DefinePlugin({
375
- 'process.env.commit': JSON.stringify(commit),
376
- 'process.env.version': JSON.stringify(dashboardVersion),
377
- 'process.env.dev': JSON.stringify(dev),
378
- 'process.env.pl': JSON.stringify(pl),
379
- 'process.env.perfTest': JSON.stringify(perfTest),
380
- 'process.env.loginLocaleSelector': JSON.stringify(loginLocaleSelector),
381
- 'process.env.excludeOperatorPkg': JSON.stringify(excludeOperatorPkg),
382
- 'process.env.rancherEnv': JSON.stringify(rancherEnv),
383
- 'process.env.harvesterPkgUrl': JSON.stringify(process.env.HARVESTER_PKG_URL),
384
- 'process.env.api': JSON.stringify(api),
385
- // Store the Router Base as env variable that we can use in `shell/config/router.js`
386
- 'process.env.routerBase': JSON.stringify(routerBasePath),
387
-
388
- __VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false',
389
-
390
- // This is a replacement of the nuxt publicRuntimeConfig
391
- 'nuxt.publicRuntimeConfig': JSON.stringify({
392
- rancherEnv,
393
- dashboardVersion
394
- }),
395
-
396
- }));
397
545
 
398
546
  // The static assets need to be in the built assets directory in order to get served (primarily the favicon)
399
547
  config.plugins.push(new CopyWebpackPlugin({ patterns: [{ from: path.join(SHELL_ABS, 'static'), to: '.' }] }));
400
548
 
401
549
  config.resolve.extensions.push(...['.tsx', '.ts', '.js', '.vue', '.scss']);
402
-
403
- /**
404
- * Add ignored paths based on env var configuration and known cases
405
- * TODO: Verify after migration completed
406
- * In Webpack5 only RegExp, string and [string] types are accepted
407
- * https://webpack.js.org/configuration/watch/#watchoptionsignored
408
- * Example conversion:
409
- * - as list: [/.shell/, /dist-pkg/, /scripts\/standalone/, /\/pkg.test-pkg/, /\/pkg.harvester/]
410
- * - as chained regex rule: /.shell|dist-pkg|scripts\/standalone|\/pkg.test-pkg|\/pkg.harvester/
411
- */
412
- config.watchOptions = config.watchOptions || {};
413
- const ignoredPkgs = excludes.map((excluded) => new RegExp(`/pkg.${ excluded }`));
414
- const watcherIgnoresPaths = [...watcherIgnores, ...ignoredPkgs];
415
- const combinedRegex = new RegExp(watcherIgnoresPaths.map(({ source }) => source).join('|'));
416
-
417
- config.watchOptions.ignored = combinedRegex;
550
+ config.watchOptions = {
551
+ ...(config.watchOptions || {}),
552
+ ignored: getWatcherIgnored(excludes)
553
+ };
418
554
 
419
555
  if (dev) {
420
556
  config.devtool = 'cheap-module-source-map';
@@ -423,128 +559,10 @@ module.exports = function(dir, _appConfig) {
423
559
  }
424
560
 
425
561
  config.resolve.symlinks = false;
426
-
427
- // Ensure we process files in the @rancher/shell folder
428
- config.module.rules.forEach((r) => {
429
- if ('test.js'.match(r.test)) {
430
- if (r.exclude) {
431
- const orig = r.exclude;
432
-
433
- r.exclude = function(modulePath) {
434
- if (modulePath.indexOf(SHELL_ABS) === 0 || typeof orig !== 'function') {
435
- return false;
436
- }
437
-
438
- return orig(modulePath);
439
- };
440
- }
441
- }
442
- });
443
-
444
- // Instrument code for tests
445
- const babelPlugins = [
446
- // TODO: Browser support
447
- // ['@babel/plugin-transform-modules-commonjs'],
448
- ['@babel/plugin-proposal-private-property-in-object', { loose: true }],
449
- ['@babel/plugin-proposal-class-properties', { loose: true }]
450
- ];
451
-
452
- if (instrumentCode) {
453
- babelPlugins.push([
454
- 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
455
- ]);
456
-
457
- console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
458
- }
459
-
460
- const loaders = [
461
- // Ensure there is a fallback for browsers that don't support web workers
462
- {
463
- test: /web-worker.[a-z-]+.js/i,
464
- loader: 'worker-loader',
465
- options: { inline: 'fallback' },
466
- },
467
- // Handler for csv files (e.g. ec2 instance data)
468
- {
469
- test: /\.csv$/i,
470
- loader: 'csv-loader',
471
- options: {
472
- dynamicTyping: true,
473
- header: true,
474
- skipEmptyLines: true
475
- },
476
- },
477
- // Handler for yaml files (used for i18n files, for example)
478
- {
479
- test: /\.ya?ml$/i,
480
- loader: 'js-yaml-loader',
481
- options: { name: '[path][name].[ext]' },
482
- },
483
- {
484
- test: /\.m?[tj]sx?$/,
485
- // This excludes no modules except for node_modules/@rancher/... so that plugins can properly compile
486
- // when referencing @rancher/shell
487
- exclude: /node_modules\/(?!(@rancher)\/).*/,
488
- use: [
489
- {
490
- loader: 'cache-loader',
491
- options: {
492
- cacheDirectory: 'node_modules/.cache/babel-loader',
493
- cacheIdentifier: 'e93f32da'
494
- }
495
- },
496
- ]
497
- },
498
- {
499
- test: /\.tsx?$/,
500
- use: [
501
- {
502
- loader: 'cache-loader',
503
- options: {
504
- cacheDirectory: 'node_modules/.cache/ts-loader',
505
- cacheIdentifier: '3596741e'
506
- }
507
- },
508
- {
509
- loader: 'ts-loader',
510
- options: {
511
- transpileOnly: true,
512
- happyPackMode: false,
513
- appendTsxSuffixTo: [
514
- '\\.vue$'
515
- ],
516
- configFile: path.join(SHELL_ABS, 'tsconfig.json')
517
- }
518
- }
519
- ]
520
- },
521
- // Prevent warning in log with the md files in the content folder
522
- {
523
- test: /\.md$/,
524
- use: [
525
- {
526
- loader: 'frontmatter-markdown-loader',
527
- options: { mode: ['body'] }
528
- }
529
- ]
530
- },
531
- ];
532
-
533
- config.module.rules.push(...loaders);
534
-
535
- // TODO: Verify after migration completed
536
- // Update vue-loader to set whitespace to 'preserve'
537
- // This was the setting with nuxt, but is not the default with vue cli
538
- // Need to find the vue loader in the webpack config and update the setting
539
- config.module.rules.forEach((loader) => {
540
- if (loader.use) {
541
- loader.use.forEach((use) => {
542
- if (use.loader.includes('vue-loader')) {
543
- use.options.compilerOptions = { ...use.options.compilerOptions, whitespace: 'preserve' };
544
- }
545
- });
546
- }
547
- });
562
+ processShellFiles(config, SHELL_ABS);
563
+ instrumentCode();
564
+ config.module.rules.push(...getLoaders(SHELL_ABS));
565
+ preserveWhitespace(config);
548
566
  },
549
567
  };
550
568