@rancher/shell 3.0.6 → 3.0.8-rc.1

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/assets/images/pl/dark/rancher-logo.svg +131 -44
  2. package/assets/images/pl/rancher-logo.svg +120 -44
  3. package/assets/images/vendor/githubapp.svg +13 -0
  4. package/assets/styles/base/_basic.scss +2 -2
  5. package/assets/styles/base/_color-classic.scss +51 -0
  6. package/assets/styles/base/_color.scss +3 -3
  7. package/assets/styles/base/_mixins.scss +1 -1
  8. package/assets/styles/base/_typography.scss +1 -1
  9. package/assets/styles/base/_variables-classic.scss +47 -0
  10. package/assets/styles/global/_button.scss +49 -17
  11. package/assets/styles/global/_form.scss +1 -1
  12. package/assets/styles/themes/_dark.scss +4 -0
  13. package/assets/styles/themes/_light.scss +3 -69
  14. package/assets/styles/themes/_modern.scss +194 -50
  15. package/assets/styles/vendor/vue-select.scss +1 -2
  16. package/assets/translations/en-us.yaml +124 -32
  17. package/assets/translations/zh-hans.yaml +0 -4
  18. package/components/ClusterIconMenu.vue +1 -1
  19. package/components/ClusterProviderIcon.vue +1 -1
  20. package/components/CodeMirror.vue +1 -1
  21. package/components/IconOrSvg.vue +40 -29
  22. package/components/Inactivity.vue +222 -106
  23. package/components/InstallHelmCharts.vue +2 -2
  24. package/components/ResourceDetail/index.vue +2 -1
  25. package/components/SortableTable/index.vue +17 -2
  26. package/components/SortableTable/sorting.js +3 -1
  27. package/components/Tabbed/index.vue +5 -5
  28. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  29. package/components/fleet/FleetSecretSelector.vue +127 -0
  30. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  31. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  32. package/components/form/FileImageSelector.vue +13 -4
  33. package/components/form/FileSelector.vue +11 -2
  34. package/components/form/ResourceLabeledSelect.vue +1 -0
  35. package/components/form/ResourceTabs/index.vue +37 -18
  36. package/components/form/SecretSelector.vue +6 -2
  37. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  38. package/components/nav/Group.vue +29 -9
  39. package/components/nav/Header.vue +7 -8
  40. package/components/nav/NamespaceFilter.vue +1 -1
  41. package/components/nav/TopLevelMenu.helper.ts +47 -20
  42. package/components/nav/TopLevelMenu.vue +44 -14
  43. package/components/nav/Type.vue +0 -5
  44. package/components/nav/__tests__/TopLevelMenu.test.ts +2 -0
  45. package/config/pagination-table-headers.js +10 -2
  46. package/config/product/auth.js +1 -0
  47. package/config/product/explorer.js +4 -3
  48. package/config/query-params.js +1 -0
  49. package/config/settings.ts +8 -1
  50. package/config/table-headers.js +9 -0
  51. package/config/types.js +2 -0
  52. package/core/plugin.ts +18 -6
  53. package/core/types.ts +8 -0
  54. package/detail/provisioning.cattle.io.cluster.vue +1 -0
  55. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  56. package/dialog/InstallExtensionDialog.vue +71 -45
  57. package/dialog/UninstallExtensionDialog.vue +2 -1
  58. package/dialog/__tests__/InstallExtensionDialog.test.ts +111 -0
  59. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  60. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  61. package/edit/auth/github-app-steps.vue +97 -0
  62. package/edit/auth/github-steps.vue +75 -0
  63. package/edit/auth/github.vue +94 -65
  64. package/edit/auth/oidc.vue +86 -16
  65. package/edit/fleet.cattle.io.helmop.vue +51 -2
  66. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  67. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  68. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  69. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  70. package/list/projectsecret.vue +1 -1
  71. package/machine-config/azure.vue +1 -1
  72. package/mixins/__tests__/chart.test.ts +1 -1
  73. package/mixins/chart.js +2 -2
  74. package/models/__tests__/chart.test.ts +17 -9
  75. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  76. package/models/catalog.cattle.io.app.js +1 -1
  77. package/models/chart.js +3 -1
  78. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  79. package/models/event.js +7 -0
  80. package/models/management.cattle.io.authconfig.js +1 -0
  81. package/models/provisioning.cattle.io.cluster.js +9 -0
  82. package/package.json +2 -2
  83. package/pages/auth/login.vue +5 -2
  84. package/pages/auth/verify.vue +1 -1
  85. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  86. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  87. package/pages/c/_cluster/explorer/EventsTable.vue +92 -9
  88. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  89. package/pages/c/_cluster/settings/performance.vue +13 -26
  90. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +159 -62
  91. package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +102 -0
  92. package/pages/c/_cluster/uiplugins/__tests__/{index.spec.ts → index.test.ts} +121 -55
  93. package/pages/c/_cluster/uiplugins/index.vue +110 -94
  94. package/pages/home.vue +313 -12
  95. package/plugins/__tests__/subscribe.events.test.ts +194 -0
  96. package/plugins/axios.js +2 -1
  97. package/plugins/dashboard-store/actions.js +4 -1
  98. package/plugins/dashboard-store/getters.js +1 -1
  99. package/plugins/dashboard-store/resource-class.js +20 -5
  100. package/plugins/steve/__tests__/subscribe.spec.ts +27 -24
  101. package/plugins/steve/index.js +18 -10
  102. package/plugins/steve/mutations.js +2 -2
  103. package/plugins/steve/resourceWatcher.js +2 -2
  104. package/plugins/steve/steve-pagination-utils.ts +12 -9
  105. package/plugins/steve/subscribe.js +113 -85
  106. package/plugins/subscribe-events.ts +211 -0
  107. package/rancher-components/BadgeState/BadgeState.vue +8 -6
  108. package/rancher-components/Banner/Banner.vue +2 -1
  109. package/rancher-components/Form/Checkbox/Checkbox.vue +3 -3
  110. package/rancher-components/Form/Radio/RadioButton.vue +3 -3
  111. package/scripts/extension/publish +1 -1
  112. package/store/auth.js +8 -3
  113. package/store/aws.js +8 -6
  114. package/store/features.js +1 -0
  115. package/store/index.js +21 -25
  116. package/store/prefs.js +6 -0
  117. package/types/extension-manager.ts +8 -1
  118. package/types/kube/kube-api.ts +2 -1
  119. package/types/rancher/index.d.ts +1 -0
  120. package/types/resources/settings.d.ts +52 -23
  121. package/types/shell/index.d.ts +412 -336
  122. package/types/store/subscribe-events.types.ts +70 -0
  123. package/types/store/subscribe.types.ts +6 -22
  124. package/utils/__tests__/cluster.test.ts +379 -1
  125. package/utils/cluster.js +157 -3
  126. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  127. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  128. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  129. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  130. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  131. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  132. package/utils/dynamic-content/config.ts +55 -0
  133. package/utils/dynamic-content/index.ts +273 -0
  134. package/utils/dynamic-content/info.ts +219 -0
  135. package/utils/dynamic-content/new-release.ts +126 -0
  136. package/utils/dynamic-content/support-notice.ts +169 -0
  137. package/utils/dynamic-content/types.d.ts +101 -0
  138. package/utils/dynamic-content/util.ts +122 -0
  139. package/utils/inactivity.ts +104 -0
  140. package/utils/pagination-utils.ts +105 -31
  141. package/utils/pagination-wrapper.ts +6 -8
  142. package/utils/release-notes.ts +1 -1
  143. package/utils/sort.js +5 -0
  144. package/utils/unit-tests/pagination-utils.spec.ts +283 -0
  145. package/utils/validators/formRules/__tests__/index.test.ts +7 -0
  146. package/utils/validators/formRules/index.ts +2 -2
package/pages/home.vue CHANGED
@@ -8,9 +8,9 @@ import { BadgeState } from '@components/BadgeState';
8
8
  import CommunityLinks from '@shell/components/CommunityLinks.vue';
9
9
  import SingleClusterInfo from '@shell/components/SingleClusterInfo.vue';
10
10
  import { mapGetters, mapState } from 'vuex';
11
- import { MANAGEMENT, CAPI } from '@shell/config/types';
11
+ import { MANAGEMENT, CAPI, COUNT } from '@shell/config/types';
12
12
  import { NAME as MANAGER } from '@shell/config/product/manager';
13
- import { STATE } from '@shell/config/table-headers';
13
+ import { AGE, STATE } from '@shell/config/table-headers';
14
14
  import { MODE, _IMPORT } from '@shell/config/query-params';
15
15
  import { createMemoryFormat, formatSi, parseSi, createMemoryValues } from '@shell/utils/units';
16
16
  import { markSeenReleaseNotes } from '@shell/utils/version';
@@ -28,8 +28,12 @@ import { PaginationParamFilter, FilterArgs, PaginationFilterField, PaginationArg
28
28
  import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
29
29
  import { sameContents } from '@shell/utils/array';
30
30
  import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
31
- import { CURRENT_RANCHER_VERSION } from '@shell/config/version';
31
+ import { CURRENT_RANCHER_VERSION, getVersionData } from '@shell/config/version';
32
32
  import { CAPI as CAPI_LAB_AND_ANO } from '@shell/config/labels-annotations';
33
+ import paginationUtils from '@shell/utils/pagination-utils';
34
+ import ResourceTable from '@shell/components/ResourceTable.vue';
35
+ import Preset from '@shell/mixins/preset';
36
+ import { PaginationFeatureHomePageClusterConfig } from '@shell/types/resources/settings';
33
37
 
34
38
  export default defineComponent({
35
39
  name: 'Home',
@@ -42,9 +46,10 @@ export default defineComponent({
42
46
  CommunityLinks,
43
47
  SingleClusterInfo,
44
48
  TabTitle,
49
+ ResourceTable,
45
50
  },
46
51
 
47
- mixins: [PageHeaderActions],
52
+ mixins: [PageHeaderActions, Preset],
48
53
 
49
54
  data() {
50
55
  const options = this.$store.getters[`type-map/optionsFor`](CAPI.RANCHER_CLUSTER)?.custom || {};
@@ -200,15 +205,41 @@ export default defineComponent({
200
205
 
201
206
  clusterCount: 0,
202
207
 
203
- CURRENT_RANCHER_VERSION
208
+ CURRENT_RANCHER_VERSION,
209
+
210
+ /**
211
+ * User has decided to disable the alt list
212
+ */
213
+ altClusterListDisabled: false,
214
+ /**
215
+ * There are too many clusters to show in the home page list.
216
+ *
217
+ * If not disabled, show alt table
218
+ */
219
+ tooManyClusters: undefined as boolean | undefined,
220
+ altClusterListRows: undefined as any[] | undefined,
221
+ altClusterListFeature: paginationUtils.getFeature<PaginationFeatureHomePageClusterConfig>({ rootGetters: this.$store.getters }, 'homePageCluster'),
222
+
223
+ presetVersion: getVersionData()?.Version,
204
224
  };
205
225
  },
206
226
 
227
+ mounted() {
228
+ this.preset('altClusterListDisabled', 'boolean');
229
+ },
230
+
207
231
  computed: {
208
232
  ...mapState(['managementReady']),
209
233
  ...mapGetters(['currentCluster', 'defaultClusterId']),
210
234
  mcm: mapFeature(MULTI_CLUSTER),
211
235
 
236
+ vaiOnSettingsHeaders() {
237
+ return [
238
+ ...this.headers, // include age as we're sorting by it
239
+ AGE
240
+ ];
241
+ },
242
+
212
243
  canCreateCluster() {
213
244
  return !!this.provClusterSchema?.collectionMethods.find((x: string) => x.toLowerCase() === 'post');
214
245
  },
@@ -216,6 +247,21 @@ export default defineComponent({
216
247
  afterLoginRoute: mapPref(AFTER_LOGIN_ROUTE),
217
248
  homePageCards: mapPref(HIDE_HOME_PAGE_CARDS),
218
249
 
250
+ /**
251
+ * Show the alt table
252
+ */
253
+ altClusterList() {
254
+ return this.tooManyClusters && !this.altClusterListDisabled;
255
+ }
256
+
257
+ },
258
+
259
+ watch: {
260
+ async altClusterList(neu) {
261
+ if (neu) {
262
+ await this.initAltClusters();
263
+ }
264
+ },
219
265
  },
220
266
 
221
267
  async created() {
@@ -227,6 +273,12 @@ export default defineComponent({
227
273
  // If we do not, then if they set the landing page, that won't work unless the release notes are marked read
228
274
  // otherwise we always take them to the home page to see the release notes
229
275
  markSeenReleaseNotes(this.$store);
276
+
277
+ this.tooManyClusters = this.isTooManyClusters();
278
+
279
+ if (this.altClusterList) {
280
+ await this.initAltClusters();
281
+ }
230
282
  },
231
283
 
232
284
  // Forget the types when we leave the page
@@ -458,7 +510,83 @@ export default defineComponent({
458
510
  }
459
511
 
460
512
  return pagination;
461
- }
513
+ },
514
+
515
+ async toggleAltClusterListDisabled(disabled: boolean) {
516
+ // Clear the cache so the table doesn't show the previous mode's results
517
+ await this.$store.dispatch('management/forgetType', CAPI.RANCHER_CLUSTER);
518
+
519
+ this.altClusterListDisabled = disabled;
520
+ },
521
+
522
+ /**
523
+ * Determine if we should use an alternative cluster list which contains most recently created clusters
524
+ *
525
+ * Checks
526
+ * - can view clusters
527
+ * - if vai is on
528
+ * - if alt list feature is on
529
+ * - if cluster count exceeds threshold
530
+ */
531
+ isTooManyClusters(): boolean {
532
+ if (!this.provClusterSchema || !this.canViewMgmtClusters) {
533
+ return false;
534
+ }
535
+
536
+ const featureConfig = this.altClusterListFeature;
537
+
538
+ if (!featureConfig || !featureConfig.enabled) { // vai is off, or feature is explicitly disabled
539
+ return false;
540
+ }
541
+
542
+ const threshold = featureConfig.configuration?.threshold;
543
+
544
+ if (threshold === undefined) { // invalid config
545
+ return false;
546
+ }
547
+
548
+ const counts = this.$store.getters[`management/all`](COUNT)?.[0]?.counts || {};
549
+
550
+ this.clusterCount = counts[CAPI.RANCHER_CLUSTER]?.summary.count;
551
+
552
+ return this.clusterCount > threshold;
553
+ },
554
+
555
+ /**
556
+ * Fetch clusters used to populate alt table
557
+ */
558
+ async initAltClusters() {
559
+ const featureConfig = this.altClusterListFeature;
560
+ const results = featureConfig?.configuration?.results || 50;
561
+
562
+ // Fetch a limited number of provisioning clusters
563
+ const opt1: ActionFindPageArgs = {
564
+ pagination: {
565
+ projectsOrNamespaces: [],
566
+ filters: paginationFilterClusters(this.$store, false),
567
+ page: 1,
568
+ pageSize: results, // We're fetching the total results... then paging locally
569
+ sort: [{ field: 'metadata.creationTimestamp', asc: false }]
570
+ },
571
+ watch: false,
572
+ };
573
+ const provClusters = await this.$store.dispatch('management/findPage', { type: CAPI.RANCHER_CLUSTER, opt: opt1 });
574
+
575
+ // Also fetch the management clusters associated with the provisioning clusters
576
+ const opt2: ActionFindPageArgs = {
577
+ pagination: new FilterArgs({
578
+ filters: PaginationParamFilter.createMultipleFields(provClusters.map((r: any) => new PaginationFilterField({
579
+ field: 'id',
580
+ value: r.mgmtClusterId
581
+ }))),
582
+ }),
583
+ watch: false,
584
+ };
585
+
586
+ await this.$store.dispatch(`management/findPage`, { type: MANAGEMENT.CLUSTER, opt: opt2 });
587
+
588
+ this.altClusterListRows = provClusters;
589
+ },
462
590
  }
463
591
  });
464
592
 
@@ -485,9 +613,164 @@ export default defineComponent({
485
613
  <IndentedPanel class="mt-20 mb-20">
486
614
  <div class="row home-panels">
487
615
  <div class="col main-panel">
488
- <div class="row panel">
616
+ <div
617
+ v-if="altClusterList !== undefined"
618
+ class="row panel"
619
+ >
620
+ <div
621
+ v-if="mcm && altClusterList"
622
+ class="col span-12"
623
+ >
624
+ <ResourceTable
625
+ :schema="provClusterSchema"
626
+ :table-actions="false"
627
+ :row-actions="false"
628
+ key-field="id"
629
+
630
+ :headers="vaiOnSettingsHeaders"
631
+ defaultSortBy="age"
632
+
633
+ :loading="!altClusterListRows"
634
+
635
+ :rows="altClusterListRows || []"
636
+ :rowsPerPage="altClusterListFeature?.configuration.pagesPerRow || 10"
637
+
638
+ :namespaced="false"
639
+ :groupable="false"
640
+ >
641
+ <template #header-left>
642
+ <div class="row table-heading">
643
+ <h1 class="mb-0">
644
+ {{ t('landing.clusters.title') }}
645
+ </h1>
646
+ </div>
647
+ </template>
648
+ <template #sub-header-row>
649
+ <h2 class="too-many-clusters">
650
+ {{ t('landing.clusters.tooMany.showingSome', { rows: altClusterListRows?.length || '...', total: clusterCount}) }}
651
+ <a @click="toggleAltClusterListDisabled(true)">{{ t('landing.clusters.tooMany.showAll') }}</a>
652
+ </h2>
653
+ </template>
654
+ <!--
655
+ Below is a big copy & paste from PaginatedResourceTable, however should be temporary (altClusterList removed in 2.14 once full SSP support for clusters if available)
656
+ -->
657
+ <template
658
+ v-if="canCreateCluster || !!provClusterSchema"
659
+ #header-middle
660
+ >
661
+ <div class="table-heading">
662
+ <router-link
663
+ v-if="!!provClusterSchema"
664
+ :to="manageLocation"
665
+ class="btn btn-sm role-secondary"
666
+ data-testid="cluster-management-manage-button"
667
+ role="button"
668
+ :aria-label="t('cluster.manageAction')"
669
+ @keyup.space="$router.push(manageLocation)"
670
+ >
671
+ {{ t('cluster.manageAction') }}
672
+ </router-link>
673
+ <router-link
674
+ v-if="canCreateCluster"
675
+ :to="importLocation"
676
+ class="btn btn-sm role-primary"
677
+ data-testid="cluster-create-import-button"
678
+ role="button"
679
+ :aria-label="t('cluster.importAction')"
680
+ @keyup.space="$router.push(importLocation)"
681
+ >
682
+ {{ t('cluster.importAction') }}
683
+ </router-link>
684
+ <router-link
685
+ v-if="canCreateCluster"
686
+ :to="createLocation"
687
+ class="btn btn-sm role-primary"
688
+ data-testid="cluster-create-button"
689
+ role="button"
690
+ :aria-label="t('generic.create')"
691
+ @keyup.space="$router.push(createLocation)"
692
+ >
693
+ {{ t('generic.create') }}
694
+ </router-link>
695
+ </div>
696
+ </template>
697
+ <template #col:name="{row}">
698
+ <td class="col-name">
699
+ <div class="list-cluster-name">
700
+ <p
701
+ v-if="row.mgmt"
702
+ class="cluster-name"
703
+ >
704
+ <router-link
705
+ v-if="row.mgmt.isReady && !row.hasError"
706
+ :to="{ name: 'c-cluster-explorer', params: { cluster: row.mgmt.id }}"
707
+ role="link"
708
+ :aria-label="row.nameDisplay"
709
+ >
710
+ {{ row.nameDisplay }}
711
+ </router-link>
712
+ <span v-else>{{ row.nameDisplay }}</span>
713
+ <i
714
+ v-if="row.unavailableMachines"
715
+ v-clean-tooltip="row.unavailableMachines"
716
+ class="conditions-alert-icon icon-alert icon"
717
+ />
718
+ <i
719
+ v-if="row.isRke1"
720
+ v-clean-tooltip="t('cluster.rke1Unsupported')"
721
+ class="rke1-unsupported-icon icon-warning icon"
722
+ />
723
+ </p>
724
+ <p
725
+ v-if="row.description"
726
+ class="cluster-description"
727
+ >
728
+ {{ row.description }}
729
+ </p>
730
+ </div>
731
+ </td>
732
+ </template>
733
+ <template #col:kubernetesVersion="{row}">
734
+ <td class="col-name">
735
+ <span>
736
+ {{ row.kubernetesVersion }}
737
+ </span>
738
+ <div
739
+ v-clean-tooltip="{content: row.architecture.tooltip, placement: 'left'}"
740
+ class="text-muted"
741
+ >
742
+ {{ row.architecture.label }}
743
+ </div>
744
+ </td>
745
+ </template>
746
+ <template #col:cpu="{row}">
747
+ <td v-if="row.mgmt && cpuAllocatable(row.mgmt)">
748
+ {{ `${cpuAllocatable(row.mgmt)} ${t('landing.clusters.cores', {count:cpuAllocatable(row.mgmt) })}` }}
749
+ </td>
750
+ <td v-else>
751
+ &mdash;
752
+ </td>
753
+ </template>
754
+ <template #col:memory="{row}">
755
+ <td v-if="row.mgmt && memoryAllocatable(row.mgmt) && !memoryAllocatable(row.mgmt).match(/^0 [a-zA-z]/)">
756
+ {{ memoryAllocatable(row.mgmt) }}
757
+ </td>
758
+ <td v-else>
759
+ &mdash;
760
+ </td>
761
+ </template>
762
+ <!-- <template #cell:explorer="{row}">
763
+ <router-link v-if="row && row.isReady" class="btn btn-sm role-primary" :to="{name: 'c-cluster', params: {cluster: row.id}}">
764
+ {{ t('landing.clusters.explore') }}
765
+ </router-link>
766
+ <button v-else :disabled="true" class="btn btn-sm role-primary">
767
+ {{ t('landing.clusters.explore') }}
768
+ </button>
769
+ </template> -->
770
+ </ResourceTable>
771
+ </div>
489
772
  <div
490
- v-if="mcm"
773
+ v-else-if="mcm"
491
774
  class="col span-12"
492
775
  >
493
776
  <PaginatedResourceTable
@@ -511,16 +794,25 @@ export default defineComponent({
511
794
  >
512
795
  <template #header-left>
513
796
  <div class="row table-heading">
514
- <h2 class="mb-0">
797
+ <h1 class="mb-0">
515
798
  {{ t('landing.clusters.title') }}
516
- </h2>
799
+ </h1>
517
800
  <BadgeState
518
- v-if="clusterCount"
801
+ v-if="clusterCount && !tooManyClusters"
519
802
  :label="clusterCount.toString()"
520
- color="role-tertiary ml-20 mr-20"
803
+ color="bg-info ml-20 mr-20"
521
804
  />
522
805
  </div>
523
806
  </template>
807
+ <template
808
+ v-if="tooManyClusters"
809
+ #sub-header-row
810
+ >
811
+ <h2 class="too-many-clusters">
812
+ {{ t('landing.clusters.tooMany.showingAll', { rows: altClusterListRows?.length || '...', total: clusterCount}) }}
813
+ <a @click="toggleAltClusterListDisabled(false)">{{ t('landing.clusters.tooMany.showSome') }}</a>
814
+ </h2>
815
+ </template>
524
816
  <template
525
817
  v-if="canCreateCluster || !!provClusterSchema"
526
818
  #header-middle
@@ -663,6 +955,14 @@ export default defineComponent({
663
955
  }
664
956
  .main-panel {
665
957
  flex: auto;
958
+
959
+ .too-many-clusters {
960
+ margin-bottom: 5px;
961
+
962
+ a {
963
+ cursor: pointer;
964
+ }
965
+ }
666
966
  }
667
967
 
668
968
  .side-panel {
@@ -744,6 +1044,7 @@ export default defineComponent({
744
1044
  .search {
745
1045
  align-items: center;
746
1046
  display: flex;
1047
+ height: 39px;
747
1048
 
748
1049
  > INPUT {
749
1050
  background-color: transparent;
@@ -0,0 +1,194 @@
1
+ import { SteveWatchEventListenerManager } from '@shell/plugins/subscribe-events';
2
+ import { STEVE_WATCH_EVENT_TYPES } from '@shell/types/store/subscribe.types';
3
+
4
+ // Mock function to create a consistent key for testing
5
+ const mockKeyForSubscribe = jest.fn(({
6
+ params: {
7
+ type, name, id, selector, mode
8
+ }
9
+ }) => {
10
+ return `${ type }-${ name }-${ id }-${ selector }-${ mode }`;
11
+ });
12
+
13
+ // Mock parameters and callbacks
14
+ const mockParams1 = {
15
+ type: 'pods', name: 'my-pod', id: 'abc-123', selector: 'app=test'
16
+ };
17
+ const mockCallback1 = jest.fn();
18
+ const mockCallback2 = jest.fn();
19
+
20
+ // The class under test
21
+ let manager: SteveWatchEventListenerManager;
22
+
23
+ describe('steveWatchEventListenerManager', () => {
24
+ beforeEach(() => {
25
+ // Reset the manager and mocks before each test
26
+ manager = new SteveWatchEventListenerManager();
27
+ jest.clearAllMocks();
28
+ // Replace the internal keyForSubscribe with our mock
29
+ (manager as any).keyForSubscribe = mockKeyForSubscribe;
30
+ });
31
+
32
+ describe('initialization and Properties', () => {
33
+ it('should be created successfully', () => {
34
+ expect(manager).toBeInstanceOf(SteveWatchEventListenerManager);
35
+ });
36
+
37
+ it('should have a supportedEventTypes array with STEVE_WATCH_EVENT_TYPES.CHANGES', () => {
38
+ expect(manager.supportedEventTypes).toStrictEqual([STEVE_WATCH_EVENT_TYPES.CHANGES]);
39
+ });
40
+
41
+ it('should correctly identify a supported event type', () => {
42
+ const isSupported = manager.isSupportedEventType(STEVE_WATCH_EVENT_TYPES.CHANGES);
43
+
44
+ expect(isSupported).toBe(true);
45
+ });
46
+
47
+ it('should correctly identify an unsupported event type', () => {
48
+ const isSupported = manager.isSupportedEventType('some.other.event' as STEVE_WATCH_EVENT_TYPES);
49
+
50
+ expect(isSupported).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe('watch Management', () => {
55
+ it('should return undefined when getting a non-existent watch', () => {
56
+ const watch = manager.getWatch({ params: mockParams1 });
57
+
58
+ expect(watch).toBeUndefined();
59
+ });
60
+
61
+ it('should create a watch when setStandardWatch is called with standardWatch true and no watch exists', () => {
62
+ manager.setStandardWatch({ standardWatch: true, args: { params: mockParams1 } });
63
+ const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
64
+
65
+ expect(watch).toBeDefined();
66
+ expect(watch.hasStandardWatch).toBe(true);
67
+ expect(watch.listeners).toStrictEqual([]);
68
+ });
69
+
70
+ it('should not create a watch when setStandardWatch is called with standardWatch false and no watch exists', () => {
71
+ manager.setStandardWatch({ standardWatch: false, args: { params: mockParams1 } });
72
+ const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
73
+
74
+ expect(watch).toBeUndefined();
75
+ });
76
+
77
+ it('should delete a watch when hasStandardWatch becomes false and there are no listeners', () => {
78
+ manager.setStandardWatch({ standardWatch: true, args: { params: mockParams1 } });
79
+ manager.setStandardWatch({ standardWatch: false, args: { params: mockParams1 } });
80
+ const watch = (manager as any).watches[mockKeyForSubscribe({ params: mockParams1 })];
81
+
82
+ expect(watch).toBeUndefined();
83
+ });
84
+ });
85
+
86
+ describe('listener and Callback Management', () => {
87
+ it('should add a new listener and a callback', () => {
88
+ const listener = manager.addEventListenerCallback({
89
+ callback: mockCallback1,
90
+ args: {
91
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
92
+ params: mockParams1,
93
+ id: 'cb-1'
94
+ }
95
+ });
96
+
97
+ expect(listener).toBeDefined();
98
+ expect(listener.event).toBe(STEVE_WATCH_EVENT_TYPES.CHANGES);
99
+ expect(listener.callbacks['cb-1']).toBe(mockCallback1);
100
+ const watch = manager.getWatch({ params: mockParams1 });
101
+
102
+ expect(watch?.listeners.length).toBe(1);
103
+ });
104
+
105
+ it('should add a second callback to an existing listener', () => {
106
+ manager.addEventListenerCallback({
107
+ callback: mockCallback1,
108
+ args: {
109
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
110
+ params: mockParams1,
111
+ id: 'cb-1'
112
+ }
113
+ });
114
+ manager.addEventListenerCallback({
115
+ callback: mockCallback2,
116
+ args: {
117
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
118
+ params: mockParams1,
119
+ id: 'cb-2'
120
+ }
121
+ });
122
+
123
+ const listener = manager.getEventListener({ args: { event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 } });
124
+
125
+ expect(Object.keys(listener?.callbacks || {})).toHaveLength(2);
126
+ expect(listener?.callbacks['cb-2']).toBe(mockCallback2);
127
+ });
128
+
129
+ it('should trigger a specific event listener and its callbacks', () => {
130
+ manager.addEventListenerCallback({
131
+ callback: mockCallback1,
132
+ args: {
133
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
134
+ params: mockParams1,
135
+ id: 'cb-1'
136
+ }
137
+ });
138
+ manager.addEventListenerCallback({
139
+ callback: mockCallback2,
140
+ args: {
141
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
142
+ params: mockParams1,
143
+ id: 'cb-2'
144
+ }
145
+ });
146
+
147
+ manager.triggerEventListener({ event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 });
148
+ expect(mockCallback1).toHaveBeenCalledTimes(1);
149
+ expect(mockCallback2).toHaveBeenCalledTimes(1);
150
+ });
151
+
152
+ it('should remove a specific callback from a listener', () => {
153
+ manager.addEventListenerCallback({
154
+ callback: mockCallback1,
155
+ args: {
156
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
157
+ params: mockParams1,
158
+ id: 'cb-1'
159
+ }
160
+ });
161
+ manager.removeEventListenerCallback({
162
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
163
+ params: mockParams1,
164
+ id: 'cb-1'
165
+ });
166
+ const listener = manager.getEventListener({ args: { event: STEVE_WATCH_EVENT_TYPES.CHANGES, params: mockParams1 } });
167
+
168
+ expect(listener?.callbacks['cb-1']).toBeUndefined();
169
+ });
170
+
171
+ it('should trigger all callbacks for a given watch', () => {
172
+ manager.addEventListenerCallback({
173
+ callback: mockCallback1,
174
+ args: {
175
+ event: STEVE_WATCH_EVENT_TYPES.CHANGES,
176
+ params: mockParams1,
177
+ id: 'cb-1'
178
+ }
179
+ });
180
+ manager.addEventListenerCallback({
181
+ callback: mockCallback2,
182
+ args: {
183
+ event: 'another.event' as STEVE_WATCH_EVENT_TYPES,
184
+ params: mockParams1,
185
+ id: 'cb-2'
186
+ }
187
+ });
188
+
189
+ manager.triggerAllEventListeners({ params: mockParams1 });
190
+ expect(mockCallback1).toHaveBeenCalledTimes(1);
191
+ expect(mockCallback2).toHaveBeenCalledTimes(1);
192
+ });
193
+ });
194
+ });
package/plugins/axios.js CHANGED
@@ -11,7 +11,8 @@ export default function({
11
11
  const options = { parseJSON: false };
12
12
  const csrf = store.getters['cookies/get']({ key: CSRF, options });
13
13
 
14
- if ( csrf ) {
14
+ // Request can ask not to send the CSRF header
15
+ if (csrf && !config.noApiCsrf) {
15
16
  config.headers['x-api-csrf'] = csrf;
16
17
  }
17
18
  });
@@ -220,6 +220,9 @@ export default {
220
220
  )
221
221
  ) {
222
222
  if (opt.watch !== false ) {
223
+ // Note - Empty revision here seems broken
224
+ // - list page (watch all) --> detail page (stop watch all, watch one) --> list page (watch all - no revision)
225
+ // - the missing revision means watch start from now... instead of the point the clusters were last monitored (cache contains stale data)
223
226
  const args = {
224
227
  type,
225
228
  revision: '',
@@ -695,7 +698,7 @@ export default {
695
698
 
696
699
  const res = await dispatch('request', { opt, type });
697
700
 
698
- await dispatch('load', { data: res });
701
+ await dispatch('load', { data: res, invalidatePageCache: opt.invalidatePageCache });
699
702
 
700
703
  if ( opt.watch !== false ) {
701
704
  dispatch('watch', createFindWatchArg({
@@ -526,7 +526,7 @@ export default {
526
526
  const store = state.config.namespace;
527
527
  const resource = id || context ? { id, context } : null;
528
528
 
529
- return paginationUtils.isEnabled({ rootGetters }, { store, resource });
529
+ return paginationUtils.isEnabled({ rootGetters, $plugin: rootState.$plugin }, { store, resource });
530
530
  },
531
531
 
532
532
  /**