@rancher/shell 3.0.8-rc.9 → 3.0.8

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 (146) hide show
  1. package/apis/impl/apis.ts +61 -0
  2. package/apis/index.ts +40 -0
  3. package/apis/intf/modal.ts +90 -0
  4. package/apis/intf/shell.ts +36 -0
  5. package/apis/intf/slide-in.ts +98 -0
  6. package/apis/intf/system.ts +41 -0
  7. package/apis/shell/__tests__/modal.test.ts +80 -0
  8. package/apis/shell/__tests__/notifications.test.ts +71 -0
  9. package/apis/shell/__tests__/slide-in.test.ts +54 -0
  10. package/apis/shell/__tests__/system.test.ts +129 -0
  11. package/apis/shell/index.ts +38 -0
  12. package/apis/shell/modal.ts +41 -0
  13. package/apis/shell/notifications.ts +65 -0
  14. package/apis/shell/slide-in.ts +33 -0
  15. package/apis/shell/system.ts +65 -0
  16. package/apis/vue-shim.d.ts +11 -0
  17. package/assets/styles/global/_tooltip.scss +6 -1
  18. package/assets/translations/en-us.yaml +5 -0
  19. package/components/ActionMenuShell.vue +3 -1
  20. package/components/CruResource.vue +8 -1
  21. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +50 -1
  22. package/components/Drawer/ResourceDetailDrawer/composables.ts +19 -0
  23. package/components/Drawer/ResourceDetailDrawer/index.vue +3 -1
  24. package/components/LocaleSelector.vue +2 -2
  25. package/components/ModalManager.vue +11 -1
  26. package/components/Questions/__tests__/Yaml.test.ts +1 -1
  27. package/components/RelatedResources.vue +5 -0
  28. package/components/Resource/Detail/ResourcePopover/index.vue +5 -1
  29. package/components/ResourceDetail/Masthead/latest.vue +23 -21
  30. package/components/ResourceDetail/index.vue +3 -0
  31. package/components/ResourceTable.vue +54 -21
  32. package/components/SlideInPanelManager.vue +16 -11
  33. package/components/SortableTable/THead.vue +2 -1
  34. package/components/SortableTable/index.vue +20 -2
  35. package/components/Tabbed/index.vue +37 -2
  36. package/components/__tests__/NamespaceFilter.test.ts +49 -0
  37. package/components/auth/SelectPrincipal.vue +4 -0
  38. package/components/auth/login/ldap.vue +3 -3
  39. package/components/fleet/FleetSecretSelector.vue +1 -1
  40. package/components/form/KeyValue.vue +1 -1
  41. package/components/form/NameNsDescription.vue +1 -1
  42. package/components/form/NodeScheduling.vue +2 -2
  43. package/components/form/ResourceTabs/composable.ts +2 -2
  44. package/components/form/ResourceTabs/index.vue +0 -2
  45. package/components/form/__tests__/NameNsDescription.test.ts +42 -0
  46. package/components/formatter/LinkName.vue +5 -0
  47. package/components/nav/Group.vue +25 -7
  48. package/components/nav/Header.vue +1 -1
  49. package/components/nav/NamespaceFilter.vue +1 -0
  50. package/components/nav/Type.vue +17 -6
  51. package/components/nav/WindowManager/panels/TabBodyContainer.vue +1 -1
  52. package/components/nav/__tests__/Type.test.ts +59 -0
  53. package/composables/cruResource.ts +27 -0
  54. package/composables/focusTrap.ts +3 -1
  55. package/composables/resourceDetail.ts +15 -0
  56. package/composables/useLabeledFormElement.ts +3 -4
  57. package/config/product/fleet.js +1 -1
  58. package/config/router/navigation-guards/clusters.js +3 -3
  59. package/config/router/navigation-guards/products.js +1 -1
  60. package/config/router/routes.js +1 -5
  61. package/core/__tests__/extension-manager-impl.test.js +437 -0
  62. package/core/extension-manager-impl.js +6 -27
  63. package/core/plugin-helpers.ts +2 -2
  64. package/core/plugin.ts +9 -1
  65. package/core/plugins-loader.js +2 -2
  66. package/core/types-provisioning.ts +4 -0
  67. package/core/types.ts +35 -0
  68. package/detail/provisioning.cattle.io.cluster.vue +8 -6
  69. package/dialog/DeveloperLoadExtensionDialog.vue +1 -1
  70. package/dialog/MoveNamespaceDialog.vue +20 -4
  71. package/dialog/SearchDialog.vue +1 -0
  72. package/dialog/__tests__/MoveNamespaceDialog.test.ts +249 -0
  73. package/directives/__tests__/clean-tooltip.test.ts +298 -0
  74. package/directives/clean-tooltip.ts +234 -0
  75. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -2
  76. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +98 -1
  77. package/edit/fleet.cattle.io.helmop.vue +5 -0
  78. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +21 -21
  79. package/edit/provisioning.cattle.io.cluster/index.vue +5 -5
  80. package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -8
  81. package/edit/resources.cattle.io.restore.vue +1 -1
  82. package/edit/workload/Job.vue +2 -2
  83. package/edit/workload/index.vue +1 -1
  84. package/initialize/install-plugins.js +4 -5
  85. package/machine-config/azure.vue +1 -1
  86. package/machine-config/components/GCEImage.vue +1 -1
  87. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +16 -0
  88. package/models/chart.js +70 -74
  89. package/models/management.cattle.io.cluster.js +1 -1
  90. package/models/provisioning.cattle.io.cluster.js +11 -3
  91. package/package.json +7 -7
  92. package/pages/auth/login.vue +3 -3
  93. package/pages/auth/setup.vue +1 -1
  94. package/pages/auth/verify.vue +3 -3
  95. package/pages/c/_cluster/apps/charts/index.vue +122 -24
  96. package/pages/c/_cluster/apps/charts/install.vue +33 -0
  97. package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
  98. package/pages/c/_cluster/fleet/index.vue +4 -7
  99. package/pages/c/_cluster/settings/index.vue +5 -0
  100. package/pkg/auto-import.js +3 -3
  101. package/pkg/dynamic-importer.lib.js +1 -1
  102. package/pkg/import.js +1 -1
  103. package/plugins/__tests__/mutations.tests.ts +179 -0
  104. package/plugins/dashboard-store/getters.js +1 -1
  105. package/plugins/dashboard-store/model-loader.js +1 -1
  106. package/plugins/dashboard-store/mutations.js +23 -2
  107. package/plugins/dashboard-store/resource-class.js +8 -3
  108. package/plugins/plugin.js +2 -2
  109. package/plugins/steve/__tests__/steve-pagination-utils.test.ts +301 -128
  110. package/plugins/steve/steve-class.js +1 -1
  111. package/plugins/steve/steve-pagination-utils.ts +108 -43
  112. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  113. package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -1
  114. package/rancher-components/RcDropdown/useDropdownContext.ts +2 -4
  115. package/rancher-components/RcItemCard/RcItemCard.vue +1 -1
  116. package/scripts/publish-shell.sh +25 -0
  117. package/store/__tests__/catalog.test.ts +1 -1
  118. package/store/__tests__/type-map.test.ts +164 -2
  119. package/store/auth.js +23 -11
  120. package/store/i18n.js +3 -3
  121. package/store/index.js +5 -3
  122. package/store/notifications.ts +2 -0
  123. package/store/prefs.js +2 -2
  124. package/store/type-map.js +17 -7
  125. package/types/internal-api/shell/modal.d.ts +6 -6
  126. package/types/notifications/index.ts +126 -15
  127. package/types/rancher/index.d.ts +9 -0
  128. package/types/shell/index.d.ts +16 -1
  129. package/types/vue-shim.d.ts +5 -4
  130. package/utils/__tests__/router.test.js +238 -0
  131. package/utils/cluster.js +4 -1
  132. package/utils/fleet.ts +8 -1
  133. package/utils/pagination-utils.ts +2 -2
  134. package/utils/pagination-wrapper.ts +1 -1
  135. package/utils/router.js +50 -0
  136. package/utils/unit-tests/pagination-utils.spec.ts +8 -8
  137. package/vue.config.js +3 -3
  138. package/composables/useExtensionManager.ts +0 -17
  139. package/core/__test__/extension-manager-impl.test.js +0 -236
  140. package/core/plugins.js +0 -38
  141. package/directives/clean-tooltip.js +0 -32
  142. package/plugins/internal-api/index.ts +0 -37
  143. package/plugins/internal-api/shared/base-api.ts +0 -13
  144. package/plugins/internal-api/shell/shell.api.ts +0 -108
  145. package/types/internal-api/shell/growl.d.ts +0 -25
  146. package/types/internal-api/shell/slideIn.d.ts +0 -15
@@ -15,7 +15,7 @@ function registerFile(file, type, pkg, f) {
15
15
  const importType = (f === 'models') ? 'require' : 'import';
16
16
  const chunkName = (f === 'l10n') ? '' : `/* webpackChunkName: "${ f }" */`;
17
17
 
18
- return ` $plugin.register('${ f }', '${ type }', () => ${ importType }(${ chunkName }'${ pkg }/${ f }/${ file }'));\n`;
18
+ return ` $extension.register('${ f }', '${ type }', () => ${ importType }(${ chunkName }'${ pkg }/${ f }/${ file }'));\n`;
19
19
  }
20
20
 
21
21
  function register(file, pkg, f) {
@@ -29,7 +29,7 @@ function register(file, pkg, f) {
29
29
  // This ensures that the webpackChunkName is respected (require.context does not support this) - so when build as a library
30
30
  // the code splitting will be respected
31
31
  function generateTypeImport(pkg, dir) {
32
- let content = 'export function importTypes($plugin) { \n';
32
+ let content = 'export function importTypes($extension) { \n';
33
33
 
34
34
  // Auto-import if the folder exists
35
35
  contextFolders.forEach((f) => {
@@ -77,7 +77,7 @@ function generateTypeImport(pkg, dir) {
77
77
  // and then restart the dev server for it to be picked up.
78
78
  function generateDynamicTypeImport(pkg, dir) {
79
79
  const template = fs.readFileSync(path.join(__dirname, 'import.js'), { encoding: 'utf8' });
80
- let content = 'export function importTypes($plugin) { \n';
80
+ let content = 'export function importTypes($extension) { \n';
81
81
 
82
82
  // Auto-import if the folder exists
83
83
  contextFolders.forEach((f) => {
@@ -41,7 +41,7 @@ export function listProducts() {
41
41
  return [];
42
42
  }
43
43
 
44
- export function loadProduct(name, $plugin) {
44
+ export function loadProduct(name, $extension) {
45
45
  return () => undefined;
46
46
  }
47
47
 
package/pkg/import.js CHANGED
@@ -6,5 +6,5 @@ _NAME.forEach((f) => {
6
6
 
7
7
  name = name.substr(0, ext);
8
8
 
9
- $plugin.register('DIR', name, () => REQUIRE(CHUNK`BASE/DIR/${ name }EXT`)); // eslint-disable-line no-undef
9
+ $extension.register('DIR', name, () => REQUIRE(CHUNK`BASE/DIR/${ name }EXT`)); // eslint-disable-line no-undef
10
10
  });
@@ -0,0 +1,179 @@
1
+ import mutations from '@shell/plugins/dashboard-store/mutations';
2
+ import getters from '@shell/plugins/steve/getters';
3
+ import { StorePagination } from '@shell/types/store/pagination.types';
4
+ import { VuexStore } from '@shell/types/store/vuex';
5
+ import Pod from '@shell/models/pod';
6
+
7
+ describe('mutations', () => {
8
+ jest.mock('vue', () => ({ reactive: jest.fn((arr) => arr) }));
9
+
10
+ // Import the function we are testing
11
+ const { loadPage } = mutations;
12
+
13
+ let state: any;
14
+ let ctx: Partial<VuexStore>;
15
+ let resourceType: string;
16
+ let pagination: Partial<StorePagination>;
17
+ let revision: string;
18
+
19
+ beforeEach(() => {
20
+ jest.clearAllMocks();
21
+
22
+ state = { types: {} };
23
+ ctx = {
24
+ getters: {
25
+ keyFieldForType: jest.fn(() => 'id'),
26
+ cleanResource: getters.cleanResource()
27
+ }
28
+ };
29
+ resourceType = 'pod';
30
+ pagination = { request: undefined, result: undefined };
31
+ revision = 'abc-123';
32
+ ctx.getters.classify = () => Pod;
33
+ });
34
+
35
+ describe('loadPage', () => {
36
+ it('should perform an initial load into an empty cache', () => {
37
+ const data = [
38
+ {
39
+ id: 'a', name: 'pod-a', type: resourceType
40
+ },
41
+ {
42
+ id: 'b', name: 'pod-b', type: resourceType
43
+ }
44
+ ];
45
+
46
+ loadPage(state, {
47
+ type: resourceType, data, ctx, pagination, revision
48
+ });
49
+
50
+ // Get the cache created by the *real* 'registerType'
51
+ const cache = state.types[resourceType];
52
+
53
+ // Check cache metadata
54
+ expect(cache.generation).toBe(1);
55
+ expect(cache.havePage).toBe(pagination);
56
+ expect(cache.revision).toBe(revision);
57
+
58
+ // Check 'list' (should contain classified proxies)
59
+ expect(cache.list).toHaveLength(2);
60
+ expect(cache.list[0]).toStrictEqual(expect.objectContaining({ id: 'a' }));
61
+ expect(cache.list[1]).toStrictEqual(expect.objectContaining({ id: 'b' }));
62
+
63
+ // Check 'map'
64
+ expect(cache.map.size).toBe(2);
65
+ expect(cache.map.get('a')).toBe(cache.list[0]); // Map and list must reference the *same* objects
66
+ expect(cache.map.get('b')).toBe(cache.list[1]);
67
+ });
68
+
69
+ it('should update existing resources in-place (tests "replaceResource" effect)', () => {
70
+ const v1Resource = new Pod({ id: 'a', metadata: { a: 'v1-a' } }, ctx);
71
+
72
+ state.types[resourceType] = {
73
+ list: [v1Resource],
74
+ map: new Map([['a', v1Resource]]),
75
+ generation: 1,
76
+ };
77
+
78
+ // Keep a reference to the *original list array* and *original resource object*
79
+ const originalListRef = state.types[resourceType].list;
80
+ const originalResourceRef = v1Resource;
81
+
82
+ const newData = [
83
+ { id: 'a', metadata: { a: 'v2-a' } }, // This is the update
84
+ ];
85
+ const newRevision = 'rev-2';
86
+
87
+ loadPage(state, {
88
+ type: resourceType, data: newData, ctx, pagination, revision: newRevision
89
+ });
90
+
91
+ const cache = state.types[resourceType];
92
+
93
+ expect(cache.list).toBe(originalListRef);
94
+ expect(cache.list).toHaveLength(1);
95
+
96
+ expect(cache.list[0]).toBe(originalResourceRef);
97
+
98
+ expect(originalResourceRef.metadata.a).toBe('v2-a');
99
+
100
+ expect(cache.map.get('a')).toBe(originalResourceRef);
101
+
102
+ expect(cache.generation).toBe(2);
103
+ expect(cache.revision).toBe(newRevision);
104
+ });
105
+
106
+ it('should remove stale data from list and map (paging)', () => {
107
+ const page1Proxy = new Pod({ id: 'a', name: 'stale-pod' }, ctx);
108
+
109
+ state.types[resourceType] = {
110
+ list: [page1Proxy],
111
+ map: new Map([['a', page1Proxy]]),
112
+ generation: 1,
113
+ };
114
+
115
+ const listRef = state.types[resourceType].list;
116
+
117
+ const page2Data = [
118
+ { id: 'b', name: 'new-pod' },
119
+ ];
120
+
121
+ loadPage(state, {
122
+ type: resourceType, data: page2Data, ctx, pagination, revision
123
+ });
124
+
125
+ const cache = state.types[resourceType];
126
+
127
+ expect(cache.list).toBe(listRef); // List reference must be preserved
128
+
129
+ expect(cache.list).toHaveLength(1);
130
+ expect(cache.list[0].id).toBe('b');
131
+
132
+ expect(cache.map.size).toBe(1);
133
+ expect(cache.map.has('a')).toBe(false); // Stale 'a' is gone
134
+ expect(cache.map.has('b')).toBe(true);
135
+ });
136
+
137
+ it('should handle partial overlaps (update, remove stale, add new)', () => {
138
+ // 1. Pre-load cache
139
+ const staleResource = new Pod({ id: 'a', prop: 'stale-pod' }, ctx);
140
+ const v1Resource = new Pod({ id: 'b', prop: 'v1-pod-b' }, ctx);
141
+
142
+ state.types[resourceType] = {
143
+ list: [staleResource, v1Resource],
144
+ map: new Map([['a', staleResource], ['b', v1Resource]]),
145
+ generation: 1,
146
+ };
147
+
148
+ const listRef = state.types[resourceType].list;
149
+ const v1ResourceRef = v1Resource;
150
+
151
+ const newData = [
152
+ { id: 'b', prop: 'v2-pod-b' }, // Updated
153
+ { id: 'c', prop: 'new-pod-c' }, // New
154
+ ];
155
+
156
+ loadPage(state, {
157
+ type: resourceType, data: newData, ctx, pagination, revision
158
+ });
159
+
160
+ const cache = state.types[resourceType];
161
+
162
+ expect(cache.list).toBe(listRef); // List reference preserved
163
+
164
+ expect(cache.list).toHaveLength(2);
165
+ expect(cache.list[0].id).toBe('b');
166
+ expect(cache.list[1].id).toBe('c');
167
+
168
+ expect(cache.list[0]).toBe(v1ResourceRef);
169
+ expect(v1ResourceRef.prop).toBe('v2-pod-b');
170
+
171
+ expect(cache.list[1]).toStrictEqual(expect.objectContaining({ id: 'c' }));
172
+
173
+ expect(cache.map.size).toBe(2);
174
+ expect(cache.map.has('a')).toBe(false); // Stale, removed
175
+ expect(cache.map.get('b')).toBe(v1ResourceRef); // Updated
176
+ expect(cache.map.get('c')).toBe(cache.list[1]); // Added
177
+ });
178
+ });
179
+ });
@@ -543,7 +543,7 @@ export default {
543
543
  const store = state.config.namespace;
544
544
  const resource = id || context ? { id, context } : null;
545
545
 
546
- return paginationUtils.isEnabled({ rootGetters, $plugin: rootState.$plugin }, { store, resource });
546
+ return paginationUtils.isEnabled({ rootGetters, $extension: rootState.$extension }, { store, resource });
547
547
  },
548
548
 
549
549
  /**
@@ -13,7 +13,7 @@ function find(cache, type, rootState) {
13
13
  }
14
14
 
15
15
  try {
16
- const pluginModel = rootState.$plugin.getDynamic('models', type);
16
+ const pluginModel = rootState.$extension.getDynamic('models', type);
17
17
  let base;
18
18
 
19
19
  if (!pluginModel) {
@@ -515,8 +515,29 @@ export default {
515
515
  cache.generation++;
516
516
 
517
517
  // Update list
518
- clear(cache.list);
519
- addObjects(cache.list, proxies);
518
+ // We want to update the old page with the new page
519
+ // We need to keep the cache.list object reference the same
520
+ // We need to keep objects that represent the same resource the same (don't remove the old object and add a new object for the same resource)
521
+ // - this helps anywhere that works with object references (sortable table selection is maintained by object reference)
522
+
523
+ // Create a map of the current references in cache.list
524
+ const currentPageMap = new Map(cache.list.map((i) => [i[keyField], i]));
525
+
526
+ // Create an array containing the new page, but using the same object for resources that exist in old and new page
527
+ const newPage = proxies.map((p) => {
528
+ const existing = currentPageMap.get(p[keyField]);
529
+
530
+ if (existing) {
531
+ replaceResource(existing, p, ctx.getters);
532
+
533
+ return existing;
534
+ }
535
+
536
+ return p;
537
+ });
538
+
539
+ // Replace the old cache.list entries with the new page
540
+ cache.list.splice(0, cache.list.length, ...newPage);
520
541
 
521
542
  // Update Map (remove stale)
522
543
  cache.map.forEach((value, key) => {
@@ -610,7 +610,11 @@ export default class Resource {
610
610
  }
611
611
 
612
612
  get '$plugin'() {
613
- return this.$ctx.rootState?.$plugin;
613
+ return this.$ctx.rootState?.$extension;
614
+ }
615
+
616
+ get '$extension'() {
617
+ return this.$ctx.rootState?.$extension;
614
618
  }
615
619
 
616
620
  get customValidationRules() {
@@ -1359,6 +1363,7 @@ export default class Resource {
1359
1363
 
1360
1364
  get _detailLocation() {
1361
1365
  const schema = this.$getters['schemaFor'](this.type);
1366
+ const isNamespaced = schema?.attributes?.namespaced;
1362
1367
 
1363
1368
  const id = this.id?.replace(/.*\//, '');
1364
1369
 
@@ -1368,7 +1373,7 @@ export default class Resource {
1368
1373
  product: this.$rootGetters['productId'],
1369
1374
  cluster: this.$rootGetters['clusterId'],
1370
1375
  resource: this.type,
1371
- namespace: this.metadata?.namespace,
1376
+ namespace: isNamespaced && this.metadata?.namespace ? this.metadata.namespace : undefined,
1372
1377
  id,
1373
1378
  }
1374
1379
  };
@@ -1777,7 +1782,7 @@ export default class Resource {
1777
1782
  CustomValidators[validatorName](pathValue, this.$rootGetters, errors, validatorArgs, displayKey, data);
1778
1783
  } else if (!isEmpty(validatorName) && !validatorExists) {
1779
1784
  // Check if validator is imported from plugin
1780
- const pluginValidator = this.$rootState.$plugin?.getValidator(validatorName);
1785
+ const pluginValidator = this.$rootState.$extension?.getValidator(validatorName);
1781
1786
 
1782
1787
  if (pluginValidator) {
1783
1788
  pluginValidator(pathValue, this.$rootGetters, errors, validatorArgs, displayKey, data);
package/plugins/plugin.js CHANGED
@@ -44,7 +44,7 @@ export default async function(context) {
44
44
  const res = await allHashSettled(fetches);
45
45
 
46
46
  // Initialize the built-in extensions now - this is now done here so that built-in extensions get the same, correct environment data (version etc)
47
- context.$plugin.loadBuiltinExtensions();
47
+ context.$extension.loadBuiltinExtensions();
48
48
 
49
49
  if (res.plugins?.status === 'rejected') {
50
50
  throw new Error(res.reason);
@@ -60,7 +60,7 @@ export default async function(context) {
60
60
  const shouldNotLoad = shouldNotLoadPlugin(plugin, { rancherVersion, kubeVersion }, context.store.getters['uiplugins/plugins'] || []); // Error key string or boolean
61
61
 
62
62
  if (!shouldNotLoad) {
63
- hash[plugin.name] = context.$plugin.loadPluginAsync(plugin);
63
+ hash[plugin.name] = context.$extension.loadPluginAsync(plugin);
64
64
  } else {
65
65
  context.store.dispatch('uiplugins/setError', { name: plugin.name, error: shouldNotLoad });
66
66
  }