@rancher/shell 3.0.2-rc.2 → 3.0.2-rc.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 (141) hide show
  1. package/assets/styles/base/_basic.scss +5 -7
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_tooltip.scss +2 -2
  4. package/assets/styles/themes/_dark.scss +14 -2
  5. package/assets/styles/themes/_light.scss +7 -2
  6. package/assets/styles/vendor/vue-select.scss +4 -0
  7. package/assets/translations/en-us.yaml +44 -5
  8. package/components/BannerGraphic.vue +0 -42
  9. package/components/ButtonMultiAction.vue +1 -1
  10. package/components/Carousel.vue +36 -29
  11. package/components/CommunityLinks.vue +6 -1
  12. package/components/GrowlManager.vue +9 -2
  13. package/components/LocaleSelector.vue +8 -1
  14. package/components/PaginatedResourceTable.vue +4 -7
  15. package/components/ProgressBarMulti.vue +14 -0
  16. package/components/Questions/Reference.vue +57 -28
  17. package/components/SelectIconGrid.vue +12 -1
  18. package/components/SideNav.vue +12 -38
  19. package/components/SortableTable/index.vue +1 -0
  20. package/components/Tabbed/index.vue +12 -1
  21. package/components/YamlEditor.vue +1 -0
  22. package/components/auth/Principal.vue +5 -3
  23. package/components/fleet/FleetClusters.vue +82 -1
  24. package/components/fleet/FleetRepos.vue +13 -30
  25. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  26. package/components/form/ChangePassword.vue +2 -0
  27. package/components/form/ColorInput.vue +24 -1
  28. package/components/form/FileSelector.vue +2 -0
  29. package/components/form/KeyValue.vue +230 -160
  30. package/components/form/LabeledSelect.vue +1 -1
  31. package/components/form/PlusMinus.vue +14 -2
  32. package/components/form/ResourceLabeledSelect.vue +13 -53
  33. package/components/form/ResourceSelector.vue +1 -0
  34. package/components/form/ResourceTabs/index.vue +79 -36
  35. package/components/form/SecretSelector.vue +2 -2
  36. package/components/form/__tests__/KeyValue.test.ts +1 -1
  37. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  38. package/components/formatter/FleetSummaryGraph.vue +6 -7
  39. package/components/formatter/WorkloadHealthScale.vue +7 -0
  40. package/components/nav/Group.vue +30 -4
  41. package/components/nav/Header.vue +82 -114
  42. package/components/nav/HeaderPageActionMenu.vue +27 -131
  43. package/components/nav/NamespaceFilter.vue +1 -1
  44. package/components/nav/Type.vue +15 -0
  45. package/config/home-links.js +21 -13
  46. package/config/labels-annotations.js +2 -0
  47. package/config/page-actions.js +1 -0
  48. package/config/pagination-table-headers.js +15 -1
  49. package/config/product/explorer.js +7 -17
  50. package/config/table-headers.js +6 -0
  51. package/config/version.js +5 -1
  52. package/core/plugin.ts +41 -1
  53. package/core/plugins.js +125 -72
  54. package/core/types-provisioning.ts +91 -2
  55. package/core/types.ts +55 -0
  56. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  57. package/detail/catalog.cattle.io.app.vue +1 -1
  58. package/detail/fleet.cattle.io.cluster.vue +3 -3
  59. package/detail/namespace.vue +13 -19
  60. package/detail/networking.k8s.io.ingress.vue +13 -53
  61. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  62. package/detail/workload/index.vue +3 -3
  63. package/dialog/AddCustomBadgeDialog.vue +5 -1
  64. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  65. package/edit/auth/ldap/config.vue +24 -0
  66. package/edit/auth/saml.vue +8 -6
  67. package/edit/fleet.cattle.io.gitrepo.vue +7 -1
  68. package/edit/logging-flow/index.vue +4 -19
  69. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  70. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  71. package/edit/provisioning.cattle.io.cluster/index.vue +13 -1
  72. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  73. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  74. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  75. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  76. package/edit/service.vue +1 -2
  77. package/list/networking.k8s.io.ingress.vue +1 -1
  78. package/list/node.vue +15 -8
  79. package/list/persistentvolume.vue +12 -4
  80. package/list/service.vue +1 -1
  81. package/list/workload.vue +4 -0
  82. package/mixins/chart.js +4 -1
  83. package/models/catalog.cattle.io.app.js +3 -1
  84. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  85. package/models/fleet.cattle.io.bundle.js +0 -11
  86. package/models/fleet.cattle.io.cluster.js +17 -1
  87. package/models/fleet.cattle.io.gitrepo.js +86 -50
  88. package/models/provisioning.cattle.io.cluster.js +47 -2
  89. package/models/service.js +1 -0
  90. package/models/workload.js +19 -1
  91. package/package.json +5 -4
  92. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  93. package/pages/c/_cluster/explorer/ConfigBadge.vue +8 -7
  94. package/pages/c/_cluster/explorer/index.vue +13 -6
  95. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  96. package/pages/c/_cluster/fleet/index.vue +75 -89
  97. package/pages/c/_cluster/settings/links.vue +2 -2
  98. package/pages/diagnostic.vue +17 -15
  99. package/pages/home.vue +32 -6
  100. package/plugins/clean-html.js +50 -0
  101. package/plugins/dashboard-store/resource-class.js +4 -0
  102. package/plugins/plugin.js +54 -49
  103. package/plugins/steve/mutations.js +1 -1
  104. package/plugins/steve/steve-class.js +8 -0
  105. package/plugins/steve/steve-pagination-utils.ts +3 -1
  106. package/rancher-components/Accordion/Accordion.vue +4 -4
  107. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  108. package/rancher-components/Card/Card.vue +27 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  110. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  111. package/rancher-components/Form/LabeledInput/LabeledInput.vue +18 -1
  112. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  113. package/rancher-components/RcButton/RcButton.vue +90 -0
  114. package/rancher-components/RcButton/index.ts +2 -0
  115. package/rancher-components/RcButton/types.ts +17 -0
  116. package/rancher-components/RcDropdown/RcDropdown.vue +111 -0
  117. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  118. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  119. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +43 -0
  120. package/rancher-components/RcDropdown/index.ts +4 -0
  121. package/rancher-components/RcDropdown/types.ts +22 -0
  122. package/rancher-components/RcDropdown/useDropdownCollection.ts +45 -0
  123. package/rancher-components/RcDropdown/useDropdownContext.ts +83 -0
  124. package/scripts/test-plugins-build.sh +2 -0
  125. package/scripts/typegen.sh +2 -0
  126. package/store/catalog.js +1 -1
  127. package/tsconfig.json +2 -1
  128. package/types/components/paginatedResourceTable.ts +25 -0
  129. package/types/components/resourceLabeledSelect.ts +48 -0
  130. package/types/resources/fleet.d.ts +17 -0
  131. package/types/shell/index.d.ts +61 -0
  132. package/utils/auth.js +5 -1
  133. package/utils/cluster.js +106 -0
  134. package/utils/fleet.ts +35 -3
  135. package/utils/ingress.ts +64 -0
  136. package/utils/uiplugins.ts +56 -44
  137. package/utils/validators/cron-schedule.js +7 -2
  138. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  139. package/utils/validators/formRules/index.ts +20 -5
  140. package/vue.config.js +1 -1
  141. package/components/RelatedWorkloadsTable.vue +0 -50
package/core/plugins.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import { productsLoaded } from '@shell/store/type-map';
2
2
  import { clearModelCache } from '@shell/plugins/dashboard-store/model-loader';
3
- import { Plugin } from './plugin';
3
+ import { EXT_IDS, Plugin } from './plugin';
4
4
  import { PluginRoutes } from './plugin-routes';
5
5
  import { UI_PLUGIN_BASE_URL } from '@shell/config/uiplugins';
6
6
  import { ExtensionPoint } from './types';
7
-
8
- const MODEL_TYPE = 'models';
7
+ import { addLinkInterceptor, removeLinkInterceptor } from '@shell/plugins/clean-html';
9
8
 
10
9
  export default function(context, inject, vueApp) {
11
10
  const {
@@ -22,10 +21,28 @@ export default function(context, inject, vueApp) {
22
21
 
23
22
  const uiConfig = {};
24
23
 
24
+ // Builtin extensions - these are registered when the UI loads and then initialized/loaded at the same time as the external extensions
25
+ let builtin = [];
26
+
25
27
  for (const ep in ExtensionPoint) {
26
28
  uiConfig[ExtensionPoint[ep]] = {};
27
29
  }
28
30
 
31
+ /**
32
+ * When an extension adds a model extension, it provides the class - we will instantiate that class and store and use that
33
+ */
34
+ function instantiateModelExtension($plugin, clz) {
35
+ const context = {
36
+ dispatch: store.dispatch,
37
+ getters: store.getters,
38
+ t: store.getters['i18n/t'],
39
+ $axios,
40
+ $plugin,
41
+ };
42
+
43
+ return new clz(context);
44
+ }
45
+
29
46
  inject(
30
47
  'plugin',
31
48
  {
@@ -78,77 +95,79 @@ export default function(context, inject, vueApp) {
78
95
  element.id = id;
79
96
  element.dataset.purpose = 'extension';
80
97
 
81
- // id is `<product>-<version>`.
82
- const oldPlugin = Object.values(plugins).find((p) => id.startsWith(p.name));
83
-
84
- let removed = Promise.resolve();
85
-
86
- if (oldPlugin) {
87
- // Uninstall existing plugin if there is one. This ensures that last loaded plugin is not always used
88
- // (nav harv1-->harv2-->harv1 and harv2 would be shown)
89
- removed = this.removePlugin(oldPlugin.name).then(() => {
90
- delete window[oldPlugin.id];
91
-
92
- delete plugins[oldPlugin.id];
93
-
94
- const oldElement = document.getElementById(oldPlugin.id);
95
-
96
- oldElement.parentElement.removeChild(oldElement);
97
- });
98
- }
99
-
100
- removed.then(() => {
101
- element.onload = () => {
102
- if (!window[id]) {
103
- return reject(new Error('Could not load plugin code'));
104
- }
98
+ element.onload = () => {
99
+ if (!window[id] || (typeof window[id].default !== 'function')) {
100
+ return reject(new Error('Could not load plugin code'));
101
+ }
105
102
 
106
- // Update the timestamp that new plugins were loaded - may be needed
107
- // to update caches when new plugins are loaded
108
- _lastLoaded = new Date().getTime();
103
+ // Update the timestamp that new plugins were loaded - may be needed
104
+ // to update caches when new plugins are loaded
105
+ _lastLoaded = new Date().getTime();
109
106
 
110
- // name is the name of the plugin, including the version number
111
- const plugin = new Plugin(id);
107
+ // name is the name of the plugin, including the version number
108
+ const plugin = new Plugin(id);
112
109
 
113
- plugins[id] = plugin;
110
+ plugins[id] = plugin;
114
111
 
115
- // Initialize the plugin
112
+ // Initialize the plugin
113
+ try {
116
114
  window[id].default(plugin, this.internal());
115
+ } catch (e) {
116
+ delete plugins[id];
117
117
 
118
- // Uninstall existing plugin if there is one
119
- this.removePlugin(plugin.name); // Removing this causes the plugin to not load on refresh
120
-
121
- // Load all of the types etc from the plugin
122
- this.applyPlugin(plugin);
123
-
124
- // Add the plugin to the store
125
- store.dispatch('uiplugins/addPlugin', plugin);
118
+ return reject(new Error('Could not initialize plugin'));
119
+ }
126
120
 
127
- resolve();
128
- };
121
+ // Load all of the types etc from the plugin
122
+ this.applyPlugin(plugin);
129
123
 
130
- element.onerror = (e) => {
131
- element.parentElement.removeChild(element);
124
+ // Add the plugin to the store
125
+ store.dispatch('uiplugins/addPlugin', plugin);
132
126
 
133
- // Massage the error into something useful
134
- const errorMessage = `Failed to load script from '${ e.target.src }'`;
127
+ resolve();
128
+ };
135
129
 
136
- console.error(errorMessage, e); // eslint-disable-line no-console
137
- reject(new Error(errorMessage)); // This is more useful where it's used
138
- };
130
+ element.onerror = (e) => {
131
+ element.parentElement.removeChild(element);
139
132
 
140
- document.head.appendChild(element);
141
- }).catch((e) => {
142
- const errorMessage = `Failed to unload old plugin${ oldPlugin?.id }`;
133
+ // Massage the error into something useful
134
+ const errorMessage = `Failed to load script from '${ e.target.src }'`;
143
135
 
144
136
  console.error(errorMessage, e); // eslint-disable-line no-console
145
137
  reject(new Error(errorMessage)); // This is more useful where it's used
146
- });
138
+ };
139
+
140
+ document.head.appendChild(element);
147
141
  });
148
142
  },
149
143
 
150
- // Used by the dynamic loader when a plugin is included in the build
151
- initPlugin(id, module) {
144
+ /**
145
+ * Load the builtin extensions by initializing them in turn
146
+ */
147
+ loadBuiltinExtensions() {
148
+ builtin.forEach((ext) => {
149
+ this.initBuiltinExtension(ext.id, ext.module);
150
+ });
151
+
152
+ // We've loaded the builtin extensions, so clear out the list so we don't load again
153
+ builtin = [];
154
+ },
155
+
156
+ /**
157
+ * Register a builtin extension that should be loaded
158
+ *
159
+ * Used by the dynamic loader when a plugin is included in the build (see shell/vue.config.js)
160
+ */
161
+ registerBuiltinExtension(id, module) {
162
+ builtin.push({ id, module });
163
+ },
164
+
165
+ /**
166
+ * Initialize a builtin extension
167
+ *
168
+ * This is only used by the 'loadBuiltinExtensions' function above
169
+ */
170
+ initBuiltinExtension(id, module) {
152
171
  const plugin = new Plugin(id);
153
172
 
154
173
  // Mark the plugin as being built-in
@@ -160,19 +179,32 @@ export default function(context, inject, vueApp) {
160
179
  const p = module;
161
180
 
162
181
  try {
163
- p.default(plugin, this.internal());
164
-
165
- // Uninstall existing product if there is one
166
- this.removePlugin(plugin.name);
167
-
168
- // Load all of the types etc from the plugin
169
- this.applyPlugin(plugin);
170
-
171
- // Add the plugin to the store
172
- store.dispatch('uiplugins/addPlugin', plugin);
182
+ const load = p.default(plugin, this.internal());
183
+
184
+ // The function must explicitly return false to skip loading of the extension (this is only allows on builtin extensions)
185
+ // Only built-in extensions can return that they should not be loaded, because the extension can still do 'things'
186
+ // in its init code (inject code, styles etc), so we do not want to hide an extension that has 'partially' loaded,
187
+ // just because it tells us it should not load.
188
+ // Built-in extensions are compiled into the app, so there is a level of trust assumed with them
189
+ if (load !== false) {
190
+ // Update last load so that the translations get loaded
191
+ _lastLoaded = new Date().getTime();
192
+
193
+ // Load all of the types etc from the extension
194
+ this.applyPlugin(plugin);
195
+
196
+ // Add the extension to the store
197
+ store.dispatch('uiplugins/addPlugin', plugin);
198
+ } else {
199
+ // Plugin did not load, so remove it so it is not shown as loaded
200
+ delete plugins[id];
201
+ }
173
202
  } catch (e) {
174
- console.error(`Error loading plugin ${ plugin.name }`); // eslint-disable-line no-console
203
+ console.error(`Error loading extension ${ plugin.name }`); // eslint-disable-line no-console
175
204
  console.error(e); // eslint-disable-line no-console
205
+
206
+ // Plugin did not load, so remove it so it is not shown as loaded
207
+ delete plugins[id];
176
208
  }
177
209
  },
178
210
 
@@ -189,7 +221,7 @@ export default function(context, inject, vueApp) {
189
221
  try {
190
222
  await this.removePlugin(plugin.name);
191
223
  } catch (e) {
192
- console.error('Error removing plugin', e); // eslint-disable-line no-console
224
+ console.error('Error removing extension', e); // eslint-disable-line no-console
193
225
  }
194
226
 
195
227
  delete plugins[plugin.id];
@@ -215,7 +247,7 @@ export default function(context, inject, vueApp) {
215
247
  Object.keys(plugin.types[typ]).forEach((name) => {
216
248
  this.unregister(typ, name);
217
249
 
218
- if (typ === MODEL_TYPE) {
250
+ if (typ === EXT_IDS.MODELS) {
219
251
  clearModelCache(name);
220
252
  }
221
253
  });
@@ -246,6 +278,13 @@ export default function(context, inject, vueApp) {
246
278
  delete validators[key];
247
279
  });
248
280
 
281
+ // Remove link interceptors
282
+ if (plugin.types.linkInterceptor) {
283
+ Object.keys(plugin.types.linkInterceptor).forEach((name) => {
284
+ removeLinkInterceptor(plugin.types.linkInterceptor[name]);
285
+ });
286
+ }
287
+
249
288
  await Promise.all(promises);
250
289
 
251
290
  // Update last load since we removed a plugin
@@ -284,6 +323,13 @@ export default function(context, inject, vueApp) {
284
323
  });
285
324
  });
286
325
 
326
+ // Model extensions
327
+ Object.keys(plugin.modelExtensions).forEach((name) => {
328
+ plugin.modelExtensions[name].forEach((fn) => {
329
+ this.register(EXT_IDS.MODEL_EXTENSION, name, instantiateModelExtension(this, fn));
330
+ });
331
+ });
332
+
287
333
  // Initialize the product if the store is ready
288
334
  if (productsLoaded()) {
289
335
  this.loadProducts([plugin]);
@@ -304,6 +350,13 @@ export default function(context, inject, vueApp) {
304
350
  Object.keys(plugin.validators).forEach((key) => {
305
351
  validators[key] = plugin.validators[key];
306
352
  });
353
+
354
+ // Link Interceptors
355
+ if (dynamic.linkInterceptor) {
356
+ Object.keys(dynamic.linkInterceptor).forEach((name) => {
357
+ addLinkInterceptor(dynamic.linkInterceptor[name], name);
358
+ });
359
+ }
307
360
  },
308
361
 
309
362
  /**
@@ -317,8 +370,8 @@ export default function(context, inject, vueApp) {
317
370
  dynamic[type] = {};
318
371
  }
319
372
 
320
- // Accumulate l10n resources rather than replace
321
- if (type === 'l10n') {
373
+ // Accumulate l10n resources and model extensions rather than replace
374
+ if (type === 'l10n' || type === EXT_IDS.MODEL_EXTENSION) {
322
375
  if (!dynamic[type][name]) {
323
376
  dynamic[type][name] = [];
324
377
  }
@@ -13,6 +13,37 @@ export type ClusterSaveHook = (cluster: any) => Promise<any>
13
13
  */
14
14
  export type RegisterClusterSaveHook = (hook: ClusterSaveHook, name: string, priority?: number, fnContext?: any) => void;
15
15
 
16
+ export type ClusterDetailTabs = {
17
+ /**
18
+ * RKE2 machine pool tabs
19
+ */
20
+ machines: boolean,
21
+ /**
22
+ * RKE2 provisioning logs
23
+ */
24
+ logs: boolean,
25
+ /**
26
+ * RKE2 registration commands
27
+ */
28
+ registration: boolean,
29
+ /**
30
+ * RKE2 snapshots
31
+ */
32
+ snapshots: boolean,
33
+ /**
34
+ * Kube resources related to the instance of provisioning.cattle.io.cluster
35
+ */
36
+ related: boolean,
37
+ /**
38
+ * Kube events associated with the instance of provisioning.cattle.io.cluster
39
+ */
40
+ events: boolean,
41
+ /**
42
+ * Kube conditions of the provisioning.cattle.io.cluster instance
43
+ */
44
+ conditions: boolean
45
+ };
46
+
16
47
  /**
17
48
  * Params used when constructing an instance of the cluster provisioner
18
49
  */
@@ -57,7 +88,6 @@ export interface ClusterProvisionerContext {
57
88
  * The majority of these hooks are used in shell/edit/provisioning.cattle.io.cluster/rke2.vue
58
89
  */
59
90
  export interface IClusterProvisioner {
60
-
61
91
  /**
62
92
  * Unique ID of the Cluster Provisioner
63
93
  * If this overlaps with the name of an existing provisioner (seen in the type query param while creating a cluster) this provisioner will overwrite the built-in ui
@@ -233,7 +263,7 @@ export interface IClusterProvisioner {
233
263
  registerSaveHooks?(registerBeforeHook: RegisterClusterSaveHook, registerAfterHook: RegisterClusterSaveHook, cluster: any): void;
234
264
 
235
265
  /**
236
- * Optionally override the save of the cluster resource itself.
266
+ * Optionally override the save of the cluster resource itself
237
267
  *
238
268
  * https://github.com/rancher/dashboard/blob/master/shell/mixins/create-edit-view/impl.js#L179
239
269
  *
@@ -263,3 +293,62 @@ export interface IClusterProvisioner {
263
293
  */
264
294
  provision?(cluster: any, pools: any[]): Promise<any[]>;
265
295
  }
296
+
297
+ /**
298
+ * Interface that a model extension for the provisioning cluster model should implement
299
+ */
300
+ export interface IClusterModelExtension {
301
+ /**
302
+ * Indicates if this extension should be used for the given cluster
303
+ *
304
+ * This allows the extension to determine if it should be used for a cluster based on attributes/metadata of its choosing
305
+ *
306
+ * @param cluster The cluster model (`provisioning.cattle.io.cluster`)
307
+ * @returns Whether to use this provisioner for the given cluster.
308
+ */
309
+ useFor(cluster: any): boolean;
310
+
311
+ /**
312
+ * Optionally Process the available actions for a cluster and return a (possibly modified) set of actions
313
+ *
314
+ * @param cluster The cluster model (`provisioning.cattle.io.cluster`)
315
+ * @returns List of actions for the cluster or undefined if the list is modified in-place
316
+ */
317
+ availableActions?(cluster: any, actions: any[]): any[] | undefined;
318
+
319
+ /**
320
+ * Get the display name for the machine provider for this model
321
+ *
322
+ * @param cluster The cluster model (`provisioning.cattle.io.cluster`)
323
+ * @returns Machine provider display name
324
+ */
325
+ machineProviderDisplay?(cluster: any): string;
326
+
327
+ /**
328
+ * Get the display name for the provisioner for this model
329
+ *
330
+ * @param cluster The cluster model (`provisioning.cattle.io.cluster`)
331
+ * @returns Provisioner display name
332
+ */
333
+ provisionerDisplay?(cluster: any): string;
334
+
335
+ /**
336
+ * Get the parent cluster for this cluster, or undefined if no parent cluster
337
+ *
338
+ * @param cluster The cluster model (`provisioning.cattle.io.cluster`)
339
+ * @returns ID of the parent cluster
340
+ */
341
+ parentCluster?(cluster: any): string;
342
+
343
+ /**
344
+ * Function to run after the cluster has been deleted
345
+ *
346
+ * @param cluster The cluster (`provisioning.cattle.io.cluster`)
347
+ */
348
+ postDelete?(cluster: any): void;
349
+
350
+ /**
351
+ * Existing tabs to show or hide in the cluster's detail view
352
+ */
353
+ get detailTabs(): ClusterDetailTabs;
354
+ }
package/core/types.ts CHANGED
@@ -156,6 +156,16 @@ export type LocationConfig = {
156
156
  context?: { [key: string]: string},
157
157
  };
158
158
 
159
+ /**
160
+ * Environment metadata that extensions can access
161
+ */
162
+ export type ExtensionEnvironment = {
163
+ version: string;
164
+ commit: string;
165
+ isPrime: boolean;
166
+ docsVersion: string; /** e.g. 'v2.10' */
167
+ };
168
+
159
169
  export interface ProductOptions {
160
170
  /**
161
171
  * The category this product belongs under. i.e. 'config'
@@ -478,6 +488,37 @@ export interface DSLReturnType {
478
488
  // weightType: (input, weight, forBasic)
479
489
  }
480
490
 
491
+ /**
492
+ * Context for the constructor of a model extension
493
+ */
494
+ export type ModelExtensionContext = {
495
+ /**
496
+ * Dispatch vuex actions
497
+ */
498
+ dispatch: any,
499
+ /**
500
+ * Get from vuex store
501
+ */
502
+ getters: any,
503
+ /**
504
+ * Used to make http requests
505
+ */
506
+ axios: any,
507
+ /**
508
+ * Definition of the extension
509
+ */
510
+ $plugin: any,
511
+ /**
512
+ * Function to retrieve a localised string
513
+ */
514
+ t: (key: string) => string,
515
+ };
516
+
517
+ /**
518
+ * Constructor signature for a model extension
519
+ */
520
+ export type ModelExtensionConstructor = (context: ModelExtensionContext) => Object;
521
+
481
522
  /**
482
523
  * Interface for a Dashboard plugin
483
524
  */
@@ -584,6 +625,15 @@ export interface IPlugin {
584
625
  onLogOut?: OnLogOut
585
626
  ): void;
586
627
 
628
+ /**
629
+ * Adds a model extension
630
+ * @experimental May change or be removed in the future
631
+ *
632
+ * @param type Model type
633
+ * @param clz Class for the model extension (constructor)
634
+ */
635
+ addModelExtension(type: string, clz: ModelExtensionConstructor): void;
636
+
587
637
  /**
588
638
  * Register 'something' that can be dynamically loaded - e.g. model, edit, create, list, i18n
589
639
  * @param {String} type type of thing to register, e.g. 'edit'
@@ -598,6 +648,11 @@ export interface IPlugin {
598
648
  * @param productName The name of the new product. This name is displayed in the navigation.
599
649
  */
600
650
  DSL(store: any, productName: string): DSLReturnType;
651
+
652
+ /**
653
+ * Get information about the Extension Environment
654
+ */
655
+ get environment(): ExtensionEnvironment;
601
656
  }
602
657
 
603
658
  // Internal interface
@@ -7,9 +7,18 @@ describe('view: autoscaling.horizontalpodautoscaler', () => {
7
7
  'i18n/t': (text: string) => text,
8
8
  t: (text: string) => text,
9
9
  currentStore: () => 'current_store',
10
- 'current_store/schemaFor': jest.fn(),
11
- 'current_store/all': jest.fn(),
12
- workspace: jest.fn(),
10
+ 'current_store/schemaFor': () => ({
11
+ attributes: {
12
+ columns: [
13
+ { name: 'Subobject', field: '' },
14
+ { name: 'Source', field: '' },
15
+ { name: 'First Seen', field: '' },
16
+ { name: 'Count', field: '' }]
17
+ }
18
+ }),
19
+ 'current_store/all': jest.fn(),
20
+ workspace: jest.fn(),
21
+ 'i18n/exists': jest.fn(),
13
22
  },
14
23
  };
15
24
 
@@ -34,7 +34,7 @@ export default {
34
34
  const promises = {
35
35
  catalog: this.$store.dispatch('catalog/load'),
36
36
  allOperations: this.$store.dispatch('cluster/findAll', { type: CATALOG.OPERATION }),
37
- secrets: this.value.fetchValues(true),
37
+ secret: this.value.fetchValues(true),
38
38
  };
39
39
 
40
40
  const res = await allHash(promises);
@@ -29,11 +29,11 @@ export default {
29
29
  },
30
30
 
31
31
  async fetch() {
32
- const clusterId = this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME];
32
+ const managementClusterId = this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME];
33
33
  const hash = await allHash({
34
34
  rancherCluster: this.$store.dispatch('management/find', {
35
35
  type: MANAGEMENT.CLUSTER,
36
- id: clusterId
36
+ id: managementClusterId
37
37
  }),
38
38
  repos: this.$store.dispatch('management/findAll', { type: FLEET.GIT_REPO }),
39
39
  workspaces: this.$store.dispatch('management/findAll', { type: FLEET.WORKSPACE }),
@@ -53,7 +53,7 @@ export default {
53
53
  return this.value.bundleDeployments;
54
54
  },
55
55
  clusterId() {
56
- return this.value?.metadata?.labels[FLEET_LABELS.CLUSTER_NAME];
56
+ return this.value.id;
57
57
  },
58
58
 
59
59
  repos() {
@@ -10,9 +10,7 @@ import Tab from '@shell/components/Tabbed/Tab';
10
10
  import ResourceTable from '@shell/components/ResourceTable';
11
11
  import SortableTable from '@shell/components/SortableTable';
12
12
  import Loading from '@shell/components/Loading';
13
- import {
14
- flatten, compact, filter, findKey, values
15
- } from 'lodash';
13
+ import { flatten, compact, findKey, values } from 'lodash';
16
14
 
17
15
  export default {
18
16
  emits: ['input'],
@@ -89,6 +87,9 @@ export default {
89
87
  },
90
88
  ];
91
89
 
90
+ const params = this.$route.params;
91
+ const { id: namespaceId } = params;
92
+
92
93
  return {
93
94
  allWorkloads: {
94
95
  default: () => ([]),
@@ -97,7 +98,10 @@ export default {
97
98
  resourceTypes: [],
98
99
  summaryStates: ['success', 'info', 'warning', 'error', 'unknown'],
99
100
  headers,
100
- workloadSchema: WORKLOAD_SCHEMA
101
+ workloadSchema: WORKLOAD_SCHEMA,
102
+ inStore: this.$store.getters['currentProduct'].inStore,
103
+ statesByType: getStatesByType(),
104
+ namespaceId,
101
105
  };
102
106
  },
103
107
 
@@ -106,10 +110,6 @@ export default {
106
110
  },
107
111
 
108
112
  computed: {
109
- inStore() {
110
- return this.$store.getters['currentProduct'].inStore;
111
- },
112
-
113
113
  namespacedResourceCounts() {
114
114
  const allClusterResourceCounts = this.$store.getters[`${ this.inStore }/all`](COUNT)[0].counts;
115
115
 
@@ -148,20 +148,11 @@ export default {
148
148
  }, totals);
149
149
  },
150
150
 
151
- statesByType() {
152
- return getStatesByType();
153
- },
154
-
155
151
  /**
156
152
  * Workload table data for current namespace
157
153
  */
158
154
  workloadRows() {
159
- const params = this.$route.params;
160
- const { id } = params;
161
- const rows = flatten(compact(this.allWorkloads)).filter((row) => !row.ownedByWorkload);
162
- const namespacedRows = filter(rows, ({ metadata: { namespace } }) => namespace === id);
163
-
164
- return namespacedRows;
155
+ return flatten(compact(this.allWorkloads)).filter((row) => !row.ownedByWorkload);
165
156
  }
166
157
  },
167
158
 
@@ -198,7 +189,10 @@ export default {
198
189
  return Promise.all(values(WORKLOAD_TYPES)
199
190
  // You may not have RBAC to see some of the types
200
191
  .filter((type) => Boolean(this.schemaFor(type)))
201
- .map((type) => this.$store.dispatch('cluster/findAll', { type }))
192
+ // findAll on each workload type here, argh! however...
193
+ // - results are shown in a single table containing all workloads rather than an SSP compatible way (one table per type)
194
+ // - we're restricting by namespace. not great, but a big improvement
195
+ .map((type) => this.$store.dispatch('cluster/findAll', { type, opt: { namespaced: this.namespaceId } }))
202
196
  );
203
197
  },
204
198