@rancher/shell 0.3.8 → 0.3.9

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 (36) hide show
  1. package/assets/translations/en-us.yaml +28 -2
  2. package/babel.config.js +17 -4
  3. package/components/CodeMirror.vue +146 -14
  4. package/components/ContainerResourceLimit.vue +14 -1
  5. package/components/CruResource.vue +21 -5
  6. package/components/ExplorerProjectsNamespaces.vue +5 -1
  7. package/components/GroupPanel.vue +57 -0
  8. package/components/YamlEditor.vue +2 -2
  9. package/components/form/ArrayList.vue +1 -1
  10. package/components/form/KeyValue.vue +34 -1
  11. package/components/form/MatchExpressions.vue +120 -21
  12. package/components/form/NodeAffinity.vue +54 -4
  13. package/components/form/PodAffinity.vue +160 -47
  14. package/components/form/Tolerations.vue +40 -4
  15. package/components/form/__tests__/ArrayList.test.ts +3 -3
  16. package/components/form/__tests__/MatchExpressions.test.ts +1 -1
  17. package/components/nav/Header.vue +2 -0
  18. package/config/settings.ts +6 -1
  19. package/core/plugins-loader.js +0 -2
  20. package/edit/configmap.vue +33 -6
  21. package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +326 -0
  22. package/edit/provisioning.cattle.io.cluster/index.vue +1 -0
  23. package/edit/provisioning.cattle.io.cluster/rke2.vue +60 -0
  24. package/mixins/chart.js +1 -1
  25. package/models/batch.cronjob.js +18 -3
  26. package/models/workload.js +1 -1
  27. package/package.json +2 -3
  28. package/pages/auth/login.vue +1 -0
  29. package/pages/prefs.vue +18 -2
  30. package/pkg/vue.config.js +0 -1
  31. package/plugins/codemirror.js +158 -0
  32. package/public/index.html +1 -1
  33. package/types/shell/index.d.ts +20 -1
  34. package/utils/create-yaml.js +105 -8
  35. package/utils/settings.ts +12 -0
  36. package/vue.config.js +2 -2
@@ -167,3 +167,161 @@ CodeMirror.registerHelper('fold', 'yamlcomments', (cm, start) => {
167
167
  };
168
168
  }
169
169
  });
170
+
171
+ /**
172
+ * It display a dot for each space character in the text;
173
+ * used in combination with 'as-text-area' css properties in CodeMirror.vue to display line break markdowns
174
+ */
175
+ CodeMirror.defineOption('showMarkdownLineBreaks', false, (codeMirror) => {
176
+ codeMirror.addOverlay({
177
+ name: 'show-markdown-line-breaks',
178
+ token: (stream) => {
179
+ if (stream.string[stream.pos].match(/\s/)) {
180
+ stream.next();
181
+
182
+ return stream.pos % 2 === 0 ? 'markdown-single-trailing-space-even' : 'markdown-single-trailing-space-odd';
183
+ }
184
+
185
+ stream.next();
186
+
187
+ return null;
188
+ }
189
+ });
190
+ });
191
+
192
+ /**
193
+ * It enables the text color selection in CodeMirror.vue
194
+ * references:
195
+ * demo: https://codemirror.net/5/demo/markselection.html#
196
+ * add-on: https://codemirror.net/5/doc/manual.html#addon_mark-selection
197
+ * source: https://codemirror.net/5/addon/selection/mark-selection.js
198
+ */
199
+ CodeMirror.defineOption('styleSelectedText', false, (cm, val, old) => {
200
+ const prev = old && old !== CodeMirror.Init;
201
+
202
+ if (val && !prev) {
203
+ cm.state.markedSelection = [];
204
+ cm.state.markedSelectionStyle = typeof val === 'string' ? val : 'CodeMirror-selectedtext';
205
+ reset(cm);
206
+ cm.on('cursorActivity', onCursorActivity);
207
+ cm.on('change', onChange);
208
+ } else if (!val && prev) {
209
+ cm.off('cursorActivity', onCursorActivity);
210
+ cm.off('change', onChange);
211
+ clear(cm);
212
+ cm.state.markedSelection = cm.state.markedSelectionStyle = null;
213
+ }
214
+ });
215
+
216
+ function onCursorActivity(cm) {
217
+ if (cm.state.markedSelection) {
218
+ cm.operation(() => {
219
+ update(cm);
220
+ });
221
+ }
222
+ }
223
+
224
+ function onChange(cm) {
225
+ if (cm.state.markedSelection && cm.state.markedSelection.length) {
226
+ cm.operation(() => {
227
+ clear(cm);
228
+ });
229
+ }
230
+ }
231
+
232
+ const CHUNK_SIZE = 8;
233
+ const Pos = CodeMirror.Pos;
234
+ const cmp = CodeMirror.cmpPos;
235
+
236
+ function coverRange(cm, from, to, addAt) {
237
+ if (cmp(from, to) === 0) {
238
+ return;
239
+ }
240
+ const array = cm.state.markedSelection;
241
+ const cls = cm.state.markedSelectionStyle;
242
+
243
+ for (let line = from.line;;) {
244
+ const start = line === from.line ? from : Pos(line, 0);
245
+ const endLine = line + CHUNK_SIZE; const atEnd = endLine >= to.line;
246
+ const end = atEnd ? to : Pos(endLine, 0);
247
+ const mark = cm.markText(start, end, { className: cls });
248
+
249
+ if (addAt === null || addAt === undefined) {
250
+ array.push(mark);
251
+ } else {
252
+ array.splice(addAt++, 0, mark);
253
+ }
254
+ if (atEnd) {
255
+ break;
256
+ }
257
+ line = endLine;
258
+ }
259
+ }
260
+
261
+ function clear(cm) {
262
+ const array = cm.state.markedSelection;
263
+
264
+ for (let i = 0; i < array.length; ++i) {
265
+ array[i].clear();
266
+ }
267
+ array.length = 0;
268
+ }
269
+
270
+ function reset(cm) {
271
+ clear(cm);
272
+ const ranges = cm.listSelections();
273
+
274
+ for (let i = 0; i < ranges.length; i++) {
275
+ coverRange(cm, ranges[i].from(), ranges[i].to());
276
+ }
277
+ }
278
+
279
+ function update(cm) {
280
+ if (!cm.somethingSelected()) {
281
+ return clear(cm);
282
+ }
283
+ if (cm.listSelections().length > 1) {
284
+ return reset(cm);
285
+ }
286
+
287
+ const from = cm.getCursor('start'); const to = cm.getCursor('end');
288
+
289
+ const array = cm.state.markedSelection;
290
+
291
+ if (!array.length) {
292
+ return coverRange(cm, from, to);
293
+ }
294
+
295
+ let coverStart = array[0].find(); let coverEnd = array[array.length - 1].find();
296
+
297
+ if (!coverStart || !coverEnd || to.line - from.line <= CHUNK_SIZE ||
298
+ cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0) {
299
+ return reset(cm);
300
+ }
301
+
302
+ while (cmp(from, coverStart.from) > 0) {
303
+ array.shift().clear();
304
+ coverStart = array[0].find();
305
+ }
306
+ if (cmp(from, coverStart.from) < 0) {
307
+ if (coverStart.to.line - from.line < CHUNK_SIZE) {
308
+ array.shift().clear();
309
+ coverRange(cm, from, coverStart.to, 0);
310
+ } else {
311
+ coverRange(cm, from, coverStart.from, 0);
312
+ }
313
+ }
314
+
315
+ while (cmp(to, coverEnd.to) < 0) {
316
+ array.pop().clear();
317
+ coverEnd = array[array.length - 1].find();
318
+ }
319
+ if (cmp(to, coverEnd.to) > 0) {
320
+ if (to.line - coverEnd.from.line < CHUNK_SIZE) {
321
+ array.pop().clear();
322
+ coverRange(cm, coverEnd.from, to);
323
+ } else {
324
+ coverRange(cm, coverEnd.to, to);
325
+ }
326
+ }
327
+ }
package/public/index.html CHANGED
@@ -5,7 +5,7 @@
5
5
  <meta charset="utf-8">
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
7
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
8
- <link rel="shortcut icon" type="image/x-icon" href="/public/favicon.png">
8
+ <link rel="shortcut icon" type="image/x-icon" href="/favicon.png">
9
9
  <title>Rancher</title>
10
10
  </head>
11
11
 
@@ -2491,10 +2491,29 @@ export default _default;
2491
2491
  // @shell/utils/create-yaml
2492
2492
 
2493
2493
  declare module '@shell/utils/create-yaml' {
2494
- export function createYaml(schemas: any, type: any, data: any, processAlwaysAdd?: boolean, depth?: number, path?: string, rootType?: any): string;
2494
+ export function createYamlWithOptions(schemas: any, type: any, data: any, options: any): string;
2495
+ export function createYaml(schemas: any, type: any, data: any, processAlwaysAdd?: boolean, depth?: number, path?: string, rootType?: any, dataOptions?: {}): string;
2495
2496
  export function typeRef(type: any, str: any): any;
2496
2497
  export function typeMunge(type: any): any;
2497
2498
  export function saferDump(obj: any): any;
2499
+ /**
2500
+ * Handles newlines indicators in the multiline blocks.
2501
+ *
2502
+ * this is required since jsyaml.dump doesn't support chomping and scalar style at the moment.
2503
+ * see: https://github.com/nodeca/js-yaml/issues/171
2504
+ *
2505
+ * @param {*} data the multiline block
2506
+ * @param {*} options blocks indicators, see: https://yaml-multiline.info
2507
+ *
2508
+ * - scalarStyle:
2509
+ * one of '|', '>'
2510
+ * default '|'
2511
+ * - chomping:
2512
+ * one of: null, '-', '+'
2513
+ * default: null
2514
+ * @returns the result of jsyaml.dump with the addition of multiline indicators
2515
+ */
2516
+ export function dumpBlock(data: any, options?: any): any;
2498
2517
  export const SIMPLE_TYPES: string[];
2499
2518
  export const NEVER_ADD: string[];
2500
2519
  export const ACTIVELY_REMOVE: string[];
@@ -65,7 +65,26 @@ export const ACTIVELY_REMOVE = [
65
65
 
66
66
  const INDENT = 2;
67
67
 
68
- export function createYaml(schemas, type, data, processAlwaysAdd = true, depth = 0, path = '', rootType = null) {
68
+ export function createYamlWithOptions(schemas, type, data, options) {
69
+ return createYaml(
70
+ schemas,
71
+ type,
72
+ data,
73
+ true, 0, '', null,
74
+ options
75
+ );
76
+ }
77
+
78
+ export function createYaml(
79
+ schemas,
80
+ type,
81
+ data,
82
+ processAlwaysAdd = true,
83
+ depth = 0,
84
+ path = '',
85
+ rootType = null,
86
+ dataOptions = {}
87
+ ) {
69
88
  const schema = findBy(schemas, 'id', type);
70
89
 
71
90
  if ( !rootType ) {
@@ -220,25 +239,25 @@ export function createYaml(schemas, type, data, processAlwaysAdd = true, depth =
220
239
  if (data[key]) {
221
240
  try {
222
241
  const cleaned = cleanUp(data);
223
- const parsedData = jsyaml.dump(cleaned[key]);
242
+ const parsedData = dumpBlock(cleaned[key], dataOptions[key]);
224
243
 
225
- out += `\n${ indent(parsedData.trim()) }`;
244
+ out += `\n${ indent(parsedData) }`;
226
245
  } catch (e) {
227
246
  console.error(`Error: Unable to parse map data for yaml of type: ${ type }`, e); // eslint-disable-line no-console
228
247
  }
229
248
  }
230
249
 
231
250
  if ( SIMPLE_TYPES.includes(mapOf) ) {
232
- out += `\n# key: ${ mapOf }`;
251
+ out += `# key: ${ mapOf }`;
233
252
  } else {
234
253
  // If not a simple type ie some sort of object/array, recusively build out commented fields (note data = null here) per the type's (mapOf's) schema
235
- const chunk = createYaml(schemas, mapOf, null, processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType);
254
+ const chunk = createYaml(schemas, mapOf, null, processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
236
255
  let indented = indent(chunk);
237
256
 
238
257
  // convert "# foo" to "#foo"
239
258
  indented = indented.replace(/^(#)?\s\s\s\s/, '$1');
240
259
 
241
- out += `\n${ indented }`;
260
+ out += `${ indented }`;
242
261
  }
243
262
 
244
263
  return out;
@@ -263,7 +282,7 @@ export function createYaml(schemas, type, data, processAlwaysAdd = true, depth =
263
282
  if ( SIMPLE_TYPES.includes(arrayOf) ) {
264
283
  out += `\n# - ${ arrayOf }`;
265
284
  } else {
266
- const chunk = createYaml(schemas, arrayOf, null, false, depth + 1, (path ? `${ path }.${ key }` : key), rootType);
285
+ const chunk = createYaml(schemas, arrayOf, null, false, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
267
286
  let indented = indent(chunk, 2);
268
287
 
269
288
  // turn "# foo" into "# - foo"
@@ -318,7 +337,7 @@ export function createYaml(schemas, type, data, processAlwaysAdd = true, depth =
318
337
  let chunk;
319
338
 
320
339
  if (subDef?.resourceFields && !isEmpty(subDef?.resourceFields)) {
321
- chunk = createYaml(schemas, type, data[key], processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType);
340
+ chunk = createYaml(schemas, type, data[key], processAlwaysAdd, depth + 1, (path ? `${ path }.${ key }` : key), rootType, dataOptions);
322
341
  } else if (data[key]) {
323
342
  // if there are no fields defined on the schema but there are in the data, just format data as yaml and add to output yaml
324
343
  try {
@@ -351,6 +370,43 @@ function serializeSimpleValue(data) {
351
370
  return jsyaml.dump(data).trim();
352
371
  }
353
372
 
373
+ function getBlockDescriptor(value, key) {
374
+ const header = getBlockHeader(value, key);
375
+
376
+ return {
377
+ header,
378
+ indentation: getBlockIndentation(header),
379
+ };
380
+ }
381
+
382
+ /**
383
+ *
384
+ * @param {string} value the block of text to be parsed
385
+ * @param {*} blockKey the key of the block
386
+ * @returns the key + the block scalar indicators, see https://yaml-multiline.info - Block Scalars
387
+ */
388
+ function getBlockHeader(value, blockKey) {
389
+ const card = `(${ blockKey })[\\:][\\s|\\t]+[\\|\\>][\\d]*[\\-\\+]?`;
390
+ const re = new RegExp(card, 'gi');
391
+
392
+ const found = value.match(re);
393
+
394
+ return found?.[0] || '';
395
+ }
396
+
397
+ /**
398
+ *
399
+ * @param {string} blockHeader the key + the block scalar indicators
400
+ * @returns the indentation indicator from the block header, see https://yaml-multiline.info - Indentation
401
+ */
402
+ function getBlockIndentation(blockHeader) {
403
+ const blockScalars = blockHeader.substr(blockHeader.indexOf(':') + 1);
404
+
405
+ const indentation = blockScalars.match(/\d+/);
406
+
407
+ return indentation?.[0] || '';
408
+ }
409
+
354
410
  export function typeRef(type, str) {
355
411
  const re = new RegExp(`^${ type }\\[(.*)\\]$`);
356
412
  const match = str.match(re);
@@ -381,3 +437,44 @@ export function saferDump(obj) {
381
437
 
382
438
  return out;
383
439
  }
440
+
441
+ /**
442
+ * Handles newlines indicators in the multiline blocks.
443
+ *
444
+ * this is required since jsyaml.dump doesn't support chomping and scalar style at the moment.
445
+ * see: https://github.com/nodeca/js-yaml/issues/171
446
+ *
447
+ * @param {*} data the multiline block
448
+ * @param {*} options blocks indicators, see: https://yaml-multiline.info
449
+ *
450
+ * - scalarStyle:
451
+ * one of '|', '>'
452
+ * default '|'
453
+ * - chomping:
454
+ * one of: null, '-', '+'
455
+ * default: null
456
+ * @returns the result of jsyaml.dump with the addition of multiline indicators
457
+ */
458
+ export function dumpBlock(data, options = {}) {
459
+ const parsed = jsyaml.dump(data);
460
+
461
+ let out = parsed;
462
+
463
+ const blockFields = Object.keys(data).filter(k => data[k].includes('\n'));
464
+
465
+ if (blockFields.length) {
466
+ for (const key of blockFields) {
467
+ const scalarStyle = options[key]?.scalarStyle ?? '|';
468
+ const chomping = options[key]?.chomping ?? '';
469
+
470
+ const desc = getBlockDescriptor(out, key);
471
+
472
+ /**
473
+ * Replace the original block indicators with the ones provided in the options param
474
+ */
475
+ out = out.replace(desc.header, `${ key }: ${ scalarStyle }${ chomping }${ desc.indentation }`);
476
+ }
477
+ }
478
+
479
+ return out;
480
+ }
package/utils/settings.ts CHANGED
@@ -22,6 +22,18 @@ export const fetchOrCreateSetting = async(store: Store<any>, id: string, val: st
22
22
  return setting;
23
23
  };
24
24
 
25
+ /**
26
+ * Fetch a specific setting that might not exist
27
+ * We fetch all settings - reality is Rancher will have done this already, so there's no overhead in doing
28
+ * this - but if we fetch a specific setting that does not exist, we will get a 404, which we don't want
29
+ */
30
+ export const fetchSetting = async(store: Store<any>, id: string) => {
31
+ const all = await store.dispatch('management/findAll', { type: MANAGEMENT.SETTING });
32
+ const setting = (all || []).find((setting: any) => setting.id === id);
33
+
34
+ return setting;
35
+ };
36
+
25
37
  export const setSetting = async(store: Store<any>, id: string, val: string): Promise<any> => {
26
38
  const setting = await fetchOrCreateSetting(store, id, val, false);
27
39
 
package/vue.config.js CHANGED
@@ -408,8 +408,8 @@ module.exports = function(dir, _appConfig) {
408
408
  }),
409
409
  }));
410
410
 
411
- // The static assets need to be in the built public folder in order to get served (primarily the favicon for now)
412
- config.plugins.push(new CopyWebpackPlugin([{ from: path.join(SHELL_ABS, 'static'), to: 'public' }]));
411
+ // The static assets need to be in the built assets directory in order to get served (primarily the favicon)
412
+ config.plugins.push(new CopyWebpackPlugin([{ from: path.join(SHELL_ABS, 'static'), to: '.' }]));
413
413
 
414
414
  config.resolve.extensions.push(...['.tsx', '.ts', '.js', '.vue', '.scss']);
415
415
  config.watchOptions = config.watchOptions || {};