@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.
- package/assets/styles/base/_variables.scss +1 -0
- package/assets/styles/themes/_dark.scss +1 -0
- package/assets/styles/themes/_light.scss +6 -5
- package/assets/translations/en-us.yaml +15 -10
- package/assets/translations/zh-hans.yaml +1 -1
- package/babel.config.js +3 -0
- package/components/ClusterProviderIconMenu.vue +161 -0
- package/components/Loading.vue +1 -1
- package/components/SideNav.vue +1 -1
- package/components/SortableTable/paging.js +10 -0
- package/components/form/GitPicker.vue +16 -0
- package/components/form/SelectOrCreateAuthSecret.vue +16 -3
- package/components/nav/Group.vue +54 -24
- package/components/nav/Header.vue +1 -1
- package/components/nav/TopLevelMenu.vue +469 -294
- package/components/nav/Type.vue +31 -5
- package/creators/pkg/init +2 -2
- package/edit/fleet.cattle.io.gitrepo.vue +43 -15
- package/edit/logging.banzaicloud.io.output/index.vue +7 -0
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +3 -8
- package/edit/provisioning.cattle.io.cluster/rke2.vue +108 -33
- package/edit/resources.cattle.io.backup.vue +3 -1
- package/edit/resources.cattle.io.restore.vue +3 -1
- package/edit/workload/storage/ContainerMountPaths.vue +7 -5
- package/initialize/App.js +2 -0
- package/initialize/client.js +63 -51
- package/initialize/index.js +2 -0
- package/layouts/default.vue +8 -0
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/fetch.client.js +3 -3
- package/package.json +1 -1
- package/pages/__tests__/prefs.test.ts +1 -1
- package/pages/c/_cluster/explorer/ConfigBadge.vue +1 -0
- package/pages/prefs.vue +3 -13
- package/plugins/dashboard-store/resource-class.js +1 -1
- package/public/index.html +4 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +8 -0
- package/rancher-components/Form/Radio/RadioButton.test.ts +7 -3
- package/scripts/extension/parse-tag-name +0 -0
- package/store/prefs.js +3 -4
- package/store/type-map.js +2 -16
- package/types/shell/index.d.ts +10 -1
- package/utils/__tests__/formatter.test.ts +77 -0
- package/utils/__tests__/sort.test.ts +61 -0
- package/utils/formatter.js +11 -0
- package/utils/string.js +12 -0
- package/vue.config.js +7 -6
- package/yarn-error.log +16 -16
package/initialize/client.js
CHANGED
|
@@ -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
|
-
|
|
60
|
+
if (debug) {
|
|
61
|
+
const logs = NUXT.logs || [];
|
|
55
62
|
|
|
56
|
-
if (logs.length > 0) {
|
|
57
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
72
|
+
// Setup global Vue error handler
|
|
73
|
+
if (!Vue.config.$nuxt) {
|
|
74
|
+
const defaultErrorHandler = Vue.config.errorHandler;
|
|
68
75
|
|
|
69
|
-
|
|
76
|
+
Vue.config.errorHandler = async(err, vm, info, ...rest) => {
|
|
70
77
|
// Call other handler if exist
|
|
71
|
-
|
|
78
|
+
let handled = null;
|
|
72
79
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
if (vm && vm.$root) {
|
|
88
|
+
const nuxtApp = Object.keys(Vue.config.$nuxt)
|
|
89
|
+
.find((nuxtInstance) => vm.$root[nuxtInstance]);
|
|
83
90
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
91
|
+
// Show Nuxt Error Page
|
|
92
|
+
if (nuxtApp && vm.$root[nuxtApp].error && info !== 'render function') {
|
|
93
|
+
const currentApp = vm.$root[nuxtApp];
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
// Load error layout
|
|
96
|
+
let layout = (NuxtError.options || NuxtError).layout;
|
|
90
97
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
106
|
+
currentApp.error(err);
|
|
107
|
+
}
|
|
100
108
|
}
|
|
101
|
-
}
|
|
102
109
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
110
|
+
if (typeof defaultErrorHandler === 'function') {
|
|
111
|
+
return handled;
|
|
112
|
+
}
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
798
|
-
|
|
807
|
+
if (isDev) {
|
|
808
|
+
// Enable hot reloading
|
|
809
|
+
hotReloadAPI(_app);
|
|
810
|
+
}
|
|
799
811
|
});
|
|
800
812
|
};
|
|
801
813
|
|
package/initialize/index.js
CHANGED
package/layouts/default.vue
CHANGED
|
@@ -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"
|
package/mixins/fetch.client.js
CHANGED
|
@@ -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
|
-
|
|
79
|
-
|
|
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
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,
|
|
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 -->
|
package/public/index.html
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
<div id="app">
|
|
14
14
|
<script>
|
|
15
15
|
(() => {
|
|
16
|
-
|
|
17
|
-
|
|
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: {
|
|
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: {
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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,
|
|
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
|
};
|
package/types/shell/index.d.ts
CHANGED
|
@@ -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:
|
|
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(
|
|
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(
|
|
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
|
}
|