@rancher/shell 0.5.1 → 0.5.3

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 (70) hide show
  1. package/assets/translations/en-us.yaml +8 -4
  2. package/components/ClusterIconMenu.vue +24 -9
  3. package/components/CodeMirror.vue +79 -18
  4. package/components/FixedBanner.vue +1 -0
  5. package/components/ResourceDetail/index.vue +1 -4
  6. package/components/ResourceYaml.vue +29 -5
  7. package/components/SideNav.vue +42 -64
  8. package/components/SortableTable/index.vue +1 -1
  9. package/components/YamlEditor.vue +1 -0
  10. package/components/__tests__/CodeMirror.spec.ts +99 -0
  11. package/components/form/BannerSettings.vue +3 -0
  12. package/components/form/FileSelector.vue +1 -0
  13. package/components/form/KeyValue.vue +1 -0
  14. package/components/formatter/WorkloadDetailEndpoints.vue +12 -22
  15. package/components/formatter/__tests__/WorkloadDetailEndpoints.test.ts +81 -0
  16. package/components/nav/Header.vue +1 -0
  17. package/components/nav/Jump.vue +19 -9
  18. package/components/nav/TopLevelMenu.vue +37 -15
  19. package/components/nav/Type.vue +15 -4
  20. package/components/nav/__tests__/TopLevelMenu.test.ts +1 -1
  21. package/components/nav/__tests__/Type.test.ts +30 -0
  22. package/core/types-provisioning.ts +7 -0
  23. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +77 -0
  24. package/detail/fleet.cattle.io.bundle.vue +1 -1
  25. package/detail/provisioning.cattle.io.cluster.vue +19 -4
  26. package/edit/management.cattle.io.setting.vue +1 -0
  27. package/edit/monitoring.coreos.com.alertmanagerconfig/types/opsgenie.vue +1 -1
  28. package/edit/monitoring.coreos.com.alertmanagerconfig/types/pagerduty.vue +1 -2
  29. package/edit/monitoring.coreos.com.alertmanagerconfig/types/slack.vue +1 -1
  30. package/edit/provisioning.cattle.io.cluster/index.vue +23 -10
  31. package/edit/provisioning.cattle.io.cluster/rke2.vue +22 -50
  32. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +9 -11
  33. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +3 -1
  34. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +3 -0
  35. package/edit/token.vue +1 -0
  36. package/list/catalog.cattle.io.app.vue +1 -0
  37. package/list/management.cattle.io.setting.vue +1 -0
  38. package/machine-config/amazonec2.vue +1 -0
  39. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +151 -0
  40. package/models/__tests__/secret.test.ts +37 -0
  41. package/models/__tests__/storage.k8s.io.storageclass.test.ts +22 -0
  42. package/models/management.cattle.io.kontainerdriver.js +2 -1
  43. package/models/provisioning.cattle.io.cluster.js +36 -1
  44. package/models/secret.js +9 -0
  45. package/models/storage.k8s.io.storageclass.js +1 -1
  46. package/package.json +1 -1
  47. package/pages/c/_cluster/settings/DefaultLinksEditor.vue +1 -0
  48. package/pages/c/_cluster/settings/brand.vue +3 -0
  49. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +4 -4
  50. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +5 -2
  51. package/pages/c/_cluster/uiplugins/__tests__/AddExtensionRepos.test.ts +96 -0
  52. package/pages/c/_cluster/uiplugins/__tests__/SetupUIPlugins.test.ts +128 -0
  53. package/plugins/dashboard-store/__tests__/actions.test.ts +196 -111
  54. package/plugins/dashboard-store/actions.js +4 -6
  55. package/plugins/dashboard-store/getters.js +60 -2
  56. package/plugins/dashboard-store/resource-class.js +6 -2
  57. package/plugins/steve/__tests__/getters.spec.ts +10 -0
  58. package/plugins/steve/__tests__/resource-utils.test.ts +159 -0
  59. package/plugins/steve/actions.js +3 -37
  60. package/plugins/steve/getters.js +6 -0
  61. package/plugins/steve/resource-utils.ts +38 -0
  62. package/scripts/extension/parse-tag-name +3 -3
  63. package/store/__tests__/type-map.test.ts +1122 -0
  64. package/store/index.js +3 -2
  65. package/store/plugins.js +7 -6
  66. package/store/type-map.js +145 -75
  67. package/types/shell/index.d.ts +2 -0
  68. package/utils/__tests__/create-yaml.test.ts +10 -0
  69. package/utils/create-yaml.js +5 -1
  70. package/utils/object.js +10 -0
package/store/index.js CHANGED
@@ -604,7 +604,7 @@ export const mutations = {
604
604
  state.isRancherInHarvester = neu;
605
605
  },
606
606
 
607
- updateNamespaces(state, { filters, all }) {
607
+ updateNamespaces(state, { filters, all, getters }) {
608
608
  state.namespaceFilters = filters.filter((x) => !!x);
609
609
 
610
610
  if ( all ) {
@@ -957,6 +957,7 @@ export const actions = {
957
957
  commit('updateNamespaces', {
958
958
  filters: filters || [ALL_USER],
959
959
  all: allNamespaces,
960
+ getters
960
961
  });
961
962
 
962
963
  if (getters['currentCluster'] && getters['currentCluster'].isHarvester) {
@@ -979,7 +980,7 @@ export const actions = {
979
980
  }
980
981
  });
981
982
 
982
- commit('updateNamespaces', { filters: ids });
983
+ commit('updateNamespaces', { filters: ids, getters });
983
984
  },
984
985
 
985
986
  async cleanNamespaces({ getters, dispatch }) {
package/store/plugins.js CHANGED
@@ -100,12 +100,13 @@ export const suffixFields = [
100
100
 
101
101
  // Machine driver to cloud provider mapping
102
102
  const driverToCloudProviderMap = {
103
- amazonec2: 'aws',
104
- azure: 'azure',
105
- digitalocean: '', // Show restricted options
106
- harvester: 'harvester',
107
- linode: '', // Show restricted options
108
- vmwarevsphere: 'rancher-vsphere',
103
+ amazonec2: 'aws',
104
+ azure: 'azure',
105
+ digitalocean: '', // Show restricted options
106
+ harvester: 'harvester',
107
+ linode: '', // Show restricted options
108
+ vmwarevsphere: 'rancher-vsphere',
109
+ ovhcloudpubliccloud: '',
109
110
 
110
111
  custom: undefined // Show all options
111
112
  };
package/store/type-map.js CHANGED
@@ -155,10 +155,48 @@ export const NAMESPACED = 'namespaced';
155
155
  export const CLUSTER_LEVEL = 'cluster';
156
156
  export const BOTH = 'both';
157
157
 
158
- export const ALL = 'all';
159
- export const BASIC = 'basic';
160
- export const FAVORITE = 'favorite';
161
- export const USED = 'used';
158
+ export const TYPE_MODES = {
159
+ /**
160
+ * allTypes usage: All resource types
161
+ *
162
+ * getTree usage: Remove ignored schemas, resources not applicable to ns, etc
163
+ */
164
+ ALL: 'all',
165
+ /**
166
+ * Represents resource types that should be shown at the top of the side nav.
167
+ *
168
+ * For example all fixed resource types above `More Resources` in the cluster explorer
169
+ *
170
+ * These will always be shown in the side nav
171
+ *
172
+ * allTypes usage: Resources that are in a group
173
+ *
174
+ * getTree usage: Remove ignored schemas, resources not applicable to ns, etc
175
+ */
176
+ BASIC: 'basic',
177
+ /**
178
+ * Represents any type of resource type that has been favourited
179
+ *
180
+ * These will always be shown in the side nav.
181
+ *
182
+ * allTypes usage: Resource types that have been favorited
183
+ *
184
+ * getTree usage: Remove ignored schemas, resources not applicable to ns, etc
185
+ */
186
+ FAVORITE: 'favorite',
187
+ /**
188
+ * Represents no virtual or spoofed types that have a count.
189
+ *
190
+ * For example the `More Resource` in the cluster explorer
191
+ *
192
+ * These will be shown in the side nav if there are resources in the ns filter OR the resource is not namespaces
193
+ *
194
+ * allTypes usage: All resource types that are not virtual or spoofed
195
+ *
196
+ * getTree usage: Remove types with no counts. Remove ignored schemas, resources not applicable to ns, etc
197
+ */
198
+ USED: 'used',
199
+ };
162
200
 
163
201
  export const ROOT = 'root';
164
202
 
@@ -535,17 +573,16 @@ export const getters = {
535
573
  },
536
574
 
537
575
  getTree(state, getters, rootState, rootGetters) {
538
- return (productId, mode, allTypes, clusterId, namespaceMode, namespaces, currentType, search) => {
576
+ // Name the function so it's easily identifiable when performance tracing
577
+ return function getTree(productId, mode, allTypes, clusterId, namespaceMode, currentType, search) {
539
578
  // getTree has four modes:
540
- // - `basic` matches data types that should always be shown even if there
541
- // are 0 of them.
542
- // - `used` matches the data types where there are more than 0 of them
543
- // in the current set of namespaces.
579
+ // - `basic` matches data types that should always be shown (even if there are 0 of them).
580
+ // - `used` matches the data types where there are more than 0 of them in the current set of namespaces.
544
581
  // - `all` matches all types.
545
582
  // - `favorite` matches starred types.
546
583
  // namespaceMode: 'namespaced', 'cluster', or 'both'
547
584
  // namespaces: null means all, otherwise it will be an array of specific namespaces to include
548
- const isBasic = mode === BASIC;
585
+ const isBasic = mode === TYPE_MODES.BASIC;
549
586
 
550
587
  let searchRegex;
551
588
 
@@ -578,7 +615,8 @@ export const getters = {
578
615
  continue;
579
616
  }
580
617
 
581
- const count = _matchingCounts(typeObj, namespaces);
618
+ const inStore = rootGetters.currentStore(typeObj.name);
619
+ const count = rootGetters[`${ inStore }/count`](typeObj);
582
620
  const groupForBasicType = getters.groupForBasicType(productId, typeObj.name);
583
621
 
584
622
  if ( typeObj.id === currentType ) {
@@ -586,7 +624,7 @@ export const getters = {
586
624
  } else if ( isBasic && !groupForBasicType ) {
587
625
  // If we want the basic tree only return basic types;
588
626
  continue;
589
- } else if ( mode === USED && count <= 0 ) {
627
+ } else if ( mode === TYPE_MODES.USED && count <= 0 ) {
590
628
  // If there's none of this type, ignore this entry when viewing only in-use types
591
629
  // Note: count is sometimes null, which is <= 0.
592
630
  continue;
@@ -594,7 +632,7 @@ export const getters = {
594
632
 
595
633
  const label = typeObj.labelKey ? rootGetters['i18n/t'](typeObj.labelKey) || typeObj.label : typeObj.label;
596
634
 
597
- const labelDisplay = highlightLabel(label, typeObj.count, typeObj.schema);
635
+ const labelDisplay = highlightLabel(label, count, typeObj.schema);
598
636
 
599
637
  if ( !labelDisplay ) {
600
638
  // Search happens in highlight and returns null if not found
@@ -605,10 +643,10 @@ export const getters = {
605
643
 
606
644
  if ( isBasic ) {
607
645
  group = _ensureGroup(root, groupForBasicType, true);
608
- } else if ( mode === FAVORITE ) {
646
+ } else if ( mode === TYPE_MODES.FAVORITE ) {
609
647
  group = _ensureGroup(root, 'starred');
610
648
  group.weight = 1000;
611
- } else if ( mode === USED ) {
649
+ } else if ( mode === TYPE_MODES.USED ) {
612
650
  group = _ensureGroup(root, `inUse::${ getters.groupLabelFor(typeObj.schema) }`);
613
651
  } else {
614
652
  group = _ensureGroup(root, typeObj.schema || typeObj.group || ROOT);
@@ -641,7 +679,6 @@ export const getters = {
641
679
  label,
642
680
  labelDisplay,
643
681
  mode: typeObj.mode,
644
- count,
645
682
  exact: typeObj.exact || false,
646
683
  namespaced,
647
684
  route,
@@ -811,69 +848,109 @@ export const getters = {
811
848
  });
812
849
  },
813
850
 
851
+ /**
852
+ * Given many things, create a list of menu items per schema given the mode
853
+ */
814
854
  allTypes(state, getters, rootState, rootGetters) {
815
- return (product, mode = ALL) => {
816
- const module = findBy(state.products, 'name', product)?.inStore;
855
+ // Name the function so it's easily identifiable when performance tracing
856
+ return function allTypes(product, modes = [TYPE_MODES.ALL]) {
857
+ const module = state.products.find((p) => p.name === product)?.inStore;
817
858
  const schemas = rootGetters[`${ module }/all`](SCHEMA);
859
+ const isLocal = !rootGetters.currentCluster?.isLocal;
860
+ const isRancher = rootGetters.isRancher;
818
861
  const counts = rootGetters[`${ module }/all`](COUNT)?.[0]?.counts || {};
819
- const isDev = rootGetters['prefs/get'](VIEW_IN_API);
820
- const isBasic = mode === BASIC;
821
862
 
822
863
  const out = {};
823
864
 
865
+ // For performance reasons this must be super quick to iterate over.
866
+ // For each schema...
867
+ // 1) Determine if it's applicable given the mode
868
+ // 2) For each applicable mode create a `Type` entry
824
869
  for ( const schema of schemas ) {
870
+ let schemaModes = { };
871
+
872
+ modes.forEach((m) => {
873
+ schemaModes[m] = true;
874
+ });
875
+
825
876
  const attrs = schema.attributes || {};
826
- const count = counts[schema.id];
827
- const label = getters.labelFor(schema, count);
828
- const weight = getters.typeWeightFor(schema?.id || label, isBasic);
829
877
  const typeOptions = getters['optionsFor'](schema);
830
878
 
831
- if ( isBasic ) {
832
- // These are separate ifs so that things with no kind can still be basic
833
- if ( !getters.groupForBasicType(product, schema.id) ) {
834
- continue;
835
- }
836
- } else if ( mode === FAVORITE && !getters.isFavorite(schema.id) ) {
837
- continue;
838
- } else if ( !attrs.kind ) {
839
- // Skip the schemas that aren't top-level types
840
- continue;
841
- } else if ( typeof typeOptions.ifRancherCluster !== 'undefined' && typeOptions.ifRancherCluster !== rootGetters.isRancher ) {
879
+ schemaModes[TYPE_MODES.BASIC] = schemaModes[TYPE_MODES.BASIC] && getters.groupForBasicType(product, schema.id);
880
+
881
+ if (Object.values(schemaModes).every((s) => !s)) {
842
882
  continue;
843
- } else if (typeOptions.localOnly && !rootGetters.currentCluster?.isLocal) {
883
+ }
884
+
885
+ schemaModes[TYPE_MODES.FAVORITE] = schemaModes[TYPE_MODES.FAVORITE] && getters.isFavorite(schema.id);
886
+
887
+ if (Object.values(schemaModes).every((s) => !s)) {
844
888
  continue;
845
889
  }
846
890
 
847
- out[schema.id] = {
848
- label,
849
- mode,
850
- weight,
851
- schema,
852
- name: schema.id,
853
- namespaced: typeOptions.namespaced === null ? attrs.namespaced : typeOptions.namespaced,
854
- count: count ? count.summary.count || 0 : null,
855
- byNamespace: count ? count.namespaces : {},
856
- revision: count ? count.revision : null,
857
- route: typeOptions.customRoute
858
- };
891
+ const onlyBasic = schemaModes[TYPE_MODES.BASIC] && modes.length === 1;
892
+
893
+ // This clause is only valid for non-basic modes. So if we have only basic... skip it
894
+ if (!onlyBasic) {
895
+ const invalidType = !attrs.kind ||
896
+ (typeof typeOptions.ifRancherCluster !== 'undefined' && typeOptions.ifRancherCluster !== isRancher) ||
897
+ (typeOptions.localOnly && isLocal);
898
+
899
+ if (invalidType) {
900
+ // Remove anything not basic
901
+ schemaModes = { [TYPE_MODES.BASIC]: schemaModes[TYPE_MODES.BASIC] };
902
+ }
903
+ }
904
+
905
+ // This is an expensive request to make, so only do it if we really need to
906
+ let label;
907
+
908
+ Object.entries(schemaModes).forEach(([mode, enabled]) => {
909
+ if (!enabled) {
910
+ return;
911
+ }
912
+
913
+ if (!out[mode]) {
914
+ out[mode] = {};
915
+ }
916
+
917
+ if (!label) {
918
+ label = getters.labelFor(schema, counts[schema.id]);
919
+ }
920
+
921
+ out[mode][schema.id] = {
922
+ label,
923
+ mode,
924
+ weight: getters.typeWeightFor(schema?.id || label, mode === TYPE_MODES.BASIC),
925
+ schema,
926
+ name: schema.id,
927
+ namespaced: typeOptions.namespaced === null ? attrs.namespaced : typeOptions.namespaced,
928
+ route: typeOptions.customRoute
929
+ };
930
+ });
859
931
  }
860
932
 
933
+ const nonUsedModes = modes.filter((m) => m !== TYPE_MODES.USED);
934
+ const isDev = rootGetters['prefs/get'](VIEW_IN_API);
935
+
861
936
  // Add virtual and spoofed types
862
- if ( mode !== USED ) {
937
+ if ( nonUsedModes.length ) {
863
938
  const virtualTypes = state.virtualTypes[product] || [];
864
939
  const spoofedTypes = state.spoofedTypes[product] || [];
865
940
  const allTypes = [...virtualTypes, ...spoofedTypes];
941
+ const virtSpoofedModes = [...nonUsedModes];
866
942
 
867
943
  for ( const type of allTypes ) {
868
944
  const item = clone(type);
869
945
  const id = item.name;
870
- const weight = type.weight || getters.typeWeightFor(item.label, isBasic);
871
946
 
872
947
  // Is there a virtual/spoofed type override for schema type?
873
948
  // Currently used by harvester, this should be investigated and removed if possible
874
- if (out[id]) {
875
- delete out[id];
876
- }
949
+ virtSpoofedModes.forEach((mode) => {
950
+ if (out[mode]?.[id]) {
951
+ delete out[mode][id];
952
+ }
953
+ });
877
954
 
878
955
  if ( item['public'] === false && !isDev ) {
879
956
  continue;
@@ -916,14 +993,13 @@ export const getters = {
916
993
  continue;
917
994
  }
918
995
 
919
- if ( isBasic && !getters.groupForBasicType(product, id) ) {
920
- continue;
921
- } else if ( mode === FAVORITE && !getters.isFavorite(id) ) {
922
- continue;
996
+ if (virtSpoofedModes.includes(TYPE_MODES.BASIC) && !getters.groupForBasicType(product, id) ) {
997
+ virtSpoofedModes.splice(virtSpoofedModes.indexOf(TYPE_MODES.BASIC), 1);
923
998
  }
924
999
 
925
- item.mode = mode;
926
- item.weight = weight;
1000
+ if (virtSpoofedModes.includes(TYPE_MODES.FAVORITE) && !getters.isFavorite(id) ) { // mode === TYPE_MODES.FAVORITE &&
1001
+ virtSpoofedModes.splice(virtSpoofedModes.indexOf(TYPE_MODES.FAVORITE), 1);
1002
+ }
927
1003
 
928
1004
  // Ensure labelKey is taken into account... with a mock count
929
1005
  // This is harmless if the translation doesn't require count
@@ -934,7 +1010,17 @@ export const getters = {
934
1010
  item.label = item.label || item.name;
935
1011
  }
936
1012
 
937
- out[id] = item;
1013
+ virtSpoofedModes.forEach((mode) => {
1014
+ const isBasic = mode === TYPE_MODES.BASIC;
1015
+ const weight = type.weight || getters.typeWeightFor(item.label, isBasic);
1016
+
1017
+ item.mode = mode;
1018
+ item.weight = weight;
1019
+ if (!out[mode]) {
1020
+ out[mode] = {};
1021
+ }
1022
+ out[mode][id] = item;
1023
+ });
938
1024
  }
939
1025
  }
940
1026
 
@@ -1684,22 +1770,6 @@ function _sortGroup(tree, mode) {
1684
1770
  }
1685
1771
  }
1686
1772
 
1687
- function _matchingCounts(typeObj, namespaces) {
1688
- // That was easy
1689
- if ( !typeObj.namespaced || !typeObj.byNamespace || namespaces === null || typeObj.count === null) {
1690
- return typeObj.count;
1691
- }
1692
-
1693
- let out = 0;
1694
-
1695
- // Otherwise start with 0 and count up
1696
- for ( const namespace of namespaces ) {
1697
- out += typeObj.byNamespace[namespace]?.count || 0;
1698
- }
1699
-
1700
- return out;
1701
- }
1702
-
1703
1773
  function _applyMapping(objOrValue, mappings, keyField, cache, defaultFn) {
1704
1774
  let key = objOrValue;
1705
1775
  let found = false;
@@ -2815,6 +2815,7 @@ export default class Resource {
2815
2815
  cleanYaml(yaml: any, mode?: string): any;
2816
2816
  cleanForNew(): void;
2817
2817
  cleanForDiff(): void;
2818
+ cleanForDownload(yaml: any): Promise<any>;
2818
2819
  yamlForSave(yaml: any): any;
2819
2820
  saveYaml(yaml: any): Promise<void>;
2820
2821
  _saveYaml(yaml: any): Promise<void>;
@@ -3686,6 +3687,7 @@ export function applyChangeset(obj: any, changeset: any): any;
3686
3687
  * Creates an object composed of the `object` properties `predicate` returns
3687
3688
  */
3688
3689
  export function pickBy(obj?: {}, predicate?: (value: any, key: any) => boolean): {};
3690
+ export function dropKeys(obj: any, keys: any): void;
3689
3691
  export { isEqualBasic as isEqual };
3690
3692
  export function toDictionary(array: any, callback: any): any;
3691
3693
  /**
@@ -2,6 +2,7 @@ import {
2
2
  getBlockDescriptor,
3
3
  dumpBlock,
4
4
  } from '@shell/utils/create-yaml';
5
+ import jsyaml from 'js-yaml';
5
6
 
6
7
  const key = 'example';
7
8
  const randomData = '\n foo\n bar\n';
@@ -61,6 +62,15 @@ describe('fx: dumpBlock', () => {
61
62
  });
62
63
  });
63
64
 
65
+ it('should not create a data block when the value of a key is not a string', () => {
66
+ const data = { key: { test: 'test' } };
67
+
68
+ const expectedResult = jsyaml.dump(data);
69
+ const result = dumpBlock(data);
70
+
71
+ expect(result).toStrictEqual(expectedResult);
72
+ });
73
+
64
74
  it('should retain line breaks when a line longer than 80 characters exists', () => {
65
75
  const data = {
66
76
  'managerApiConfiguration.properties': `# Sample XPlanManagerAPI Configuration (if this comment is longer than 80 characters, the output should remain the same)
@@ -465,7 +465,11 @@ export function dumpBlock(data, options = {}) {
465
465
 
466
466
  let out = parsed;
467
467
 
468
- const blockFields = Object.keys(data).filter((k) => data[k].includes('\n'));
468
+ const blockFields = Object.keys(data).filter((k) => {
469
+ if (typeof data[k] === 'string') {
470
+ return data[k].includes('\n');
471
+ }
472
+ });
469
473
 
470
474
  if (blockFields.length) {
471
475
  for (const key of blockFields) {
package/utils/object.js CHANGED
@@ -425,3 +425,13 @@ export function pickBy(obj = {}, predicate = (value, key) => false) {
425
425
  export const toDictionary = (array, callback) => Object.assign(
426
426
  {}, ...array.map((item) => ({ [item]: callback(item) }))
427
427
  );
428
+
429
+ export function dropKeys(obj, keys) {
430
+ if ( !obj ) {
431
+ return;
432
+ }
433
+
434
+ for ( const k of keys ) {
435
+ delete obj[k];
436
+ }
437
+ }