@rancher/shell 0.3.22 → 0.3.24

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 (48) hide show
  1. package/assets/styles/base/_variables.scss +1 -0
  2. package/assets/styles/themes/_dark.scss +1 -0
  3. package/assets/styles/themes/_light.scss +6 -5
  4. package/assets/translations/en-us.yaml +15 -10
  5. package/assets/translations/zh-hans.yaml +1 -1
  6. package/babel.config.js +3 -0
  7. package/components/ClusterProviderIconMenu.vue +161 -0
  8. package/components/Loading.vue +1 -1
  9. package/components/SideNav.vue +1 -1
  10. package/components/SortableTable/paging.js +10 -0
  11. package/components/form/GitPicker.vue +16 -0
  12. package/components/form/SelectOrCreateAuthSecret.vue +16 -3
  13. package/components/nav/Group.vue +54 -24
  14. package/components/nav/Header.vue +1 -1
  15. package/components/nav/TopLevelMenu.vue +469 -294
  16. package/components/nav/Type.vue +31 -5
  17. package/creators/pkg/init +2 -2
  18. package/edit/fleet.cattle.io.gitrepo.vue +43 -15
  19. package/edit/logging.banzaicloud.io.output/index.vue +7 -0
  20. package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +3 -8
  21. package/edit/provisioning.cattle.io.cluster/rke2.vue +108 -33
  22. package/edit/resources.cattle.io.backup.vue +3 -1
  23. package/edit/resources.cattle.io.restore.vue +3 -1
  24. package/edit/workload/storage/ContainerMountPaths.vue +7 -5
  25. package/initialize/App.js +2 -0
  26. package/initialize/client.js +63 -51
  27. package/initialize/index.js +2 -0
  28. package/layouts/default.vue +8 -0
  29. package/machine-config/amazonec2.vue +1 -0
  30. package/mixins/fetch.client.js +3 -3
  31. package/package.json +1 -1
  32. package/pages/__tests__/prefs.test.ts +1 -1
  33. package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
  34. package/pages/prefs.vue +3 -13
  35. package/plugins/dashboard-store/resource-class.js +1 -1
  36. package/public/index.html +4 -2
  37. package/rancher-components/Form/LabeledInput/LabeledInput.vue +8 -0
  38. package/rancher-components/Form/Radio/RadioButton.test.ts +7 -3
  39. package/scripts/extension/parse-tag-name +0 -0
  40. package/store/prefs.js +3 -4
  41. package/store/type-map.js +2 -16
  42. package/types/shell/index.d.ts +10 -1
  43. package/utils/__tests__/formatter.test.ts +77 -0
  44. package/utils/__tests__/sort.test.ts +61 -0
  45. package/utils/formatter.js +11 -0
  46. package/utils/string.js +12 -0
  47. package/vue.config.js +7 -6
  48. package/yarn-error.log +16 -16
@@ -1,3 +1,5 @@
1
+ // Taken from @nuxt/vue-app/template/client.js
2
+
1
3
  import Vue from 'vue';
2
4
  import fetch from 'unfetch';
3
5
  import middleware from '../config/middleware.js';
@@ -21,6 +23,10 @@ import { createApp, NuxtError } from './index.js';
21
23
  import fetchMixin from '../mixins/fetch.client';
22
24
  import NuxtLink from '../components/nuxt/nuxt-link.client.js'; // should be included after ./index.js
23
25
 
26
+ // Mimic old @nuxt/vue-app/template/client.js
27
+ const isDev = process.env.dev;
28
+ const debug = isDev;
29
+
24
30
  // Fetch mixin
25
31
  if (!Vue.__nuxt__fetch__mixin__) {
26
32
  Vue.mixin(fetchMixin);
@@ -51,69 +57,71 @@ if ($config._app) {
51
57
 
52
58
  Object.assign(Vue.config, { silent: false, performance: true });
53
59
 
54
- const logs = NUXT.logs || [];
60
+ if (debug) {
61
+ const logs = NUXT.logs || [];
55
62
 
56
- if (logs.length > 0) {
57
- const ssrLogStyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;';
63
+ if (logs.length > 0) {
64
+ const ssrLogStyle = 'background: #2E495E;border-radius: 0.5em;color: white;font-weight: bold;padding: 2px 0.5em;';
58
65
 
59
- console.group && console.group('%cNuxt SSR', ssrLogStyle); // eslint-disable-line no-console
60
- logs.forEach((logObj) => (console[logObj.type] || console.log)(...logObj.args)); // eslint-disable-line no-console
61
- delete NUXT.logs;
62
- console.groupEnd && console.groupEnd(); // eslint-disable-line no-console
63
- }
66
+ console.group && console.group('%cNuxt SSR', ssrLogStyle); // eslint-disable-line no-console
67
+ logs.forEach((logObj) => (console[logObj.type] || console.log)(...logObj.args)); // eslint-disable-line no-console
68
+ delete NUXT.logs;
69
+ console.groupEnd && console.groupEnd(); // eslint-disable-line no-console
70
+ }
64
71
 
65
- // Setup global Vue error handler
66
- if (!Vue.config.$nuxt) {
67
- const defaultErrorHandler = Vue.config.errorHandler;
72
+ // Setup global Vue error handler
73
+ if (!Vue.config.$nuxt) {
74
+ const defaultErrorHandler = Vue.config.errorHandler;
68
75
 
69
- Vue.config.errorHandler = async(err, vm, info, ...rest) => {
76
+ Vue.config.errorHandler = async(err, vm, info, ...rest) => {
70
77
  // Call other handler if exist
71
- let handled = null;
78
+ let handled = null;
72
79
 
73
- if (typeof defaultErrorHandler === 'function') {
74
- handled = defaultErrorHandler(err, vm, info, ...rest);
75
- }
76
- if (handled === true) {
77
- return handled;
78
- }
80
+ if (typeof defaultErrorHandler === 'function') {
81
+ handled = defaultErrorHandler(err, vm, info, ...rest);
82
+ }
83
+ if (handled === true) {
84
+ return handled;
85
+ }
79
86
 
80
- if (vm && vm.$root) {
81
- const nuxtApp = Object.keys(Vue.config.$nuxt)
82
- .find((nuxtInstance) => vm.$root[nuxtInstance]);
87
+ if (vm && vm.$root) {
88
+ const nuxtApp = Object.keys(Vue.config.$nuxt)
89
+ .find((nuxtInstance) => vm.$root[nuxtInstance]);
83
90
 
84
- // Show Nuxt Error Page
85
- if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') {
86
- const currentApp = vm.$root[nuxtApp];
91
+ // Show Nuxt Error Page
92
+ if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') {
93
+ const currentApp = vm.$root[nuxtApp];
87
94
 
88
- // Load error layout
89
- let layout = (NuxtError.options || NuxtError).layout;
95
+ // Load error layout
96
+ let layout = (NuxtError.options || NuxtError).layout;
90
97
 
91
- if (typeof layout === 'function') {
92
- layout = layout(currentApp.context);
93
- }
94
- if (layout) {
95
- await currentApp.loadLayout(layout).catch(() => {});
96
- }
97
- currentApp.setLayout(layout);
98
+ if (typeof layout === 'function') {
99
+ layout = layout(currentApp.context);
100
+ }
101
+ if (layout) {
102
+ await currentApp.loadLayout(layout).catch(() => {});
103
+ }
104
+ currentApp.setLayout(layout);
98
105
 
99
- currentApp.error(err);
106
+ currentApp.error(err);
107
+ }
100
108
  }
101
- }
102
109
 
103
- if (typeof defaultErrorHandler === 'function') {
104
- return handled;
105
- }
110
+ if (typeof defaultErrorHandler === 'function') {
111
+ return handled;
112
+ }
106
113
 
107
- // Log to console
108
- if (process.env.NODE_ENV !== 'production') {
109
- console.error(err); // eslint-disable-line no-console
110
- } else {
111
- console.error(err.message || err); // eslint-disable-line no-console
112
- }
113
- };
114
- Vue.config.$nuxt = {};
114
+ // Log to console
115
+ if (process.env.NODE_ENV !== 'production') {
116
+ console.error(err); // eslint-disable-line no-console
117
+ } else {
118
+ console.error(err.message || err); // eslint-disable-line no-console
119
+ }
120
+ };
121
+ Vue.config.$nuxt = {};
122
+ }
123
+ Vue.config.$nuxt.$nuxt = true;
115
124
  }
116
- Vue.config.$nuxt.$nuxt = true;
117
125
 
118
126
  const errorHandler = Vue.config.errorHandler || console.error; // eslint-disable-line no-console
119
127
 
@@ -623,7 +631,9 @@ function fixPrepatch(to, ___) {
623
631
  checkForErrors(this);
624
632
 
625
633
  // Hot reloading
626
- setTimeout(() => hotReloadAPI(this), 100);
634
+ if (isDev) {
635
+ setTimeout(() => hotReloadAPI(this), 100);
636
+ }
627
637
  });
628
638
  }
629
639
 
@@ -794,8 +804,10 @@ async function mountApp(__app) {
794
804
  // Call window.{{globals.readyCallback}} callbacks
795
805
  nuxtReady(_app);
796
806
 
797
- // Enable hot reloading
798
- hotReloadAPI(_app);
807
+ if (isDev) {
808
+ // Enable hot reloading
809
+ hotReloadAPI(_app);
810
+ }
799
811
  });
800
812
  };
801
813
 
@@ -1,3 +1,5 @@
1
+ // Taken from @nuxt/vue-app/template/index.js
2
+
1
3
  import Vue from 'vue';
2
4
  import Meta from 'vue-meta';
3
5
  import ClientOnly from 'vue-client-only';
@@ -318,6 +318,14 @@ export default {
318
318
  overflow-y: auto;
319
319
  min-height: 0px;
320
320
 
321
+ &:has(.side-menu) {
322
+ padding-left: $app-bar-collapsed-width;
323
+
324
+ .overlay-content-mode {
325
+ left: calc(var(--nav-width) + $app-bar-collapsed-width);
326
+ }
327
+ }
328
+
321
329
  &.pin-right {
322
330
  grid-template-areas:
323
331
  "header header header"
@@ -303,6 +303,7 @@ export default {
303
303
  },
304
304
 
305
305
  'value.region'() {
306
+ this.updateNetwork();
306
307
  this.$fetch();
307
308
  },
308
309
 
@@ -75,9 +75,9 @@ async function $_fetch() { // eslint-disable-line camelcase
75
75
  try {
76
76
  await this.$options.fetch.call(this);
77
77
  } catch (err) {
78
- if (process.dev) {
79
- console.error('Error in fetch():', err); // eslint-disable-line no-console
80
- }
78
+ // In most cases we don't handle errors at all in `fetch`es. Lets always log to help in production
79
+ console.error('Error in fetch():', err); // eslint-disable-line no-console
80
+
81
81
  error = normalizeError(err);
82
82
  }
83
83
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rancher/shell",
3
- "version": "0.3.22",
3
+ "version": "0.3.24",
4
4
  "description": "Rancher Dashboard Shell",
5
5
  "repository": "https://github.com/rancherlabs/dashboard",
6
6
  "license": "Apache-2.0",
@@ -1,5 +1,5 @@
1
- import { shallowMount } from '@vue/test-utils';
2
1
  import Preferences from '@shell/pages/prefs.vue';
2
+ import { shallowMount } from '@vue/test-utils';
3
3
 
4
4
  describe('page: prefs should', () => {
5
5
  it.each([
@@ -27,6 +27,7 @@ export default {
27
27
  <div>
28
28
  <a
29
29
  class="badge-install"
30
+ data-testid="add-custom-cluster-badge"
30
31
  @click="customBadgeDialog"
31
32
  >
32
33
  <i class="icon icon-cluster" />
package/pages/prefs.vue CHANGED
@@ -8,9 +8,11 @@ import ButtonGroup from '@shell/components/ButtonGroup';
8
8
  import { Checkbox } from '@components/Form/Checkbox';
9
9
  import LandingPagePreference from '@shell/components/LandingPagePreference';
10
10
  import {
11
- mapPref, THEME, KEYMAP, DATE_FORMAT, TIME_FORMAT, ROWS_PER_PAGE, HIDE_DESC, SHOW_PRE_RELEASE, MENU_MAX_CLUSTERS,
11
+ mapPref, THEME, KEYMAP, DATE_FORMAT, TIME_FORMAT, ROWS_PER_PAGE, HIDE_DESC, SHOW_PRE_RELEASE,
12
12
  VIEW_IN_API, ALL_NAMESPACES, THEME_SHORTCUT, PLUGIN_DEVELOPER, SCALE_POOL_PROMPT
13
+ , MENU_MAX_CLUSTERS
13
14
  } from '@shell/store/prefs';
15
+
14
16
  import LabeledSelect from '@shell/components/form/LabeledSelect';
15
17
  import { addObject } from '@shell/utils/array';
16
18
  import LocaleSelector from '@shell/components/LocaleSelector';
@@ -34,7 +36,6 @@ export default {
34
36
  perPage: mapPref(ROWS_PER_PAGE),
35
37
  hideDesc: mapPref(HIDE_DESC),
36
38
  showPreRelease: mapPref(SHOW_PRE_RELEASE),
37
- menuMaxClusters: mapPref(MENU_MAX_CLUSTERS),
38
39
  pluginDeveloper: mapPref(PLUGIN_DEVELOPER),
39
40
  scalingDownPrompt: mapPref(SCALE_POOL_PROMPT),
40
41
 
@@ -254,17 +255,6 @@ export default {
254
255
  placeholder="Select a row count"
255
256
  />
256
257
  </div>
257
- <div class="col span-4">
258
- <LabeledSelect
259
- v-model.number="menuMaxClusters"
260
- data-testid="prefs__displaySetting__menuMaxClusters"
261
- :label="t('prefs.clusterToShow.label')"
262
- :options="menuClusterOptions"
263
- option-key="value"
264
- option-label="label"
265
- placeholder="Select a row count"
266
- />
267
- </div>
268
258
  </div>
269
259
  </div>
270
260
  <!-- Confirmation setting -->
@@ -1464,7 +1464,7 @@ export default class Resource {
1464
1464
  }
1465
1465
 
1466
1466
  async saveYaml(yaml) {
1467
- this._saveYaml(yaml);
1467
+ await this._saveYaml(yaml);
1468
1468
  }
1469
1469
 
1470
1470
  async _saveYaml(yaml) {
package/public/index.html CHANGED
@@ -13,8 +13,10 @@
13
13
  <div id="app">
14
14
  <script>
15
15
  (() => {
16
- const isDark = document.cookie.includes('R_THEME=auto') ?
17
- // User selected automatic theme, so use pcs (set when ui previously loaded and is either os theme or time of day based
16
+ // Has the user chosen to auto detect the theme.... or if they haven't chosen anything.. --> check the auto-detected theme via R_PCS
17
+ // Otherwise check if they've specifically selected a theme --> R_THEME
18
+ const isDark = document.cookie.includes('R_THEME=auto') || !document.cookie.includes('R_THEME') ?
19
+ // User selected automatic theme, so use PCS (set when ui previously loaded and is either os theme or time of day based)
18
20
  document.cookie.includes('R_PCS=dark') :
19
21
  // Otherwise user selected light/dark theme directly
20
22
  document.cookie.includes('R_THEME=dark');
@@ -206,6 +206,13 @@ export default (
206
206
  }
207
207
  },
208
208
 
209
+ /**
210
+ * Emit on input change
211
+ */
212
+ onChange(event: Event): void {
213
+ this.$emit('change', event);
214
+ },
215
+
209
216
  /**
210
217
  * Emit on input with delay. Note: Arrow function is avoided due context
211
218
  * binding.
@@ -299,6 +306,7 @@ export default (
299
306
  @input="onInput($event.target.value)"
300
307
  @focus="onFocus"
301
308
  @blur="onBlur"
309
+ @change="onChange"
302
310
  >
303
311
  </slot>
304
312
 
@@ -4,7 +4,7 @@ import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
4
4
 
5
5
  describe('radioButton.vue', () => {
6
6
  it('renders label slot contents', () => {
7
- const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' } });
7
+ const wrapper = shallowMount(RadioButton, { slots: { label: 'Test Label' }, propsData: { val: {}, value: {} } });
8
8
 
9
9
  expect(wrapper.find('.radio-label').text()).toBe('Test Label');
10
10
  });
@@ -14,7 +14,9 @@ describe('radioButton.vue', () => {
14
14
  RadioButton,
15
15
  {
16
16
  directives: { cleanHtmlDirective },
17
- propsData: { label: 'Test Label' }
17
+ propsData: {
18
+ label: 'Test Label', val: {}, value: {}
19
+ }
18
20
  });
19
21
 
20
22
  expect(wrapper.find('.radio-label').text()).toBe('Test Label');
@@ -23,7 +25,9 @@ describe('radioButton.vue', () => {
23
25
  it('renders slot contents when both slot and label prop are provided', () => {
24
26
  const wrapper = shallowMount(RadioButton, {
25
27
  slots: { label: 'Test Label - Slot' },
26
- propsData: { label: 'Test Label - Props' },
28
+ propsData: {
29
+ label: 'Test Label - Props', val: {}, value: {}
30
+ },
27
31
  });
28
32
 
29
33
  expect(wrapper.find('.radio-label').text()).toBe('Test Label - Slot');
File without changes
package/store/prefs.js CHANGED
@@ -1,7 +1,7 @@
1
- import Vue from 'vue';
1
+ import { SETTING } from '@shell/config/settings';
2
2
  import { MANAGEMENT, STEVE } from '@shell/config/types';
3
3
  import { clone } from '@shell/utils/object';
4
- import { SETTING } from '@shell/config/settings';
4
+ import Vue from 'vue';
5
5
 
6
6
  const definitions = {};
7
7
  /**
@@ -115,8 +115,7 @@ export const PROVISIONER = create('provisioner', _RKE2, { options: [_RKE1, _RKE2
115
115
  export const PSP_DEPRECATION_BANNER = create('hide-psp-deprecation-banner', false, { parseJSON });
116
116
 
117
117
  // Maximum number of clusters to show in the slide-in menu
118
- export const MENU_MAX_CLUSTERS = create('menu-max-clusters', 4, { options: [2, 3, 4, 5, 6, 7, 8, 9, 10], parseJSON });
119
-
118
+ export const MENU_MAX_CLUSTERS = 10;
120
119
  // Prompt for confirm when scaling down node pool in GUI and save the pref
121
120
  export const SCALE_POOL_PROMPT = create('scale-pool-prompt', null, { parseJSON });
122
121
  // --------------------
package/store/type-map.js CHANGED
@@ -593,18 +593,8 @@ export const getters = {
593
593
  }
594
594
 
595
595
  const label = typeObj.labelKey ? rootGetters['i18n/t'](typeObj.labelKey) || typeObj.label : typeObj.label;
596
- const virtual = !!typeObj.virtual;
597
- let icon = typeObj.icon;
598
596
 
599
- if ( (!virtual || typeObj.isSpoofed ) && !icon ) {
600
- if ( namespaced ) {
601
- icon = 'folder';
602
- } else {
603
- icon = 'globe';
604
- }
605
- }
606
-
607
- const labelDisplay = highlightLabel(label, icon, typeObj.count, typeObj.schema);
597
+ const labelDisplay = highlightLabel(label, typeObj.count, typeObj.schema);
608
598
 
609
599
  if ( !labelDisplay ) {
610
600
  // Search happens in highlight and returns null if not found
@@ -711,7 +701,7 @@ export const getters = {
711
701
  return group;
712
702
  }
713
703
 
714
- function highlightLabel(original, icon, count, schema) {
704
+ function highlightLabel(original, count, schema) {
715
705
  let label = escapeHtml(original);
716
706
 
717
707
  if ( searchRegex ) {
@@ -735,10 +725,6 @@ export const getters = {
735
725
  }
736
726
  }
737
727
 
738
- if ( icon ) {
739
- label = `<i class="icon icon-fw icon-${ icon }"></i>${ label }`;
740
- }
741
-
742
728
  return label;
743
729
  }
744
730
  };
@@ -2868,7 +2868,7 @@ export const _RKE1: "rke1";
2868
2868
  export const _RKE2: "rke2";
2869
2869
  export const PROVISIONER: any;
2870
2870
  export const PSP_DEPRECATION_BANNER: any;
2871
- export const MENU_MAX_CLUSTERS: any;
2871
+ export const MENU_MAX_CLUSTERS: 10;
2872
2872
  export const SCALE_POOL_PROMPT: any;
2873
2873
  export function state(): {
2874
2874
  cookiesLoaded: boolean;
@@ -3376,6 +3376,12 @@ export function haveSetFavIcon(): boolean;
3376
3376
  export function setFavIcon(store: any): void;
3377
3377
  }
3378
3378
 
3379
+ // @shell/utils/formatter
3380
+
3381
+ declare module '@shell/utils/formatter' {
3382
+ export function formatEncryptionSecretNames(secrets: any, chartNamespace: any): any;
3383
+ }
3384
+
3379
3385
  // @shell/utils/grafana
3380
3386
 
3381
3387
  declare module '@shell/utils/grafana' {
@@ -3902,6 +3908,9 @@ export function splitObjectPath(path: any): any;
3902
3908
  export function joinObjectPath(ary: any): string;
3903
3909
  export function shortenedImage(image: any): any;
3904
3910
  export function isIpv4(ip: any): boolean;
3911
+ export function sanitizeKey(k: any): any;
3912
+ export function sanitizeValue(v: any): any;
3913
+ export function sanitizeIP(v: any): any;
3905
3914
  export namespace CHARSET {
3906
3915
  export { num as NUMERIC };
3907
3916
  export const NO_VOWELS: string;
@@ -0,0 +1,77 @@
1
+ import { formatEncryptionSecretNames } from '@shell/utils/formatter';
2
+
3
+ describe('formatter', () => {
4
+ const secrets = [
5
+ {
6
+ id: 'test5',
7
+ _type: 'Opaque',
8
+ data: { hash: 'test5', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
9
+ metadata: {
10
+ name: 'test5',
11
+ namespace: 'test',
12
+ state: {
13
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
14
+ }
15
+ }
16
+ },
17
+ {
18
+ id: 'test2',
19
+ _type: 'Opaque',
20
+ data: { hash: 'test', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
21
+ metadata: {
22
+ name: 'test2',
23
+ namespace: 'test',
24
+ state: {
25
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
26
+ }
27
+ }
28
+ },
29
+ {
30
+ id: 'test4',
31
+ _type: 'Opaque',
32
+ data: { hash: 'test4' },
33
+ metadata: {
34
+ name: 'test4',
35
+ namespace: 'test',
36
+ state: {
37
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
38
+ }
39
+ }
40
+ },
41
+ {
42
+ id: 'test1',
43
+ _type: 'Custom',
44
+ data: { hash: 'test1', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
45
+ metadata: {
46
+ name: 'test1',
47
+ namespace: 'test',
48
+ state: {
49
+ error: false, message: 'Resource is always ready', name: 'active', transitioning: false
50
+ },
51
+ }
52
+ },
53
+ {
54
+ id: 'test6',
55
+ _type: 'Opaque',
56
+ data: { hash: 'test5', 'encryption-provider-config.yaml': 'MTIzNFFhYWEh' },
57
+ metadata: {
58
+ name: 'test5',
59
+ namespace: 'test',
60
+ state: {
61
+ error: true, message: 'Failed', name: 'active', transitioning: true
62
+ }
63
+ }
64
+ }];
65
+ const chart = 'test';
66
+
67
+ it.each([[chart, 2], ['test1', 0]])('should show correct number of secrets', (chartVal: string, result: number) => {
68
+ const res = formatEncryptionSecretNames(secrets, chartVal);
69
+
70
+ expect(res).toHaveLength(result);
71
+ });
72
+ it('should return correct results in a correct order', () => {
73
+ const res = formatEncryptionSecretNames(secrets, chart);
74
+
75
+ expect(res).toStrictEqual(['test2', 'test5']);
76
+ });
77
+ });
@@ -0,0 +1,61 @@
1
+ import { sortBy } from '@shell/utils/sort';
2
+
3
+ describe('fx: sort', () => {
4
+ describe('sortBy', () => {
5
+ const testSortBy = <T = object[]>(ary: T[], key: string[], expected: T[], desc?: boolean) => {
6
+ const result = sortBy(ary, key, desc);
7
+
8
+ expect(result).toStrictEqual(expected);
9
+ };
10
+
11
+ it.each([
12
+ [[{ a: 1 }, { a: 9 }], ['a'], [{ a: 1 }, { a: 9 }]],
13
+ [[{ a: 2 }, { a: 1 }], ['a'], [{ a: 1 }, { a: 2 }]],
14
+ ])('should sort by single property', (ary, key, expected) => {
15
+ testSortBy(ary, key, expected);
16
+ });
17
+
18
+ it.each([
19
+ [[{ a: 1, b: 1 }, { a: 9, b: 9 }], ['a', 'b'], [{ a: 1, b: 1 }, { a: 9, b: 9 }]],
20
+ [[{ a: 2, b: 2 }, { a: 1, b: 1 }], ['a', 'b'], [{ a: 1, b: 1 }, { a: 2, b: 2 }]],
21
+ [[{ a: 2, b: 1 }, { a: 1, b: 9 }], ['a', 'b'], [{ a: 1, b: 9 }, { a: 2, b: 1 }]],
22
+ [[{ a: 1, b: 2 }, { a: 9, b: 1 }], ['a', 'b'], [{ a: 1, b: 2 }, { a: 9, b: 1 }]],
23
+ [[{ a: 1, b: 1 }, { a: 9, b: 9 }], ['a', 'b'], [{ a: 1, b: 1 }, { a: 9, b: 9 }]],
24
+ ])('should sort by two properties (primary property always first)', (ary, key, expected) => {
25
+ testSortBy(ary, key, expected);
26
+ });
27
+
28
+ it.each([
29
+ [[{ a: 1, b: 1 }, { a: 1, b: 9 }], ['a', 'b'], [{ a: 1, b: 1 }, { a: 1, b: 9 }]],
30
+ [[{ a: 1, b: 2 }, { a: 1, b: 1 }], ['a', 'b'], [{ a: 1, b: 1 }, { a: 1, b: 2 }]],
31
+ ])('should sort by two properties (primary property the same)', (ary, key, expected) => {
32
+ testSortBy(ary, key, expected);
33
+ });
34
+
35
+ describe('descending', () => {
36
+ it.each([
37
+ [[{ a: 1 }, { a: 9 }], ['a'], [{ a: 9 }, { a: 1 }]],
38
+ [[{ a: 2 }, { a: 1 }], ['a'], [{ a: 2 }, { a: 1 }]],
39
+ ])('should sort by single property', (ary, key, expected) => {
40
+ testSortBy(ary, key, expected, true);
41
+ });
42
+
43
+ it.each([
44
+ [[{ a: 1, b: 1 }, { a: 9, b: 9 }], ['a', 'b'], [{ a: 9, b: 9 }, { a: 1, b: 1 }]],
45
+ [[{ a: 2, b: 2 }, { a: 1, b: 1 }], ['a', 'b'], [{ a: 2, b: 2 }, { a: 1, b: 1 }]],
46
+ [[{ a: 2, b: 1 }, { a: 1, b: 9 }], ['a', 'b'], [{ a: 2, b: 1 }, { a: 1, b: 9 }]],
47
+ [[{ a: 1, b: 2 }, { a: 9, b: 1 }], ['a', 'b'], [{ a: 9, b: 1 }, { a: 1, b: 2 }]],
48
+ [[{ a: 1, b: 1 }, { a: 9, b: 9 }], ['a', 'b'], [{ a: 9, b: 9 }, { a: 1, b: 1 }]],
49
+ ])('should sort by two properties', (ary, key, expected) => {
50
+ testSortBy(ary, key, expected, true);
51
+ });
52
+
53
+ it.each([
54
+ [[{ a: 1, b: 1 }, { a: 1, b: 9 }], ['a', 'b'], [{ a: 1, b: 9 }, { a: 1, b: 1 }]],
55
+ [[{ a: 1, b: 2 }, { a: 1, b: 1 }], ['a', 'b'], [{ a: 1, b: 2 }, { a: 1, b: 1 }]],
56
+ ])('should sort by two properties (primary property the same)', (ary, key, expected) => {
57
+ testSortBy(ary, key, expected, true);
58
+ });
59
+ });
60
+ });
61
+ });
@@ -0,0 +1,11 @@
1
+
2
+ import { SECRET_TYPES } from '@shell/config/secret';
3
+
4
+ export function formatEncryptionSecretNames(secrets, chartNamespace) {
5
+ return secrets.filter(
6
+ (secret) => (secret.data || {})['encryption-provider-config.yaml'] &&
7
+ secret.metadata.namespace === chartNamespace &&
8
+ !secret.metadata?.state?.error &&
9
+ secret._type === SECRET_TYPES.OPAQUE
10
+ ).map((secret) => secret.metadata.name).sort();
11
+ }
package/utils/string.js CHANGED
@@ -309,3 +309,15 @@ export function isIpv4(ip) {
309
309
 
310
310
  return reg.test(ip);
311
311
  }
312
+
313
+ export function sanitizeKey(k) {
314
+ return (k || '').replace(/[^a-z0-9./_-]/ig, '');
315
+ }
316
+
317
+ export function sanitizeValue(v) {
318
+ return (v || '').replace(/[^a-z0-9._-]/ig, '');
319
+ }
320
+
321
+ export function sanitizeIP(v) {
322
+ return (v || '').replace(/[^a-z0-9.:_-]/ig, '');
323
+ }
package/vue.config.js CHANGED
@@ -72,7 +72,9 @@ module.exports = function(dir, _appConfig) {
72
72
  ];
73
73
 
74
74
  if (instrumentCode) {
75
- babelPlugins.push('babel-plugin-istanbul');
75
+ babelPlugins.push([
76
+ 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
77
+ ]);
76
78
 
77
79
  console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
78
80
  }
@@ -248,10 +250,6 @@ module.exports = function(dir, _appConfig) {
248
250
  console.log(`Version: ${ dashboardVersion }`); // eslint-disable-line no-console
249
251
  }
250
252
 
251
- if ( !dev ) {
252
- console.log(`Version: ${ dashboardVersion }`); // eslint-disable-line no-console
253
- }
254
-
255
253
  if ( resourceBase ) {
256
254
  console.log(`Resource Base URL: ${ resourceBase }`); // eslint-disable-line no-console
257
255
  }
@@ -408,6 +406,7 @@ module.exports = function(dir, _appConfig) {
408
406
  rancherEnv,
409
407
  dashboardVersion
410
408
  }),
409
+
411
410
  }));
412
411
 
413
412
  // The static assets need to be in the built assets directory in order to get served (primarily the favicon)
@@ -451,7 +450,9 @@ module.exports = function(dir, _appConfig) {
451
450
  ];
452
451
 
453
452
  if (instrumentCode) {
454
- babelPlugins.push('babel-plugin-istanbul');
453
+ babelPlugins.push([
454
+ 'babel-plugin-istanbul', { extension: ['.js', '.vue'] }, 'add-vue'
455
+ ]);
455
456
 
456
457
  console.warn('Instrumenting code for coverage'); // eslint-disable-line no-console
457
458
  }