@rancher/shell 0.3.26 → 0.3.28
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/translations/en-us.yaml +8 -23
- package/assets/translations/zh-hans.yaml +2 -26
- package/chart/gatekeeper.vue +2 -11
- package/chart/istio.vue +1 -10
- package/chart/logging/index.vue +2 -11
- package/chart/monitoring/index.vue +1 -9
- package/chart/rancher-backup/index.vue +1 -9
- package/components/AlertTable.vue +8 -6
- package/components/Carousel.vue +2 -1
- package/components/EmberPage.vue +2 -2
- package/components/EtcdInfoBanner.vue +12 -2
- package/components/GlobalRoleBindings.vue +10 -0
- package/components/GrafanaDashboard.vue +8 -3
- package/components/Wizard.vue +17 -1
- package/components/auth/RoleDetailEdit.vue +17 -1
- package/components/form/ArrayList.vue +20 -11
- package/components/form/__tests__/ArrayList.test.ts +44 -0
- package/components/formatter/ClusterProvider.vue +1 -18
- package/components/nav/Header.vue +5 -4
- package/components/nav/TopLevelMenu.vue +38 -15
- package/components/nav/WindowManager/ContainerLogs.vue +22 -19
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -0
- package/components/nav/__tests__/Type.test.ts +139 -0
- package/config/private-label.js +1 -1
- package/config/product/manager.js +0 -13
- package/config/settings.ts +0 -2
- package/config/types.js +0 -4
- package/core/types.ts +11 -4
- package/edit/management.cattle.io.project.vue +1 -52
- package/edit/management.cattle.io.setting.vue +31 -2
- package/edit/provisioning.cattle.io.cluster/Basics.vue +19 -107
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.tests.ts +0 -3
- package/edit/provisioning.cattle.io.cluster/rke2.vue +3 -128
- package/edit/workload/mixins/workload.js +14 -4
- package/middleware/authenticated.js +4 -2
- package/models/__tests__/management.cattle.io.cluster.test.ts +19 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +90 -0
- package/models/cluster.x-k8s.io.machine.js +1 -1
- package/models/fleet.cattle.io.cluster.js +11 -1
- package/models/management.cattle.io.cluster.js +4 -0
- package/models/management.cattle.io.project.js +0 -36
- package/models/management.cattle.io.setting.js +11 -7
- package/models/provisioning.cattle.io.cluster.js +16 -4
- package/package.json +1 -1
- package/pages/auth/setup.vue +38 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.helper.test.ts +2 -17
- package/pages/c/_cluster/apps/charts/index.vue +0 -15
- package/pages/c/_cluster/apps/charts/install.helpers.js +2 -13
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/auth/roles/index.vue +11 -1
- package/pages/c/_cluster/explorer/index.vue +7 -49
- package/pages/c/_cluster/manager/pages/_page.vue +4 -5
- package/pages/c/_cluster/monitoring/index.vue +26 -39
- package/pages/support/index.vue +1 -8
- package/promptRemove/management.cattle.io.project.vue +6 -9
- package/rancher-components/BadgeState/BadgeState.vue +1 -5
- package/rancher-components/Banner/Banner.test.ts +1 -51
- package/rancher-components/Banner/Banner.vue +53 -134
- package/rancher-components/Card/Card.vue +7 -24
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +29 -20
- package/rancher-components/Form/Checkbox/Checkbox.vue +20 -45
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +8 -2
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +10 -22
- package/rancher-components/Form/Radio/RadioButton.vue +13 -30
- package/rancher-components/Form/Radio/RadioGroup.vue +7 -26
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +6 -7
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +38 -25
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +11 -23
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +5 -19
- package/rancher-components/StringList/StringList.test.ts +49 -453
- package/rancher-components/StringList/StringList.vue +58 -92
- package/rancher-components/components/Form/Radio/RadioGroup.test.ts +30 -0
- package/rancher-components/components/Form/Radio/RadioGroup.vue +4 -0
- package/rancher-components/components/StringList/StringList.test.ts +270 -0
- package/rancher-components/components/StringList/StringList.vue +57 -18
- package/store/features.js +1 -0
- package/store/prefs.js +0 -3
- package/types/shell/index.d.ts +26 -17
- package/utils/__tests__/object.test.ts +67 -1
- package/utils/__tests__/version.test.ts +13 -23
- package/utils/cluster.js +1 -1
- package/utils/custom-validators.js +0 -2
- package/utils/error.js +16 -1
- package/utils/grafana.js +1 -2
- package/utils/monitoring.js +25 -1
- package/utils/object.js +4 -3
- package/utils/sort.js +1 -1
- package/utils/validators/formRules/__tests__/index.test.ts +49 -4
- package/utils/validators/formRules/index.ts +13 -10
- package/utils/validators/role-template.js +1 -1
- package/utils/validators/setting.js +6 -10
- package/utils/version.js +0 -13
- package/components/ChartPsp.vue +0 -76
- package/components/__tests__/ChartPsp.test.ts +0 -75
- package/components/formatter/__tests__/ClusterProvider.test.ts +0 -28
- package/rancher-components/Card/Card.test.ts +0 -37
- package/rancher-components/Form/Radio/RadioButton.test.ts +0 -31
- package/yarn-error.log +0 -200
|
@@ -82,6 +82,13 @@ export default Vue.extend({
|
|
|
82
82
|
return {} as ErrorMessages;
|
|
83
83
|
},
|
|
84
84
|
},
|
|
85
|
+
/**
|
|
86
|
+
* Enables bulk addition and defines the delimiter to split the input string.
|
|
87
|
+
*/
|
|
88
|
+
bulkAdditionDelimiter: {
|
|
89
|
+
type: RegExp,
|
|
90
|
+
default: null,
|
|
91
|
+
}
|
|
85
92
|
},
|
|
86
93
|
data() {
|
|
87
94
|
return {
|
|
@@ -125,13 +132,9 @@ export default Vue.extend({
|
|
|
125
132
|
},
|
|
126
133
|
|
|
127
134
|
methods: {
|
|
128
|
-
onChange(value: string) {
|
|
135
|
+
onChange(value: string, index?: number) {
|
|
129
136
|
this.value = value;
|
|
130
|
-
|
|
131
|
-
const items = [
|
|
132
|
-
...this.items,
|
|
133
|
-
this.value
|
|
134
|
-
];
|
|
137
|
+
const items = this.addValueToItems(this.items, value, index);
|
|
135
138
|
|
|
136
139
|
this.toggleError(
|
|
137
140
|
'duplicate',
|
|
@@ -321,10 +324,7 @@ export default Vue.extend({
|
|
|
321
324
|
const value = this.value?.trim();
|
|
322
325
|
|
|
323
326
|
if (value) {
|
|
324
|
-
const items =
|
|
325
|
-
...this.items,
|
|
326
|
-
value,
|
|
327
|
-
];
|
|
327
|
+
const items = this.addValueToItems(this.items, value);
|
|
328
328
|
|
|
329
329
|
if (!hasDuplicatedStrings(items, this.caseSensitive)) {
|
|
330
330
|
this.updateItems(items);
|
|
@@ -343,12 +343,8 @@ export default Vue.extend({
|
|
|
343
343
|
const value = this.value?.trim();
|
|
344
344
|
|
|
345
345
|
if (value) {
|
|
346
|
-
const
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
if (index !== -1) {
|
|
350
|
-
items[index] = value;
|
|
351
|
-
}
|
|
346
|
+
const index = findStringIndex(this.items, item, false);
|
|
347
|
+
const items = index !== -1 ? this.addValueToItems(this.items, value, index) : this.items;
|
|
352
348
|
|
|
353
349
|
if (!hasDuplicatedStrings(items, this.caseSensitive)) {
|
|
354
350
|
this.updateItems(items);
|
|
@@ -360,6 +356,49 @@ export default Vue.extend({
|
|
|
360
356
|
}
|
|
361
357
|
},
|
|
362
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Add a new or update an exiting item in the items list.
|
|
361
|
+
*
|
|
362
|
+
* @param items The current items list.
|
|
363
|
+
* @param value The new value to be added.
|
|
364
|
+
* @param index The list index of the item to be updated (optional).
|
|
365
|
+
* @returns Updated items list.
|
|
366
|
+
*/
|
|
367
|
+
addValueToItems(items: string[], value: string, index?: number): string[] {
|
|
368
|
+
const updatedItems = [...items];
|
|
369
|
+
|
|
370
|
+
// Add new item
|
|
371
|
+
if (index === undefined) {
|
|
372
|
+
if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) {
|
|
373
|
+
updatedItems.push(...this.splitBulkValue(value));
|
|
374
|
+
} else {
|
|
375
|
+
updatedItems.push(value);
|
|
376
|
+
}
|
|
377
|
+
} else { // Update existing item
|
|
378
|
+
if (this.bulkAdditionDelimiter && value.search(this.bulkAdditionDelimiter) >= 0) {
|
|
379
|
+
updatedItems.splice(index, 1, ...this.splitBulkValue(value));
|
|
380
|
+
} else {
|
|
381
|
+
updatedItems[index] = value;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return updatedItems;
|
|
386
|
+
},
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Split the value by the defined delimiter and remove empty strings.
|
|
390
|
+
*
|
|
391
|
+
* @param value The value to be split.
|
|
392
|
+
* @returns Array containing split values.
|
|
393
|
+
*/
|
|
394
|
+
splitBulkValue(value: string): string[] {
|
|
395
|
+
return value
|
|
396
|
+
.split(this.bulkAdditionDelimiter)
|
|
397
|
+
.filter((item) => {
|
|
398
|
+
return item.trim().length > 0;
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
|
|
363
402
|
/**
|
|
364
403
|
* Remove an item from items list
|
|
365
404
|
*/
|
|
@@ -393,7 +432,7 @@ export default Vue.extend({
|
|
|
393
432
|
@dblclick="onClickEmptyBody()"
|
|
394
433
|
>
|
|
395
434
|
<div
|
|
396
|
-
v-for="item in items"
|
|
435
|
+
v-for="(item, index) in items"
|
|
397
436
|
:key="item"
|
|
398
437
|
:ref="item"
|
|
399
438
|
:class="{
|
|
@@ -421,7 +460,7 @@ export default Vue.extend({
|
|
|
421
460
|
:data-testid="`item-edit-${item}`"
|
|
422
461
|
class="edit-input static"
|
|
423
462
|
:value="value != null ? value : item"
|
|
424
|
-
@input="onChange($event)"
|
|
463
|
+
@input="onChange($event, index)"
|
|
425
464
|
@blur.prevent="updateItem(item)"
|
|
426
465
|
@keydown.native.enter="updateItem(item, !errors.duplicate)"
|
|
427
466
|
/>
|
package/store/features.js
CHANGED
|
@@ -31,6 +31,7 @@ export const UNSUPPORTED_STORAGE_DRIVERS = create('unsupported-storage-drivers',
|
|
|
31
31
|
export const FLEET = create('continuous-delivery', true);
|
|
32
32
|
export const HARVESTER = create('harvester', true);
|
|
33
33
|
export const HARVESTER_CONTAINER = create('harvester-baremetal-container-workload', false);
|
|
34
|
+
export const FLEET_WORKSPACE_BACK = create('provisioningv2-fleet-workspace-back-population', false);
|
|
34
35
|
|
|
35
36
|
// Not currently used.. no point defining ones we don't use
|
|
36
37
|
// export const EMBEDDED_CLUSTER_API = create('embedded-cluster-api', true);
|
package/store/prefs.js
CHANGED
|
@@ -112,9 +112,6 @@ export const _RKE1 = 'rke1';
|
|
|
112
112
|
export const _RKE2 = 'rke2';
|
|
113
113
|
export const PROVISIONER = create('provisioner', _RKE2, { options: [_RKE1, _RKE2] });
|
|
114
114
|
|
|
115
|
-
// Promo for Pod Security Policies (PSPs) being deprecated on kube version 1.25 on Cluster Dashboard page
|
|
116
|
-
export const PSP_DEPRECATION_BANNER = create('hide-psp-deprecation-banner', false, { parseJSON });
|
|
117
|
-
|
|
118
115
|
// Maximum number of clusters to show in the slide-in menu
|
|
119
116
|
export const MENU_MAX_CLUSTERS = 10;
|
|
120
117
|
// Prompt for confirm when scaling down node pool in GUI and save the pref
|
package/types/shell/index.d.ts
CHANGED
|
@@ -1765,8 +1765,6 @@ export const NODE: "node";
|
|
|
1765
1765
|
export const NETWORK_POLICY: "networking.k8s.io.networkpolicy";
|
|
1766
1766
|
export const POD: "pod";
|
|
1767
1767
|
export const POD_DISRUPTION_BUDGET: "policy.poddisruptionbudget";
|
|
1768
|
-
export const PSP: "policy.podsecuritypolicy";
|
|
1769
|
-
export const PSPS: "policy.podsecuritypolicies";
|
|
1770
1768
|
export const PV: "persistentvolume";
|
|
1771
1769
|
export const PVC: "persistentvolumeclaim";
|
|
1772
1770
|
export const RESOURCE_QUOTA: "resourcequota";
|
|
@@ -1884,8 +1882,6 @@ export namespace MANAGEMENT {
|
|
|
1884
1882
|
export { GLOBAL_ROLE_1 as GLOBAL_ROLE };
|
|
1885
1883
|
const GLOBAL_ROLE_BINDING_1: string;
|
|
1886
1884
|
export { GLOBAL_ROLE_BINDING_1 as GLOBAL_ROLE_BINDING };
|
|
1887
|
-
export const POD_SECURITY_POLICY_TEMPLATE: string;
|
|
1888
|
-
export const PSP_TEMPLATE_BINDING: string;
|
|
1889
1885
|
export const PSA: string;
|
|
1890
1886
|
export const MANAGED_CHART: string;
|
|
1891
1887
|
export const USER_NOTIFICATION: string;
|
|
@@ -2807,6 +2803,7 @@ export const UNSUPPORTED_STORAGE_DRIVERS: any;
|
|
|
2807
2803
|
export const FLEET: any;
|
|
2808
2804
|
export const HARVESTER: any;
|
|
2809
2805
|
export const HARVESTER_CONTAINER: any;
|
|
2806
|
+
export const FLEET_WORKSPACE_BACK: any;
|
|
2810
2807
|
export namespace getters {
|
|
2811
2808
|
function get(state: any, getters: any, rootState: any, rootGetters: any): (name: any) => any;
|
|
2812
2809
|
}
|
|
@@ -2868,7 +2865,6 @@ export const PLUGIN_DEVELOPER: any;
|
|
|
2868
2865
|
export const _RKE1: "rke1";
|
|
2869
2866
|
export const _RKE2: "rke2";
|
|
2870
2867
|
export const PROVISIONER: any;
|
|
2871
|
-
export const PSP_DEPRECATION_BANNER: any;
|
|
2872
2868
|
export const MENU_MAX_CLUSTERS: 10;
|
|
2873
2869
|
export const SCALE_POOL_PROMPT: any;
|
|
2874
2870
|
export function state(): {
|
|
@@ -3300,7 +3296,6 @@ declare namespace _default {
|
|
|
3300
3296
|
export { cronSchedule };
|
|
3301
3297
|
export { podAffinity };
|
|
3302
3298
|
export { roleTemplateRules };
|
|
3303
|
-
export { isHttps };
|
|
3304
3299
|
}
|
|
3305
3300
|
export default _default;
|
|
3306
3301
|
}
|
|
@@ -3360,8 +3355,17 @@ declare module '@shell/utils/error' {
|
|
|
3360
3355
|
export function stringify(err: any): any;
|
|
3361
3356
|
export function exceptionToErrorsArray(err: any): any;
|
|
3362
3357
|
export class ClusterNotFoundError extends Error {
|
|
3358
|
+
static name: string;
|
|
3363
3359
|
constructor(message: any);
|
|
3364
3360
|
}
|
|
3361
|
+
/**
|
|
3362
|
+
* An error occurred and the user should be redirected to a certain location (where this is handled)
|
|
3363
|
+
*/
|
|
3364
|
+
export class RedirectToError extends Error {
|
|
3365
|
+
static name: string;
|
|
3366
|
+
constructor(message: any, url: any);
|
|
3367
|
+
url: any;
|
|
3368
|
+
}
|
|
3365
3369
|
export class ApiError extends Error {
|
|
3366
3370
|
constructor(res: any);
|
|
3367
3371
|
status: any;
|
|
@@ -3444,6 +3448,9 @@ export function monitoringStatus(): {
|
|
|
3444
3448
|
export function haveV2Monitoring(getters: any): boolean;
|
|
3445
3449
|
export function haveV1Monitoring(getters: any): boolean;
|
|
3446
3450
|
export function haveV1MonitoringWorkloads(store: any): Promise<boolean>;
|
|
3451
|
+
export function canViewGrafanaLink(store: any): Promise<boolean>;
|
|
3452
|
+
export function canViewAlertManagerLink(store: any): Promise<boolean>;
|
|
3453
|
+
export function canViewPrometheusLink(store: any): Promise<boolean>;
|
|
3447
3454
|
}
|
|
3448
3455
|
|
|
3449
3456
|
// @shell/utils/namespace-filter
|
|
@@ -3614,35 +3621,35 @@ export namespace KEY {
|
|
|
3614
3621
|
}
|
|
3615
3622
|
}
|
|
3616
3623
|
|
|
3617
|
-
// @shell/utils/poller
|
|
3624
|
+
// @shell/utils/poller
|
|
3618
3625
|
|
|
3619
|
-
declare module '@shell/utils/poller
|
|
3620
|
-
export default class
|
|
3626
|
+
declare module '@shell/utils/poller' {
|
|
3627
|
+
export default class Poller {
|
|
3621
3628
|
constructor(fn: any, pollRateMs: any, maxRetries?: number);
|
|
3622
3629
|
fn: any;
|
|
3623
3630
|
pollRateMs: any;
|
|
3624
3631
|
maxRetries: number;
|
|
3625
|
-
|
|
3632
|
+
intervalId: any;
|
|
3626
3633
|
tryCount: number;
|
|
3627
3634
|
start(): void;
|
|
3628
3635
|
stop(): void;
|
|
3629
|
-
_poll(): void;
|
|
3630
3636
|
_intervalMethod(): Promise<void>;
|
|
3631
3637
|
}
|
|
3632
3638
|
}
|
|
3633
3639
|
|
|
3634
|
-
// @shell/utils/poller
|
|
3640
|
+
// @shell/utils/poller-sequential
|
|
3635
3641
|
|
|
3636
|
-
declare module '@shell/utils/poller' {
|
|
3637
|
-
export default class
|
|
3642
|
+
declare module '@shell/utils/poller-sequential' {
|
|
3643
|
+
export default class PollerSequential {
|
|
3638
3644
|
constructor(fn: any, pollRateMs: any, maxRetries?: number);
|
|
3639
3645
|
fn: any;
|
|
3640
3646
|
pollRateMs: any;
|
|
3641
3647
|
maxRetries: number;
|
|
3642
|
-
|
|
3648
|
+
timeoutId: any;
|
|
3643
3649
|
tryCount: number;
|
|
3644
3650
|
start(): void;
|
|
3645
3651
|
stop(): void;
|
|
3652
|
+
_poll(): void;
|
|
3646
3653
|
_intervalMethod(): Promise<void>;
|
|
3647
3654
|
}
|
|
3648
3655
|
}
|
|
@@ -4169,7 +4176,10 @@ export function externalName(spec: any, getters: any, errors: any, validatorArgs
|
|
|
4169
4176
|
// @shell/utils/validators/setting
|
|
4170
4177
|
|
|
4171
4178
|
declare module '@shell/utils/validators/setting' {
|
|
4172
|
-
export function
|
|
4179
|
+
export function isServerUrl(value: any): boolean;
|
|
4180
|
+
export function isHttps(value: any): any;
|
|
4181
|
+
export function isLocalhost(value: any): boolean;
|
|
4182
|
+
export function hasTrailingForwardSlash(value: any): any;
|
|
4173
4183
|
}
|
|
4174
4184
|
|
|
4175
4185
|
// @shell/utils/version
|
|
@@ -4188,7 +4198,6 @@ export function seenReleaseNotes(store: any): boolean;
|
|
|
4188
4198
|
export function markSeenReleaseNotes(store: any): Promise<void>;
|
|
4189
4199
|
export function readReleaseNotes(store: any): boolean;
|
|
4190
4200
|
export function markReadReleaseNotes(store: any): Promise<void>;
|
|
4191
|
-
export function generateSupportLink(version: any): string;
|
|
4192
4201
|
}
|
|
4193
4202
|
|
|
4194
4203
|
// @shell/utils/width
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
-
clone, get, getter, isEmpty, toDictionary, remove
|
|
2
|
+
clone, get, getter, isEmpty, toDictionary, remove, diff, definedKeys
|
|
3
3
|
} from '@shell/utils/object';
|
|
4
4
|
|
|
5
5
|
describe('fx: get', () => {
|
|
@@ -161,3 +161,69 @@ describe('fx: remove', () => {
|
|
|
161
161
|
expect(result).toStrictEqual(expected);
|
|
162
162
|
});
|
|
163
163
|
});
|
|
164
|
+
|
|
165
|
+
describe('fx: diff', () => {
|
|
166
|
+
it('should return an object including only the differences between two objects', () => {
|
|
167
|
+
const from = {
|
|
168
|
+
foo: 'bar',
|
|
169
|
+
baz: 'bang',
|
|
170
|
+
};
|
|
171
|
+
const to = {
|
|
172
|
+
foo: 'bar',
|
|
173
|
+
bang: 'baz'
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const result = diff(from, to);
|
|
177
|
+
const expected = {
|
|
178
|
+
baz: null,
|
|
179
|
+
bang: 'baz'
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
expect(result).toStrictEqual(expected);
|
|
183
|
+
});
|
|
184
|
+
it('should return an object and dot characters in object should still be respected', () => {
|
|
185
|
+
const from = {};
|
|
186
|
+
const to = { foo: { 'bar.baz': 'bang' } };
|
|
187
|
+
|
|
188
|
+
const result = diff(from, to);
|
|
189
|
+
const expected = { foo: { 'bar.baz': 'bang' } };
|
|
190
|
+
|
|
191
|
+
expect(result).toStrictEqual(expected);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('fx: definedKeys', () => {
|
|
196
|
+
it('should return an array of keys within an array', () => {
|
|
197
|
+
const obj = {
|
|
198
|
+
foo: 'bar',
|
|
199
|
+
baz: 'bang',
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const result = definedKeys(obj);
|
|
203
|
+
const expected = ['"foo"', '"baz"'];
|
|
204
|
+
|
|
205
|
+
expect(result).toStrictEqual(expected);
|
|
206
|
+
});
|
|
207
|
+
it('should return an array of keys with primitive values and their full nested path', () => {
|
|
208
|
+
const obj = {
|
|
209
|
+
foo: 'bar',
|
|
210
|
+
baz: { bang: 'bop' },
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const result = definedKeys(obj);
|
|
214
|
+
const expected = ['"foo"', '"baz"."bang"'];
|
|
215
|
+
|
|
216
|
+
expect(result).toStrictEqual(expected);
|
|
217
|
+
});
|
|
218
|
+
it('should return an array of keys with primitive values and their full nested path with quotation marks to escape keys with dots in them', () => {
|
|
219
|
+
const obj = {
|
|
220
|
+
foo: 'bar',
|
|
221
|
+
baz: { 'bang.bop': 'beep' },
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const result = definedKeys(obj);
|
|
225
|
+
const expected = ['"foo"', '"baz"."bang.bop"'];
|
|
226
|
+
|
|
227
|
+
expect(result).toStrictEqual(expected);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
@@ -1,28 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isDevBuild } from '@shell/utils/version';
|
|
2
2
|
|
|
3
|
-
describe('fx:
|
|
4
|
-
it(
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
describe('fx: isDevBuild', () => {
|
|
4
|
+
it.each([
|
|
5
|
+
'dev',
|
|
6
|
+
'master',
|
|
7
|
+
'head',
|
|
8
|
+
'whatever-head',
|
|
9
|
+
'whatever-rc1',
|
|
10
|
+
'whatever-alpha1',
|
|
11
|
+
])(
|
|
12
|
+
'should exclude version type %p', (version: string) => {
|
|
13
|
+
const result = isDevBuild(version);
|
|
7
14
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
expect(result).toStrictEqual(expectation);
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const latestVersionSupportURL = 'https://rancher.com/support-maintenance-terms';
|
|
14
|
-
const testCases = [
|
|
15
|
-
['v2.7-0bcf068e1237acafd4aca01385c7c6b432e22fd7-head', latestVersionSupportURL],
|
|
16
|
-
['v2.7.5-rc4', latestVersionSupportURL],
|
|
17
|
-
[undefined, latestVersionSupportURL],
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
it.each(testCases)(
|
|
21
|
-
'should generate support link corresponding to the latest Rancher version when version is unknown or for dev build',
|
|
22
|
-
(version, expected) => {
|
|
23
|
-
const result = generateSupportLink(version);
|
|
24
|
-
|
|
25
|
-
expect(result).toBe(expected);
|
|
15
|
+
expect(result).toBe(true);
|
|
26
16
|
}
|
|
27
17
|
);
|
|
28
18
|
});
|
package/utils/cluster.js
CHANGED
|
@@ -7,7 +7,7 @@ import { SETTING } from '@shell/config/settings';
|
|
|
7
7
|
export function filterOnlyKubernetesClusters(mgmtClusters, store) {
|
|
8
8
|
const openHarvesterContainerWorkload = store.getters['features/get']('harvester-baremetal-container-workload');
|
|
9
9
|
|
|
10
|
-
return mgmtClusters
|
|
10
|
+
return mgmtClusters?.filter((c) => {
|
|
11
11
|
return openHarvesterContainerWorkload ? true : !isHarvesterCluster(c);
|
|
12
12
|
});
|
|
13
13
|
}
|
|
@@ -8,7 +8,6 @@ import { cronSchedule } from '@shell/utils/validators/cron-schedule';
|
|
|
8
8
|
import { podAffinity } from '@shell/utils/validators/pod-affinity';
|
|
9
9
|
import { roleTemplateRules } from '@shell/utils/validators/role-template';
|
|
10
10
|
import { clusterName } from '@shell/utils/validators/cluster-name';
|
|
11
|
-
import { isHttps } from '@shell/utils/validators/setting';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Custom validation functions beyond normal scalr types
|
|
@@ -30,5 +29,4 @@ export default {
|
|
|
30
29
|
cronSchedule,
|
|
31
30
|
podAffinity,
|
|
32
31
|
roleTemplateRules,
|
|
33
|
-
isHttps,
|
|
34
32
|
};
|
package/utils/error.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import { isArray } from '@shell/utils/array';
|
|
2
2
|
|
|
3
3
|
export class ClusterNotFoundError extends Error {
|
|
4
|
+
static name = 'ClusterNotFoundError'
|
|
5
|
+
|
|
4
6
|
constructor(message) {
|
|
5
7
|
super(message);
|
|
6
|
-
this.name =
|
|
8
|
+
this.name = ClusterNotFoundError.name;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* An error occurred and the user should be redirected to a certain location (where this is handled)
|
|
14
|
+
*/
|
|
15
|
+
export class RedirectToError extends Error {
|
|
16
|
+
static name = 'RedirectToError'
|
|
17
|
+
|
|
18
|
+
constructor(message, url) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.name = RedirectToError.name;
|
|
7
22
|
}
|
|
8
23
|
}
|
|
9
24
|
|
package/utils/grafana.js
CHANGED
|
@@ -63,14 +63,13 @@ export async function allDashboardsExist(store, clusterId, embeddedUrls, storeNa
|
|
|
63
63
|
|
|
64
64
|
let monitoringVersion = '';
|
|
65
65
|
|
|
66
|
-
if (!projectId) {
|
|
66
|
+
if (!projectId && store.getters[`${ storeName }/canList`](CATALOG.APP)) {
|
|
67
67
|
try {
|
|
68
68
|
res = await store.dispatch(`${ storeName }/find`, {
|
|
69
69
|
type: CATALOG.APP,
|
|
70
70
|
id: 'cattle-monitoring-system/rancher-monitoring'
|
|
71
71
|
});
|
|
72
72
|
} catch (err) {
|
|
73
|
-
return false;
|
|
74
73
|
}
|
|
75
74
|
|
|
76
75
|
monitoringVersion = res?.currentVersion;
|
package/utils/monitoring.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Helpers for determining if V2 or v1 Monitoring are installed
|
|
2
2
|
|
|
3
|
-
import { SCHEMA, MONITORING, WORKLOAD_TYPES } from '@shell/config/types';
|
|
3
|
+
import { SCHEMA, MONITORING, WORKLOAD_TYPES, ENDPOINTS } from '@shell/config/types';
|
|
4
4
|
import { normalizeType } from '@shell/plugins/dashboard-store/normalize';
|
|
5
5
|
import { findBy } from '@shell/utils/array';
|
|
6
6
|
import { isEmpty } from '@shell/utils/object';
|
|
@@ -65,6 +65,30 @@ export async function haveV1MonitoringWorkloads(store) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
async function hasEndpointSubsets(store, id) {
|
|
69
|
+
if (store.getters['cluster/schemaFor'](ENDPOINTS)) {
|
|
70
|
+
const endpoints = await store.dispatch('cluster/findAll', { type: ENDPOINTS }) || [];
|
|
71
|
+
|
|
72
|
+
const endpoint = endpoints.find((ep) => ep.id === id);
|
|
73
|
+
|
|
74
|
+
return endpoint && !isEmpty(endpoint) && !isEmpty(endpoint.subsets);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function canViewGrafanaLink(store) {
|
|
81
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-grafana`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function canViewAlertManagerLink(store) {
|
|
85
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-alertmanager`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function canViewPrometheusLink(store) {
|
|
89
|
+
return await hasEndpointSubsets(store, `${ CATTLE_MONITORING_NAMESPACE }/rancher-monitoring-prometheus`);
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
// Other ways we check for monitoring:
|
|
69
93
|
|
|
70
94
|
// (1) Using counts (requires RBAC permissions)
|
package/utils/object.js
CHANGED
|
@@ -178,11 +178,12 @@ export function definedKeys(obj) {
|
|
|
178
178
|
const val = obj[key];
|
|
179
179
|
|
|
180
180
|
if ( Array.isArray(val) ) {
|
|
181
|
-
return key
|
|
181
|
+
return `"${ key }"`;
|
|
182
182
|
} else if ( isObject(val) ) {
|
|
183
|
-
|
|
183
|
+
// no need for quotes around the subkey since the recursive call will fill that in via one of the other two statements in the if block
|
|
184
|
+
return ( definedKeys(val) || [] ).map((subkey) => `"${ key }".${ subkey }`);
|
|
184
185
|
} else {
|
|
185
|
-
return key
|
|
186
|
+
return `"${ key }"`;
|
|
186
187
|
}
|
|
187
188
|
});
|
|
188
189
|
|
package/utils/sort.js
CHANGED
|
@@ -184,7 +184,7 @@ export function sortBy(ary, keys, desc) {
|
|
|
184
184
|
keys = [keys];
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
return ary.slice().sort((objA, objB) => {
|
|
187
|
+
return (ary || []).slice().sort((objA, objB) => {
|
|
188
188
|
for ( let i = 0 ; i < keys.length ; i++ ) {
|
|
189
189
|
const parsed = parseField(keys[i]);
|
|
190
190
|
const a = get(objA, parsed.field);
|
|
@@ -43,21 +43,66 @@ describe('formRules', () => {
|
|
|
43
43
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
44
44
|
});
|
|
45
45
|
|
|
46
|
-
it('"
|
|
46
|
+
it('"https" : returns undefined when valid https url value is supplied', () => {
|
|
47
47
|
const testValue = 'https://url.com';
|
|
48
|
-
const formRuleResult = formRules.
|
|
48
|
+
const formRuleResult = formRules.https(testValue);
|
|
49
49
|
|
|
50
50
|
expect(formRuleResult).toBeUndefined();
|
|
51
51
|
});
|
|
52
52
|
|
|
53
|
-
it('"
|
|
53
|
+
it('"https" : returns correct message when http url value is supplied', () => {
|
|
54
54
|
const testValue = 'http://url.com';
|
|
55
|
-
const formRuleResult = formRules.
|
|
55
|
+
const formRuleResult = formRules.https(testValue);
|
|
56
56
|
const expectedResult = JSON.stringify({ message: 'validation.setting.serverUrl.https' });
|
|
57
57
|
|
|
58
58
|
expect(formRuleResult).toStrictEqual(expectedResult);
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
+
describe('localhost', () => {
|
|
62
|
+
const message = JSON.stringify({ message: 'validation.setting.serverUrl.localhost' });
|
|
63
|
+
const testCases = [
|
|
64
|
+
['http://LOCALhosT:8005', message],
|
|
65
|
+
['http://localhost:8005', message],
|
|
66
|
+
['https://localhost:8005', message],
|
|
67
|
+
['localhost', message],
|
|
68
|
+
['http://127.0.0.1', message],
|
|
69
|
+
['https://127.0.0.1', message],
|
|
70
|
+
['127.0.0.1', message],
|
|
71
|
+
['https://test.com', undefined],
|
|
72
|
+
['https://test.com/localhost', undefined],
|
|
73
|
+
[undefined, undefined]
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
it.each(testCases)(
|
|
77
|
+
'should return undefined or correct message based on the provided url',
|
|
78
|
+
(url, expected) => {
|
|
79
|
+
const formRuleResult = formRules.localhost(url);
|
|
80
|
+
|
|
81
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe('trailingForwardSlash', () => {
|
|
87
|
+
const message = JSON.stringify({ message: 'validation.setting.serverUrl.trailingForwardSlash' });
|
|
88
|
+
const testCases = [
|
|
89
|
+
['https://test.com', undefined],
|
|
90
|
+
['https://test.com/', message],
|
|
91
|
+
['https://', undefined],
|
|
92
|
+
['/', undefined],
|
|
93
|
+
[undefined, undefined]
|
|
94
|
+
];
|
|
95
|
+
|
|
96
|
+
it.each(testCases)(
|
|
97
|
+
'should return undefined or correct message based on the provided url',
|
|
98
|
+
(url, expected) => {
|
|
99
|
+
const formRuleResult = formRules.trailingForwardSlash(url);
|
|
100
|
+
|
|
101
|
+
expect(formRuleResult).toStrictEqual(expected);
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
61
106
|
it('"interval" : returns undefined when valid hour interval value is supplied', () => {
|
|
62
107
|
const testValue = '5h';
|
|
63
108
|
const formRuleResult = formRules.interval(testValue);
|
|
@@ -2,9 +2,11 @@ import { RBAC } from '@shell/config/types';
|
|
|
2
2
|
import { HCI } from '@shell/config/labels-annotations';
|
|
3
3
|
import isEmpty from 'lodash/isEmpty';
|
|
4
4
|
import has from 'lodash/has';
|
|
5
|
+
import isUrl from 'is-url';
|
|
5
6
|
// import uniq from 'lodash/uniq';
|
|
6
7
|
import cronstrue from 'cronstrue';
|
|
7
8
|
import { Translation } from '@shell/types/t';
|
|
9
|
+
import { isHttps, isLocalhost, hasTrailingForwardSlash } from '@shell/utils/validators/setting';
|
|
8
10
|
|
|
9
11
|
// import uniq from 'lodash/uniq';
|
|
10
12
|
export type Validator<T = undefined | string> = (val: any, arg?: any) => T;
|
|
@@ -34,10 +36,6 @@ export class Port {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
const httpsKeys = [
|
|
38
|
-
'server-url'
|
|
39
|
-
];
|
|
40
|
-
|
|
41
39
|
const runValidators = (val: any, validators: Validator[]) => {
|
|
42
40
|
for (const validator of validators) {
|
|
43
41
|
const message = validator(val);
|
|
@@ -139,11 +137,13 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
139
137
|
}
|
|
140
138
|
};
|
|
141
139
|
|
|
142
|
-
const
|
|
143
|
-
const isHttps: Validator = (val: string) => httpsKeys.includes(key) && !val.toLowerCase().startsWith('https://') ? t('validation.setting.serverUrl.https') : undefined;
|
|
140
|
+
const https: Validator = (val: string) => val && !isHttps(val) ? t('validation.setting.serverUrl.https') : undefined;
|
|
144
141
|
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
const localhost: Validator = (val: string) => isLocalhost(val) ? t('validation.setting.serverUrl.localhost') : undefined;
|
|
143
|
+
|
|
144
|
+
const trailingForwardSlash: Validator = (val: string) => hasTrailingForwardSlash(val) ? t('validation.setting.serverUrl.trailingForwardSlash') : undefined;
|
|
145
|
+
|
|
146
|
+
const url: Validator = (val: string) => val && !isUrl(val) ? t('validation.setting.serverUrl.url') : undefined;
|
|
147
147
|
|
|
148
148
|
const interval: Validator = (val: string) => !/^\d+[hms]$/.test(val) ? t('validation.monitoring.route.interval', { key }) : undefined;
|
|
149
149
|
|
|
@@ -382,7 +382,7 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
382
382
|
if (val.some((rule: any) => isEmpty(rule.apiGroups))) {
|
|
383
383
|
return t('validation.roleTemplate.roleTemplateRules.missingApiGroup');
|
|
384
384
|
}
|
|
385
|
-
} else if (val.some((rule: any) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs)
|
|
385
|
+
} else if (val.some((rule: any) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs))) {
|
|
386
386
|
return t('validation.roleTemplate.roleTemplateRules.missingOneResource');
|
|
387
387
|
}
|
|
388
388
|
|
|
@@ -475,7 +475,10 @@ export default function(t: Translation, { key = 'Value' }: ValidationOptions): {
|
|
|
475
475
|
hostname,
|
|
476
476
|
imageUrl,
|
|
477
477
|
interval,
|
|
478
|
-
|
|
478
|
+
https,
|
|
479
|
+
localhost,
|
|
480
|
+
trailingForwardSlash,
|
|
481
|
+
url,
|
|
479
482
|
matching,
|
|
480
483
|
maxLength,
|
|
481
484
|
maxValue,
|
|
@@ -21,7 +21,7 @@ export function roleTemplateRules(rules = [], getters, errors, validatorArgs = [
|
|
|
21
21
|
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.noResourceAndNonResource'));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs)
|
|
24
|
+
if (rules.some((rule) => isEmpty(rule.resources) && isEmpty(rule.nonResourceURLs))) {
|
|
25
25
|
errors.push(getters['i18n/t']('validation.roleTemplate.roleTemplateRules.missingOneResource'));
|
|
26
26
|
}
|
|
27
27
|
}
|