@rancher/shell 3.0.6 → 3.0.7

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 (78) hide show
  1. package/assets/images/pl/dark/rancher-logo.svg +131 -44
  2. package/assets/images/pl/rancher-logo.svg +120 -44
  3. package/assets/styles/base/_basic.scss +2 -2
  4. package/assets/styles/base/_color-classic.scss +51 -0
  5. package/assets/styles/base/_color.scss +3 -3
  6. package/assets/styles/base/_mixins.scss +1 -1
  7. package/assets/styles/base/_variables-classic.scss +47 -0
  8. package/assets/styles/global/_button.scss +49 -17
  9. package/assets/styles/global/_form.scss +1 -1
  10. package/assets/styles/themes/_dark.scss +4 -0
  11. package/assets/styles/themes/_light.scss +3 -69
  12. package/assets/styles/themes/_modern.scss +194 -50
  13. package/assets/styles/vendor/vue-select.scss +1 -2
  14. package/assets/translations/en-us.yaml +33 -21
  15. package/components/ClusterIconMenu.vue +1 -1
  16. package/components/ClusterProviderIcon.vue +1 -1
  17. package/components/CodeMirror.vue +1 -1
  18. package/components/IconOrSvg.vue +40 -29
  19. package/components/ResourceDetail/index.vue +1 -0
  20. package/components/SortableTable/sorting.js +3 -1
  21. package/components/Tabbed/index.vue +5 -5
  22. package/components/form/ResourceTabs/index.vue +37 -18
  23. package/components/form/SecretSelector.vue +6 -2
  24. package/components/nav/Group.vue +29 -9
  25. package/components/nav/Header.vue +6 -8
  26. package/components/nav/NamespaceFilter.vue +1 -1
  27. package/components/nav/TopLevelMenu.helper.ts +47 -20
  28. package/components/nav/TopLevelMenu.vue +44 -14
  29. package/components/nav/Type.vue +0 -5
  30. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  31. package/config/pagination-table-headers.js +10 -2
  32. package/config/product/explorer.js +4 -3
  33. package/config/table-headers.js +9 -0
  34. package/core/plugin.ts +18 -6
  35. package/core/types.ts +8 -0
  36. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  37. package/dialog/InstallExtensionDialog.vue +71 -45
  38. package/dialog/UninstallExtensionDialog.vue +2 -1
  39. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  40. package/edit/auth/oidc.vue +86 -16
  41. package/mixins/__tests__/chart.test.ts +1 -1
  42. package/mixins/chart.js +1 -1
  43. package/models/event.js +7 -0
  44. package/models/provisioning.cattle.io.cluster.js +9 -0
  45. package/package.json +1 -1
  46. package/pages/c/_cluster/explorer/EventsTable.vue +3 -6
  47. package/pages/c/_cluster/settings/performance.vue +1 -1
  48. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  49. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  50. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  51. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  52. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  53. package/plugins/dashboard-store/actions.js +3 -0
  54. package/plugins/dashboard-store/getters.js +1 -1
  55. package/plugins/dashboard-store/resource-class.js +3 -3
  56. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  57. package/plugins/steve/index.js +18 -10
  58. package/plugins/steve/mutations.js +2 -2
  59. package/plugins/steve/resourceWatcher.js +2 -2
  60. package/plugins/steve/steve-pagination-utils.ts +12 -9
  61. package/plugins/steve/subscribe.js +113 -85
  62. package/plugins/subscribe-events.ts +211 -0
  63. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  64. package/rancher-components/Banner/Banner.vue +2 -1
  65. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  66. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  67. package/store/index.js +12 -22
  68. package/types/extension-manager.ts +8 -1
  69. package/types/resources/settings.d.ts +24 -17
  70. package/types/shell/index.d.ts +352 -335
  71. package/types/store/subscribe-events.types.ts +70 -0
  72. package/types/store/subscribe.types.ts +6 -22
  73. package/utils/pagination-utils.ts +87 -28
  74. package/utils/pagination-wrapper.ts +6 -8
  75. package/utils/sort.js +5 -0
  76. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  77. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  78. package/utils/validators/formRules/index.ts +2 -2
@@ -139,12 +139,6 @@ export abstract class BaseTopLevelMenuHelper {
139
139
  this.$store = $store;
140
140
 
141
141
  this.hasProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
142
-
143
- // Reduce flicker when component is recreated on a different layout
144
- const { clustersPinned = [], clustersOthers = [] } = this.$store.getters['sideNavCache'] || {};
145
-
146
- this.clustersPinned.push(...clustersPinned);
147
- this.clustersOthers.push(...clustersOthers);
148
142
  }
149
143
 
150
144
  protected convertToCluster(mgmtCluster: MgmtCluster, provCluster: ProvCluster): TopLevelMenuCluster {
@@ -163,10 +157,6 @@ export abstract class BaseTopLevelMenuHelper {
163
157
  clusterRoute: { name: 'c-cluster-explorer', params: { cluster: mgmtCluster.id } }
164
158
  };
165
159
  }
166
-
167
- protected cacheClusters() {
168
- this.$store.dispatch('setSideNavCache', { clustersPinned: this.clustersPinned, clustersOthers: this.clustersOthers });
169
- }
170
160
  }
171
161
 
172
162
  /**
@@ -202,9 +192,9 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
202
192
  this.clustersOthersWrapper = new PaginationWrapper({
203
193
  $store,
204
194
  id: 'tlm-unpinned-clusters',
205
- onChange: () => {
195
+ onChange: async() => {
206
196
  if (this.args) {
207
- this.update(this.args);
197
+ await this.update(this.args);
208
198
  }
209
199
  },
210
200
  enabledFor: {
@@ -220,9 +210,9 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
220
210
  this.provClusterWrapper = new PaginationWrapper({
221
211
  $store,
222
212
  id: 'tlm-prov-clusters',
223
- onChange: () => {
213
+ onChange: async() => {
224
214
  if (this.args) {
225
- this.update(this.args);
215
+ await this.update(this.args);
226
216
  }
227
217
  },
228
218
  enabledFor: {
@@ -276,8 +266,6 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
276
266
 
277
267
  this.clustersPinned.push(..._clustersPinned);
278
268
  this.clustersOthers.push(..._clustersNotPinned);
279
-
280
- this.cacheClusters();
281
269
  }
282
270
 
283
271
  async destroy() {
@@ -390,7 +378,6 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
390
378
  private async updateProvCluster(notPinned: MgmtCluster[], pinned: MgmtCluster[]): Promise<ProvCluster[]> {
391
379
  return this.provClusterWrapper.request({
392
380
  pagination: {
393
-
394
381
  filters: [
395
382
  PaginationParamFilter.createMultipleFields(
396
383
  [...notPinned, ...pinned]
@@ -399,7 +386,6 @@ export class TopLevelMenuHelperPagination extends BaseTopLevelMenuHelper impleme
399
386
  }))
400
387
  )
401
388
  ],
402
-
403
389
  page: 1,
404
390
  sort: [],
405
391
  projectsOrNamespaces: []
@@ -432,8 +418,6 @@ export class TopLevelMenuHelperLegacy extends BaseTopLevelMenuHelper implements
432
418
 
433
419
  this.clustersPinned.push(..._clustersPinned);
434
420
  this.clustersOthers.push(..._clustersNotPinned);
435
-
436
- this.cacheClusters();
437
421
  }
438
422
 
439
423
  async destroy() {
@@ -581,3 +565,46 @@ export class TopLevelMenuHelperLegacy extends BaseTopLevelMenuHelper implements
581
565
  return sorted;
582
566
  }
583
567
  }
568
+
569
+ /**
570
+ * Retain state of the side nav, no matter when the TopLevelMenu component is created/deleted (on layout change)
571
+ *
572
+ * This means there's no flickering when the user changes pages and the side nav component re-renders
573
+ *
574
+ * Also it means we're not unwatching then watching the clusters
575
+ */
576
+ class TopLevelMenuHelperService {
577
+ private _helper?: TopLevelMenuHelper;
578
+ public init($store: VuexStore) {
579
+ if (this._helper) {
580
+ return;
581
+ }
582
+
583
+ const canPagination = $store.getters[`management/paginationEnabled`]({
584
+ id: MANAGEMENT.CLUSTER,
585
+ context: 'side-bar',
586
+ }) && $store.getters[`management/paginationEnabled`]({
587
+ id: CAPI.RANCHER_CLUSTER,
588
+ context: 'side-bar',
589
+ });
590
+
591
+ this._helper = canPagination ? new TopLevelMenuHelperPagination({ $store }) : new TopLevelMenuHelperLegacy({ $store });
592
+ }
593
+
594
+ public async reset() {
595
+ await this._helper?.destroy();
596
+ delete this._helper;
597
+ }
598
+
599
+ get helper(): TopLevelMenuHelper {
600
+ if (!this._helper) {
601
+ throw new Error('Unable to use the side nav cluster helper (not initialised)');
602
+ }
603
+
604
+ return this._helper;
605
+ }
606
+ }
607
+
608
+ const instance = new TopLevelMenuHelperService();
609
+
610
+ export default instance;
@@ -14,7 +14,7 @@ import { SETTING } from '@shell/config/settings';
14
14
  import { getProductFromRoute } from '@shell/utils/router';
15
15
  import { isRancherPrime } from '@shell/config/version';
16
16
  import Pinned from '@shell/components/nav/Pinned';
17
- import { TopLevelMenuHelperPagination, TopLevelMenuHelperLegacy } from '@shell/components/nav/TopLevelMenu.helper';
17
+ import sideNavService from '@shell/components/nav/TopLevelMenu.helper';
18
18
  import { debounce } from 'lodash';
19
19
  import { sameContents } from '@shell/utils/array';
20
20
 
@@ -27,6 +27,8 @@ export default {
27
27
  },
28
28
 
29
29
  data() {
30
+ sideNavService.init(this.$store);
31
+
30
32
  const { displayVersion, fullVersion } = getVersionInfo(this.$store);
31
33
  const hasProvCluster = this.$store.getters[`management/schemaFor`](CAPI.RANCHER_CLUSTER);
32
34
 
@@ -37,7 +39,7 @@ export default {
37
39
  id: CAPI.RANCHER_CLUSTER,
38
40
  context: 'side-bar',
39
41
  });
40
- const helper = canPagination ? new TopLevelMenuHelperPagination({ $store: this.$store }) : new TopLevelMenuHelperLegacy({ $store: this.$store });
42
+ const helper = sideNavService.helper;
41
43
  const provClusters = !canPagination && hasProvCluster ? this.$store.getters[`management/all`](CAPI.RANCHER_CLUSTER) : [];
42
44
  const mgmtClusters = !canPagination ? this.$store.getters[`management/all`](MANAGEMENT.CLUSTER) : [];
43
45
 
@@ -327,7 +329,6 @@ export default {
327
329
 
328
330
  beforeUnmount() {
329
331
  document.removeEventListener('keyup', this.handler);
330
- this.helper?.destroy();
331
332
  },
332
333
 
333
334
  methods: {
@@ -1073,6 +1074,14 @@ export default {
1073
1074
  width: 300px;
1074
1075
  overflow: auto;
1075
1076
 
1077
+ & .category {
1078
+ & a.router-link-active {
1079
+ &:hover {
1080
+ color: var(--on-active, var(--default));
1081
+ }
1082
+ }
1083
+ }
1084
+
1076
1085
  .option {
1077
1086
  align-items: center;
1078
1087
  cursor: pointer;
@@ -1119,6 +1128,15 @@ export default {
1119
1128
  }
1120
1129
  }
1121
1130
 
1131
+ &:not(.active-menu-link) {
1132
+ &:hover {
1133
+ .pin {
1134
+ display: block;
1135
+ color: var(--body-text-hover);
1136
+ }
1137
+ }
1138
+ }
1139
+
1122
1140
  &:hover {
1123
1141
  text-decoration: none;
1124
1142
 
@@ -1178,19 +1196,31 @@ export default {
1178
1196
  outline-offset: -4px;
1179
1197
  }
1180
1198
 
1181
- background: var(--primary-hover-bg);
1182
- color: var(--primary-hover-text);
1199
+ background: var(--active-nav, var(--primary-hover-bg));
1200
+ color: var(--on-active, var(--primary-hover-text));
1183
1201
 
1184
1202
  svg {
1185
- fill: var(--primary-hover-text);
1203
+ fill: var(--on-active, var(--primary-hover-text));
1186
1204
  }
1187
1205
 
1188
1206
  i {
1189
- color: var(--primary-hover-text);
1207
+ color: var(--on-active, var(--primary-hover-text));
1190
1208
  }
1191
1209
 
1192
1210
  div .description {
1193
- color: var(--default);
1211
+ color: var(--on-active, var(--default));
1212
+ }
1213
+
1214
+ &:hover {
1215
+ background: var(--active-hover, var(--primary-hover-bg));
1216
+
1217
+ div {
1218
+ color: var(--on-active, var(--default));
1219
+ }
1220
+
1221
+ svg {
1222
+ fill: var(--on-active, var(--primary-hover-text));
1223
+ }
1194
1224
  }
1195
1225
  }
1196
1226
 
@@ -1201,8 +1231,8 @@ export default {
1201
1231
  }
1202
1232
 
1203
1233
  &:hover {
1204
- color: var(--primary-hover-text);
1205
- background: var(--primary-hover-bg);
1234
+ color: var(--tertiary-hover-app-bar, var(--primary-hover-text));
1235
+ background: var(--nav-hover-top-level, var(--primary-hover-bg));
1206
1236
  > div {
1207
1237
  color: var(--primary-hover-text);
1208
1238
 
@@ -1211,10 +1241,10 @@ export default {
1211
1241
  }
1212
1242
  }
1213
1243
  svg {
1214
- fill: var(--primary-hover-text);
1244
+ fill: var(--tertiary-hover-app-bar, var(--primary-hover-text));
1215
1245
  }
1216
1246
  div {
1217
- color: var(--primary-hover-text);
1247
+ color: var(--tertiary-hover-app-bar, var(--primary-hover-text));
1218
1248
  }
1219
1249
  &.disabled {
1220
1250
  background: transparent;
@@ -1549,8 +1579,8 @@ export default {
1549
1579
  overflow: hidden;
1550
1580
  & IMG {
1551
1581
  object-fit: contain;
1552
- height: 21px;
1553
1582
  max-width: 200px;
1583
+ height: 36px;
1554
1584
  }
1555
1585
  }
1556
1586
 
@@ -1592,7 +1622,7 @@ export default {
1592
1622
  padding: 8px 20px;
1593
1623
 
1594
1624
  &:hover {
1595
- background-color: var(--primary-hover-bg);
1625
+ background-color: var(--active-hover, var(--primary-hover-bg));
1596
1626
  color: var(--primary-hover-text);
1597
1627
  text-decoration: none;
1598
1628
  }
@@ -249,12 +249,7 @@ export default {
249
249
  height: 33px;
250
250
 
251
251
  &:hover {
252
- background: var(--nav-hover);
253
252
  text-decoration: none;
254
-
255
- :deep() .icon {
256
- color: var(--body-text);
257
- }
258
253
  }
259
254
  }
260
255
 
@@ -3,6 +3,7 @@ import { mount, Wrapper } from '@vue/test-utils';
3
3
  import { CAPI, COUNT, MANAGEMENT } from '@shell/config/types';
4
4
  import { PINNED_CLUSTERS } from '@shell/store/prefs';
5
5
  import { nextTick } from 'vue';
6
+ import sideNavService from '@shell/components/nav/TopLevelMenu.helper';
6
7
 
7
8
  /**
8
9
  * `clusters` doubles up as both mgmt and prov clusters (don't shoot the messenger)
@@ -53,6 +54,7 @@ const waitForIt = async() => {
53
54
  describe('topLevelMenu', () => {
54
55
  beforeEach(() => {
55
56
  jest.useFakeTimers();
57
+ sideNavService.reset();
56
58
  });
57
59
 
58
60
  afterEach(() => {
@@ -3,7 +3,8 @@ import {
3
3
  STATE, NAME as NAME_COL, NAMESPACE as NAMESPACE_COL, AGE, OBJECT,
4
4
  EVENT_LAST_SEEN_TIME,
5
5
  EVENT_TYPE,
6
- SECRET_CLONE
6
+ SECRET_CLONE,
7
+ EVENT_FIRST_SEEN_TIME
7
8
  } from '@shell/config/table-headers';
8
9
 
9
10
  // This file contains table headers
@@ -56,10 +57,17 @@ export const STEVE_EVENT_OBJECT = {
56
57
  search: 'involvedObject.kind',
57
58
  };
58
59
 
60
+ export const STEVE_EVENT_FIRST_SEEN = {
61
+ ...EVENT_FIRST_SEEN_TIME,
62
+
63
+ value: 'metadata.fields.7',
64
+ sort: 'metadata.fields.7:desc',
65
+ };
66
+
59
67
  export const STEVE_EVENT_LAST_SEEN = {
60
68
  ...EVENT_LAST_SEEN_TIME,
61
69
  value: 'metadata.fields.0',
62
- sort: 'metadata.fields.0',
70
+ sort: 'metadata.fields.0:desc',
63
71
  };
64
72
 
65
73
  export const STEVE_EVENT_TYPE = {
@@ -22,11 +22,12 @@ import {
22
22
  ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, LAST_USED, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE, POD_RESTARTS,
23
23
  DURATION, MESSAGE, REASON, EVENT_TYPE, OBJECT, ROLE, ROLES, VERSION, INTERNAL_EXTERNAL_IP, KUBE_NODE_OS, CPU, RAM, SECRET_DATA,
24
24
  EVENT_LAST_SEEN_TIME,
25
+ EVENT_FIRST_SEEN_TIME,
25
26
  } from '@shell/config/table-headers';
26
27
 
27
28
  import { DSL } from '@shell/store/type-map';
28
29
  import {
29
- STEVE_AGE_COL, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_OBJECT, STEVE_EVENT_TYPE, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL
30
+ STEVE_AGE_COL, STEVE_EVENT_FIRST_SEEN, STEVE_EVENT_LAST_SEEN, STEVE_EVENT_OBJECT, STEVE_EVENT_TYPE, STEVE_LIST_GROUPS, STEVE_NAMESPACE_COL, STEVE_NAME_COL, STEVE_STATE_COL
30
31
  } from '@shell/config/pagination-table-headers';
31
32
 
32
33
  import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map';
@@ -337,7 +338,7 @@ export function init(store) {
337
338
  );
338
339
 
339
340
  headers(EVENT,
340
- [STATE, EVENT_LAST_SEEN_TIME, EVENT_TYPE, REASON, OBJECT, 'Subobject', 'Source', MESSAGE, 'First Seen', 'Count', NAME_COL, NAMESPACE_COL],
341
+ [STATE, EVENT_LAST_SEEN_TIME, EVENT_TYPE, REASON, OBJECT, 'Subobject', 'Source', MESSAGE, EVENT_FIRST_SEEN_TIME, 'Count', NAME_COL, NAMESPACE_COL],
341
342
  [
342
343
  STEVE_STATE_COL,
343
344
  STEVE_EVENT_LAST_SEEN,
@@ -347,7 +348,7 @@ export function init(store) {
347
348
  'Subobject',
348
349
  'Source',
349
350
  MESSAGE,
350
- 'First Seen',
351
+ STEVE_EVENT_FIRST_SEEN,
351
352
  'Count',
352
353
  STEVE_NAME_COL,
353
354
  STEVE_NAMESPACE_COL,
@@ -541,6 +541,15 @@ export const LAST_SEEN_TIME = {
541
541
  tooltip: 'tableHeaders.lastSeenTooltip'
542
542
  };
543
543
 
544
+ export const EVENT_FIRST_SEEN_TIME = {
545
+ name: 'firstSeen',
546
+ labelKey: 'tableHeaders.firstSeen',
547
+ tooltip: 'tableHeaders.firstSeenTooltip',
548
+
549
+ value: 'firstSeen',
550
+ sort: 'firstSeen:desc',
551
+ };
552
+
544
553
  export const EVENT_LAST_SEEN_TIME = {
545
554
  ...LAST_SEEN_TIME,
546
555
  defaultSort: true,
package/core/plugin.ts CHANGED
@@ -17,24 +17,31 @@ import {
17
17
  PluginRouteRecordRaw, RegisterStore, UnregisterStore, CoreStoreSpecifics, CoreStoreConfig,
18
18
  NavHooks, OnNavToPackage, OnNavAwayFromPackage, OnLogIn, OnLogOut,
19
19
  PaginationTableColumn,
20
- ExtensionEnvironment
20
+ ExtensionEnvironment,
21
+ ServerSidePaginationExtensionConfig
21
22
  } from './types';
22
23
  import coreStore, { coreStoreModule, coreStoreState } from '@shell/plugins/dashboard-store';
23
24
  import { defineAsyncComponent, markRaw, Component } from 'vue';
24
25
  import { getVersionData, CURRENT_RANCHER_VERSION } from '@shell/config/version';
26
+ import { ExtensionManagerTypes } from '@shell/types/extension-manager';
25
27
 
26
- // Registration IDs used for different extension points in the extensions catalog
28
+ /** Registration IDs used for different extension points in the extensions catalog */
27
29
  export const EXT_IDS = {
28
- MODELS: 'models',
29
- MODEL_EXTENSION: 'model-extension',
30
- };
30
+ MODELS: 'models',
31
+ MODEL_EXTENSION: 'model-extension',
32
+ /**
33
+ * Extension can provide resources that use server-side-pagination
34
+ */
35
+ SERVER_SIDE_PAGINATION_RESOURCES: 'server-side-pagination',
36
+ } as const;
37
+ export type EXT_IDS_VALUES = (typeof EXT_IDS)[keyof typeof EXT_IDS];
31
38
 
32
39
  export type ProductFunction = (plugin: IPlugin, store: any) => void;
33
40
 
34
41
  export class Plugin implements IPlugin {
35
42
  public id: string;
36
43
  public name: string;
37
- public types: any = {};
44
+ public types: ExtensionManagerTypes = {};
38
45
  public l10n: { [key: string]: Function[] } = {};
39
46
  public modelExtensions: { [key: string]: Function[] } = {};
40
47
  public locales: { locale: string, label: string}[] = [];
@@ -360,6 +367,11 @@ export class Plugin implements IPlugin {
360
367
  }
361
368
  }
362
369
 
370
+ public enableServerSidePagination(config: ServerSidePaginationExtensionConfig) {
371
+ console.info(`Extension "${ this.name || this.id }" is enabling server-side pagination for some resources`, config); // eslint-disable-line no-console
372
+ this.register(EXT_IDS.SERVER_SIDE_PAGINATION_RESOURCES, this.id, () => config);
373
+ }
374
+
363
375
  public async onLogOut(store: any) {
364
376
  await Promise.all(this.stores.map((s: any) => store.dispatch(`${ s.storeName }/onLogout`)));
365
377
 
package/core/types.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ProductFunction } from './plugin';
2
2
  import { RouteRecordRaw } from 'vue-router';
3
3
  import type { ExtensionManager } from '@shell/types/extension-manager';
4
+ import { PaginationSettingsStores } from '@shell/types/resources/settings';
4
5
 
5
6
  // Cluster Provisioning types
6
7
  export * from './types-provisioning';
@@ -351,6 +352,11 @@ export type TableColumn = HeaderOptions;
351
352
  */
352
353
  export type PaginationTableColumn = PaginationHeaderOptions;
353
354
 
355
+ /**
356
+ * External extension configuration for @PaginationSettingsStores
357
+ */
358
+ export type ServerSidePaginationExtensionConfig = PaginationSettingsStores;
359
+
354
360
  export interface ConfigureTypeOptions {
355
361
  /**
356
362
  * Override for the create button string on a list view
@@ -693,6 +699,8 @@ export interface IPlugin {
693
699
  ): void;
694
700
  addNavHooks(hooks: NavHooks): void;
695
701
 
702
+ enableServerSidePagination(config: ServerSidePaginationExtensionConfig): void;
703
+
696
704
  /**
697
705
  * Adds a model extension
698
706
  * @experimental May change or be removed in the future
@@ -89,6 +89,7 @@ export default {
89
89
  },
90
90
 
91
91
  async fetch() {
92
+ await this.$store.dispatch(`management/find`, { type: MANAGEMENT.CLUSTER, id: this.value.mgmtClusterId });
92
93
  await this.value.waitForProvisioner();
93
94
 
94
95
  // Support for the 'provisioner' extension
@@ -3,12 +3,12 @@ import AsyncButton from '@shell/components/AsyncButton';
3
3
  import LabeledSelect from '@shell/components/form/LabeledSelect';
4
4
  import { CATALOG, MANAGEMENT } from '@shell/config/types';
5
5
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
6
- import { UI_PLUGIN_NAMESPACE } from '@shell/config/uiplugins';
6
+ import { UI_PLUGIN_NAMESPACE, isChartVersionHigher } from '@shell/config/uiplugins';
7
7
  import Banner from '@components/Banner/Banner.vue';
8
8
  import { SETTING } from '@shell/config/settings';
9
- import { getPluginChartVersion, getPluginChartVersionLabel } from '@shell/utils/uiplugins';
9
+ import { getPluginChartVersionLabel } from '@shell/utils/uiplugins';
10
10
 
11
- // Note: This dialog handles installation and update of a plugin
11
+ // Note: This dialog handles installation, upgrade and downgrade of a plugin
12
12
 
13
13
  export default {
14
14
  emits: ['close'],
@@ -29,7 +29,14 @@ export default {
29
29
  required: true
30
30
  },
31
31
  /**
32
- * The action to perform (install, update, rollback)
32
+ * The pre-selected version in the dropdown
33
+ */
34
+ initialVersion: {
35
+ type: String,
36
+ default: null
37
+ },
38
+ /**
39
+ * The action to perform (install, upgrade, downgrade)
33
40
  */
34
41
  action: {
35
42
  type: String,
@@ -63,33 +70,32 @@ export default {
63
70
  },
64
71
 
65
72
  async fetch() {
66
- const chartVersion = getPluginChartVersion(this.plugin);
67
-
68
- // Default to latest version on install (this is default on the plugin)
69
- this.version = chartVersion;
70
-
71
- if (this.action === 'update') {
72
- this.currentVersion = chartVersion;
73
-
74
- // Update to latest version, so take the first version
75
- if (this.plugin?.installableVersions?.length > 0) {
76
- this.version = this.plugin?.installableVersions?.[0]?.version;
77
- }
78
- } else if (this.action === 'rollback') {
79
- // Find the newest version once we remove the current version
80
- const versionNames = this.plugin.installableVersions.filter((v) => v.version !== chartVersion);
73
+ // Determine the currently installed version, if any
74
+ if (this.plugin.installed) {
75
+ this.currentVersion = this.plugin.installedVersion;
76
+ }
81
77
 
82
- this.currentVersion = chartVersion;
78
+ // Determine the initial version to select in the dropdown
79
+ if (this.initialVersion) {
80
+ this.version = this.initialVersion;
81
+ } else if (this.action === 'upgrade') {
82
+ // Upgrade to the latest version, so take the first version
83
+ this.version = this.plugin?.installableVersions?.[0]?.version;
84
+ } else if (this.action === 'downgrade') {
85
+ const versions = this.plugin.installableVersions;
86
+ const currentIndex = versions.findIndex((v) => v.version === this.currentVersion);
83
87
 
84
- if (versionNames.length > 0) {
85
- this.version = versionNames[0].version;
88
+ if (currentIndex !== -1 && currentIndex < versions.length - 1) {
89
+ // Select the version just below the current version
90
+ this.version = versions[currentIndex + 1].version;
86
91
  }
92
+ } else {
93
+ // Default to the latest installable version for new installs
94
+ this.version = this.plugin?.installableVersions?.[0]?.version;
87
95
  }
88
96
 
89
- // Make sure we have the version available
90
- const versionChart = this.plugin?.installableVersions?.find((v) => v.version === this.version);
91
-
92
- if (!versionChart) {
97
+ // Fallback if no version could be determined
98
+ if (!this.version) {
93
99
  this.version = this.plugin?.installableVersions?.[0]?.version;
94
100
  }
95
101
 
@@ -119,37 +125,39 @@ export default {
119
125
  },
120
126
 
121
127
  versionOptions() {
122
- if (!this.plugin) {
128
+ if (!this.plugin?.installableVersions) {
123
129
  return [];
124
130
  }
125
131
 
126
- // Don't allow update/rollback to current version
127
- const versions = this.plugin?.installableVersions?.filter((v) => {
128
- if (this.currentVersion) {
129
- return v.version !== this.currentVersion;
130
- }
131
-
132
- return true;
133
- });
132
+ // Don't allow upgrade/downgrade to current version by disabling the option
133
+ return this.plugin.installableVersions.map((v) => {
134
+ const isCurrent = v.version === this.currentVersion;
134
135
 
135
- return versions.map((version) => {
136
136
  return {
137
- label: getPluginChartVersionLabel(version),
138
- value: version.version,
137
+ label: getPluginChartVersionLabel(v) + (isCurrent ? ` (${ this.t('plugins.labels.current') })` : ''),
138
+ value: v.version,
139
+ disabled: isCurrent,
139
140
  };
140
141
  });
141
142
  },
142
143
 
143
144
  buttonMode() {
144
- if (this.action === 'rollback') {
145
- return 'rollback';
145
+ if (this.action === 'install') {
146
+ return 'install';
146
147
  }
147
148
 
148
- if (this.action === 'update') {
149
- return 'update';
149
+ if (this.currentVersion && this.version) {
150
+ if (isChartVersionHigher(this.version, this.currentVersion)) {
151
+ return 'upgrade';
152
+ }
153
+
154
+ if (isChartVersionHigher(this.currentVersion, this.version)) {
155
+ return 'downgrade';
156
+ }
150
157
  }
151
158
 
152
- return 'install';
159
+ // Fallback for safety, though should not be reached if version is selected
160
+ return this.action;
153
161
  },
154
162
 
155
163
  chartVersionLoadsWithoutAuth() {
@@ -158,6 +166,23 @@ export default {
158
166
 
159
167
  returnFocusSelector() {
160
168
  return `[data-testid="extension-card-${ this.action }-btn-${ this.plugin?.name }"]`;
169
+ },
170
+
171
+ buttonIcon() {
172
+ if (this.busy) {
173
+ return '';
174
+ }
175
+
176
+ switch (this.buttonMode) {
177
+ case 'install':
178
+ return 'icon-plus';
179
+ case 'upgrade':
180
+ return 'icon-upgrade-alt';
181
+ case 'downgrade':
182
+ return 'icon-downgrade-alt';
183
+ default:
184
+ return '';
185
+ }
161
186
  }
162
187
  },
163
188
 
@@ -295,12 +320,12 @@ export default {
295
320
  <template>
296
321
  <div class="plugin-install-dialog">
297
322
  <h4 class="mt-10">
298
- {{ t(`plugins.${ action }.title`, { name: plugin?.label }) }}
323
+ {{ t(`plugins.${ buttonMode }.title`, { name: `"${plugin?.label}"` }, true) }}
299
324
  </h4>
300
325
  <div class="custom mt-10">
301
326
  <div class="dialog-panel">
302
327
  <p>
303
- {{ t(`plugins.${ action }.prompt`) }}
328
+ {{ t(`plugins.${ buttonMode }.prompt`) }}
304
329
  </p>
305
330
  <Banner
306
331
  v-if="chartVersionLoadsWithoutAuth"
@@ -335,6 +360,7 @@ export default {
335
360
  </button>
336
361
  <AsyncButton
337
362
  :mode="buttonMode"
363
+ :icon="buttonIcon"
338
364
  data-testid="install-ext-modal-install-btn"
339
365
  @click="install"
340
366
  />
@@ -103,7 +103,7 @@ export default {
103
103
  <template>
104
104
  <div class="plugin-install-dialog">
105
105
  <h4 class="mt-10">
106
- {{ t('plugins.uninstall.title', { name: plugin?.label }) }}
106
+ {{ t('plugins.uninstall.title', { name: `"${plugin?.label}"` }, true) }}
107
107
  </h4>
108
108
  <div class="mt-10 dialog-panel">
109
109
  <div class="dialog-info">
@@ -122,6 +122,7 @@ export default {
122
122
  </button>
123
123
  <AsyncButton
124
124
  mode="uninstall"
125
+ :icon="busy ? '' : 'icon-delete'"
125
126
  data-testid="uninstall-ext-modal-uninstall-btn"
126
127
  @click="uninstall()"
127
128
  />