@rancher/shell 0.3.27 → 0.3.29
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 +19 -21
- package/assets/translations/zh-hans.yaml +0 -23
- package/chart/gatekeeper.vue +2 -11
- package/chart/istio.vue +1 -10
- package/chart/logging/index.vue +2 -11
- package/chart/monitoring/grafana/index.vue +2 -2
- package/chart/monitoring/index.vue +1 -9
- package/chart/rancher-backup/index.vue +1 -9
- package/components/AsyncButton.vue +9 -0
- package/components/Carousel.vue +2 -1
- package/components/SortableTable/THead.vue +7 -9
- package/components/SortableTable/index.vue +1 -2
- package/components/fleet/FleetStatus.vue +3 -3
- package/components/fleet/FleetSummary.vue +32 -27
- package/components/formatter/ClusterProvider.vue +1 -18
- package/components/nav/WindowManager/ContainerLogs.vue +22 -19
- package/config/product/manager.js +0 -13
- package/config/types.js +0 -4
- package/detail/provisioning.cattle.io.cluster.vue +2 -1
- 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 +6 -107
- 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/index.vue +2 -1
- package/machine-config/__tests__/vmwarevsphere.test.ts +72 -0
- package/machine-config/vmwarevsphere.vue +68 -13
- 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/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 +2 -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/explorer/index.vue +0 -47
- package/pages/c/_cluster/manager/pages/_page.vue +4 -5
- package/rancher-components/components/StringList/StringList.test.ts +270 -0
- package/rancher-components/components/StringList/StringList.vue +57 -18
- package/store/prefs.js +0 -3
- package/types/shell/index.d.ts +13 -7
- package/utils/custom-validators.js +0 -2
- package/utils/error.js +16 -1
- package/utils/validators/formRules/__tests__/index.test.ts +49 -4
- package/utils/validators/formRules/index.ts +12 -9
- package/utils/validators/setting.js +6 -10
- package/components/ChartPsp.vue +0 -76
- package/components/__tests__/ChartPsp.test.ts +0 -75
- package/components/formatter/__tests__/ClusterProvider.test.ts +0 -28
|
@@ -398,6 +398,276 @@ describe('stringList.vue', () => {
|
|
|
398
398
|
});
|
|
399
399
|
});
|
|
400
400
|
|
|
401
|
+
describe('bulk delimiter', () => {
|
|
402
|
+
const delimiter = /;/;
|
|
403
|
+
|
|
404
|
+
describe('add', () => {
|
|
405
|
+
const items: string[] = [];
|
|
406
|
+
|
|
407
|
+
beforeEach(() => {
|
|
408
|
+
wrapper = mount(StringList, {
|
|
409
|
+
propsData: {
|
|
410
|
+
items,
|
|
411
|
+
bulkAdditionDelimiter: delimiter,
|
|
412
|
+
errorMessages: { duplicate: 'error, item is duplicate.' },
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it('should split values if delimiter set', async() => {
|
|
418
|
+
const value = 'test;test1;test2';
|
|
419
|
+
const result = ['test', 'test1', 'test2'];
|
|
420
|
+
|
|
421
|
+
// activate create mode
|
|
422
|
+
await wrapper.setData({ isCreateItem: true });
|
|
423
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
424
|
+
|
|
425
|
+
await inputField.setValue(value);
|
|
426
|
+
|
|
427
|
+
// press enter
|
|
428
|
+
await inputField.trigger('keydown.enter');
|
|
429
|
+
await wrapper.vm.$nextTick();
|
|
430
|
+
|
|
431
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
432
|
+
|
|
433
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it('should show warning if one of the values is a duplicate', async() => {
|
|
437
|
+
const value = 'test;test1;test2';
|
|
438
|
+
|
|
439
|
+
await wrapper.setProps({ items: ['test1'] });
|
|
440
|
+
|
|
441
|
+
// activate create mode
|
|
442
|
+
await wrapper.setData({ isCreateItem: true });
|
|
443
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
444
|
+
|
|
445
|
+
await inputField.setValue(value);
|
|
446
|
+
|
|
447
|
+
// press enter
|
|
448
|
+
await inputField.trigger('keydown.enter');
|
|
449
|
+
await wrapper.vm.$nextTick();
|
|
450
|
+
|
|
451
|
+
const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
|
|
452
|
+
|
|
453
|
+
expect(isDuplicate).toBe(true);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it('should show a warning if the new values are all duplicates', async() => {
|
|
457
|
+
const value = 'test;test';
|
|
458
|
+
|
|
459
|
+
// activate create mode
|
|
460
|
+
await wrapper.setData({ isCreateItem: true });
|
|
461
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
462
|
+
|
|
463
|
+
await inputField.setValue(value);
|
|
464
|
+
|
|
465
|
+
// press enter
|
|
466
|
+
await inputField.trigger('keydown.enter');
|
|
467
|
+
await wrapper.vm.$nextTick();
|
|
468
|
+
|
|
469
|
+
const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
|
|
470
|
+
|
|
471
|
+
expect(isDuplicate).toBe(true);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
it('should not consider empty strings at the beginning', async() => {
|
|
475
|
+
const value = ';test;test1;test2';
|
|
476
|
+
const result = ['test', 'test1', 'test2'];
|
|
477
|
+
|
|
478
|
+
// activate create mode
|
|
479
|
+
await wrapper.setData({ isCreateItem: true });
|
|
480
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
481
|
+
|
|
482
|
+
await inputField.setValue(value);
|
|
483
|
+
|
|
484
|
+
// press enter
|
|
485
|
+
await inputField.trigger('keydown.enter');
|
|
486
|
+
await wrapper.vm.$nextTick();
|
|
487
|
+
|
|
488
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
489
|
+
|
|
490
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
it('should not consider empty strings in the middle', async() => {
|
|
494
|
+
const value = 'test;test1;;test2';
|
|
495
|
+
const result = ['test', 'test1', 'test2'];
|
|
496
|
+
|
|
497
|
+
// activate create mode
|
|
498
|
+
await wrapper.setData({ isCreateItem: true });
|
|
499
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
500
|
+
|
|
501
|
+
await inputField.setValue(value);
|
|
502
|
+
|
|
503
|
+
// press enter
|
|
504
|
+
await inputField.trigger('keydown.enter');
|
|
505
|
+
await wrapper.vm.$nextTick();
|
|
506
|
+
|
|
507
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
508
|
+
|
|
509
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it('should not consider empty strings at the end', async() => {
|
|
513
|
+
const value = 'test;test1;test2;';
|
|
514
|
+
const result = ['test', 'test1', 'test2'];
|
|
515
|
+
|
|
516
|
+
// activate create mode
|
|
517
|
+
await wrapper.setData({ isCreateItem: true });
|
|
518
|
+
const inputField = wrapper.find('[data-testid="item-create"]');
|
|
519
|
+
|
|
520
|
+
await inputField.setValue(value);
|
|
521
|
+
|
|
522
|
+
// press enter
|
|
523
|
+
await inputField.trigger('keydown.enter');
|
|
524
|
+
await wrapper.vm.$nextTick();
|
|
525
|
+
|
|
526
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
527
|
+
|
|
528
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
describe('edit', () => {
|
|
533
|
+
const items = ['test1', 'test2'];
|
|
534
|
+
|
|
535
|
+
beforeEach(() => {
|
|
536
|
+
wrapper = mount(StringList, {
|
|
537
|
+
propsData: {
|
|
538
|
+
items,
|
|
539
|
+
bulkAdditionDelimiter: delimiter,
|
|
540
|
+
errorMessages: { duplicate: 'error, item is duplicate.' },
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
it('should split values if delimiter set', async() => {
|
|
546
|
+
const newValue = 'test1;new;values';
|
|
547
|
+
const result = ['test1', 'new', 'values', 'test2'];
|
|
548
|
+
|
|
549
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
550
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
551
|
+
|
|
552
|
+
await inputField.setValue(newValue);
|
|
553
|
+
|
|
554
|
+
// press enter
|
|
555
|
+
await inputField.trigger('keydown.enter');
|
|
556
|
+
await wrapper.vm.$nextTick();
|
|
557
|
+
|
|
558
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
559
|
+
|
|
560
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
561
|
+
});
|
|
562
|
+
|
|
563
|
+
it('should show warning if one of the values is a duplicate', async() => {
|
|
564
|
+
const newValue = 'test1;test2';
|
|
565
|
+
|
|
566
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
567
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
568
|
+
|
|
569
|
+
await inputField.setValue(newValue);
|
|
570
|
+
|
|
571
|
+
// press enter
|
|
572
|
+
await inputField.trigger('keydown.enter');
|
|
573
|
+
await wrapper.vm.$nextTick();
|
|
574
|
+
|
|
575
|
+
const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
|
|
576
|
+
|
|
577
|
+
expect(isDuplicate).toBe(true);
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
it('should show a warning if the new values are all duplicates', async() => {
|
|
581
|
+
const newValue = 'test;test';
|
|
582
|
+
|
|
583
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
584
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
585
|
+
|
|
586
|
+
await inputField.setValue(newValue);
|
|
587
|
+
|
|
588
|
+
// press enter
|
|
589
|
+
await inputField.trigger('keydown.enter');
|
|
590
|
+
await wrapper.vm.$nextTick();
|
|
591
|
+
|
|
592
|
+
const isDuplicate = (wrapper.emitted('errors') || [])[0][0].duplicate;
|
|
593
|
+
|
|
594
|
+
expect(isDuplicate).toBe(true);
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should not consider empty strings at the beginning', async() => {
|
|
598
|
+
const newValue = ';test1;new;value';
|
|
599
|
+
const result = ['test1', 'new', 'value', 'test2'];
|
|
600
|
+
|
|
601
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
602
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
603
|
+
|
|
604
|
+
await inputField.setValue(newValue);
|
|
605
|
+
|
|
606
|
+
// press enter
|
|
607
|
+
await inputField.trigger('keydown.enter');
|
|
608
|
+
await wrapper.vm.$nextTick();
|
|
609
|
+
|
|
610
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
611
|
+
|
|
612
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('should not consider empty strings in the middle 1', async() => {
|
|
616
|
+
const newValue = 'test1; ;new;value';
|
|
617
|
+
const result = ['test1', 'new', 'value', 'test2'];
|
|
618
|
+
|
|
619
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
620
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
621
|
+
|
|
622
|
+
await inputField.setValue(newValue);
|
|
623
|
+
|
|
624
|
+
// press enter
|
|
625
|
+
await inputField.trigger('keydown.enter');
|
|
626
|
+
await wrapper.vm.$nextTick();
|
|
627
|
+
|
|
628
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
629
|
+
|
|
630
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
it('should not consider empty strings in the middle 2', async() => {
|
|
634
|
+
const newValue = 'test1;;new;value';
|
|
635
|
+
const result = ['test1', 'new', 'value', 'test2'];
|
|
636
|
+
|
|
637
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
638
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
639
|
+
|
|
640
|
+
await inputField.setValue(newValue);
|
|
641
|
+
|
|
642
|
+
// press enter
|
|
643
|
+
await inputField.trigger('keydown.enter');
|
|
644
|
+
await wrapper.vm.$nextTick();
|
|
645
|
+
|
|
646
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
647
|
+
|
|
648
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
it('should not consider empty strings at the end', async() => {
|
|
652
|
+
const newValue = 'test1;new;value;';
|
|
653
|
+
const result = ['test1', 'new', 'value', 'test2'];
|
|
654
|
+
|
|
655
|
+
await wrapper.setData({ editedItem: items[0] });
|
|
656
|
+
const inputField = wrapper.find('[data-testid^="item-edit"]');
|
|
657
|
+
|
|
658
|
+
await inputField.setValue(newValue);
|
|
659
|
+
|
|
660
|
+
// press enter
|
|
661
|
+
await inputField.trigger('keydown.enter');
|
|
662
|
+
await wrapper.vm.$nextTick();
|
|
663
|
+
|
|
664
|
+
const itemsResult = (wrapper.emitted('change') || [])[0][0];
|
|
665
|
+
|
|
666
|
+
expect(JSON.stringify(itemsResult)).toBe(JSON.stringify(result));
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
|
|
401
671
|
describe('errors handling', () => {
|
|
402
672
|
it('show duplicate warning icon when errorMessages is defined', async() => {
|
|
403
673
|
const items = ['test'];
|
|
@@ -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/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;
|
|
@@ -2869,7 +2865,6 @@ export const PLUGIN_DEVELOPER: any;
|
|
|
2869
2865
|
export const _RKE1: "rke1";
|
|
2870
2866
|
export const _RKE2: "rke2";
|
|
2871
2867
|
export const PROVISIONER: any;
|
|
2872
|
-
export const PSP_DEPRECATION_BANNER: any;
|
|
2873
2868
|
export const MENU_MAX_CLUSTERS: 10;
|
|
2874
2869
|
export const SCALE_POOL_PROMPT: any;
|
|
2875
2870
|
export function state(): {
|
|
@@ -3301,7 +3296,6 @@ declare namespace _default {
|
|
|
3301
3296
|
export { cronSchedule };
|
|
3302
3297
|
export { podAffinity };
|
|
3303
3298
|
export { roleTemplateRules };
|
|
3304
|
-
export { isHttps };
|
|
3305
3299
|
}
|
|
3306
3300
|
export default _default;
|
|
3307
3301
|
}
|
|
@@ -3361,8 +3355,17 @@ declare module '@shell/utils/error' {
|
|
|
3361
3355
|
export function stringify(err: any): any;
|
|
3362
3356
|
export function exceptionToErrorsArray(err: any): any;
|
|
3363
3357
|
export class ClusterNotFoundError extends Error {
|
|
3358
|
+
static name: string;
|
|
3364
3359
|
constructor(message: any);
|
|
3365
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
|
+
}
|
|
3366
3369
|
export class ApiError extends Error {
|
|
3367
3370
|
constructor(res: any);
|
|
3368
3371
|
status: any;
|
|
@@ -4173,7 +4176,10 @@ export function externalName(spec: any, getters: any, errors: any, validatorArgs
|
|
|
4173
4176
|
// @shell/utils/validators/setting
|
|
4174
4177
|
|
|
4175
4178
|
declare module '@shell/utils/validators/setting' {
|
|
4176
|
-
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;
|
|
4177
4183
|
}
|
|
4178
4184
|
|
|
4179
4185
|
// @shell/utils/version
|
|
@@ -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
|
|
|
@@ -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
|
|
|
@@ -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,
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
'server-url'
|
|
3
|
-
];
|
|
1
|
+
import isUrl from 'is-url';
|
|
4
2
|
|
|
5
|
-
export
|
|
6
|
-
const key = validatorArgs[0];
|
|
3
|
+
export const isServerUrl = (value) => value === 'server-url';
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
errors.push(getters['i18n/t']('validation.setting.serverUrl.https'));
|
|
10
|
-
}
|
|
5
|
+
export const isHttps = (value) => value.toLowerCase().startsWith('https://');
|
|
11
6
|
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export const isLocalhost = (value) => (/^(?:https?:\/\/)?(?:localhost|127\.0\.0\.1)/i).test(value);
|
|
8
|
+
|
|
9
|
+
export const hasTrailingForwardSlash = (value) => isUrl(value) && value?.toLowerCase().endsWith('/');
|