@rancher/shell 3.0.1-rc.4 → 3.0.2-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 (131) hide show
  1. package/assets/data/aws-regions.json +1 -0
  2. package/assets/styles/base/_basic.scss +22 -5
  3. package/assets/styles/base/_mixins.scss +10 -1
  4. package/assets/styles/global/_button.scss +15 -0
  5. package/assets/styles/global/_form.scss +2 -2
  6. package/assets/styles/themes/_dark.scss +2 -0
  7. package/assets/styles/themes/_light.scss +2 -0
  8. package/assets/translations/en-us.yaml +60 -16
  9. package/assets/translations/zh-hans.yaml +2 -2
  10. package/chart/monitoring/StorageClassSelector.vue +1 -1
  11. package/components/ActionMenu.vue +8 -0
  12. package/components/AssignTo.vue +1 -0
  13. package/components/AsyncButton.vue +9 -2
  14. package/components/BackLink.vue +8 -2
  15. package/components/BannerGraphic.vue +10 -0
  16. package/components/ButtonGroup.vue +2 -0
  17. package/components/ButtonMultiAction.vue +6 -0
  18. package/components/ClusterIconMenu.vue +1 -1
  19. package/components/CodeMirror.vue +28 -1
  20. package/components/CommunityLinks.vue +13 -0
  21. package/components/CruResource.vue +6 -0
  22. package/components/GrowlManager.vue +14 -4
  23. package/components/LocaleSelector.vue +49 -5
  24. package/components/PaginatedResourceTable.vue +136 -0
  25. package/components/ResourceDetail/Masthead.vue +11 -4
  26. package/components/ResourceList/index.vue +5 -4
  27. package/components/ResourceTable.vue +7 -2
  28. package/components/SortableTable/THead.vue +19 -4
  29. package/components/SortableTable/index.vue +21 -15
  30. package/components/SortableTable/selection.js +19 -5
  31. package/components/Tabbed/index.vue +35 -2
  32. package/components/YamlEditor.vue +2 -1
  33. package/components/auth/SelectPrincipal.vue +1 -1
  34. package/components/fleet/FleetBundles.vue +2 -1
  35. package/components/form/LabeledSelect.vue +20 -7
  36. package/components/form/NodeScheduling.vue +5 -1
  37. package/components/form/Password.vue +23 -13
  38. package/components/form/ResourceLabeledSelect.vue +3 -3
  39. package/components/form/ResourceTabs/index.vue +0 -23
  40. package/components/form/Select.vue +28 -6
  41. package/components/form/SelectOrCreateAuthSecret.vue +39 -11
  42. package/components/form/Taints.vue +1 -1
  43. package/components/form/__tests__/NodeScheduling.test.ts +44 -0
  44. package/components/formatter/Endpoints.vue +1 -1
  45. package/components/formatter/LiveExpiryDate.vue +5 -1
  46. package/components/formatter/ServiceTargets.vue +1 -1
  47. package/components/formatter/ServiceType.vue +19 -17
  48. package/components/nav/Pinned.vue +6 -1
  49. package/components/nav/TopLevelMenu.helper.ts +562 -0
  50. package/components/nav/TopLevelMenu.vue +268 -168
  51. package/components/nav/__tests__/TopLevelMenu.test.ts +338 -326
  52. package/config/pagination-table-headers.js +13 -5
  53. package/config/product/apps.js +63 -30
  54. package/config/product/explorer.js +184 -17
  55. package/config/product/settings.js +9 -1
  56. package/config/router/routes.js +1 -2
  57. package/config/settings.ts +32 -2
  58. package/config/table-headers.js +23 -15
  59. package/config/types.js +2 -1
  60. package/core/plugin.ts +8 -1
  61. package/core/types-provisioning.ts +5 -0
  62. package/core/types.ts +26 -1
  63. package/dialog/DrainNode.vue +6 -6
  64. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +12 -3
  65. package/edit/catalog.cattle.io.clusterrepo.vue +95 -52
  66. package/edit/fleet.cattle.io.gitrepo.vue +40 -33
  67. package/edit/provisioning.cattle.io.cluster/index.vue +8 -3
  68. package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
  69. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +10 -2
  70. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
  71. package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +6 -3
  72. package/edit/workload/mixins/workload.js +15 -7
  73. package/list/catalog.cattle.io.app.vue +4 -11
  74. package/list/catalog.cattle.io.clusterrepo.vue +59 -25
  75. package/list/fleet.cattle.io.bundle.vue +2 -2
  76. package/list/management.cattle.io.feature.vue +12 -5
  77. package/list/management.cattle.io.setting.vue +30 -19
  78. package/list/namespace.vue +4 -1
  79. package/list/networking.k8s.io.ingress.vue +14 -11
  80. package/list/node.vue +72 -67
  81. package/list/persistentvolume.vue +55 -20
  82. package/list/persistentvolumeclaim.vue +3 -15
  83. package/list/service.vue +16 -21
  84. package/list/workload.vue +35 -49
  85. package/mixins/resource-fetch-api-pagination.js +40 -5
  86. package/mixins/resource-fetch.js +56 -6
  87. package/mixins/vue-select-overrides.js +10 -16
  88. package/models/management.cattle.io.cluster.js +6 -1
  89. package/models/management.cattle.io.nodepool.js +5 -4
  90. package/models/persistentvolume.js +1 -3
  91. package/models/provisioning.cattle.io.cluster.js +2 -10
  92. package/models/storage.k8s.io.storageclass.js +4 -0
  93. package/package.json +32 -33
  94. package/pages/about.vue +22 -0
  95. package/pages/c/_cluster/explorer/EventsTable.vue +58 -16
  96. package/pages/c/_cluster/explorer/__tests__/index.test.ts +36 -24
  97. package/pages/c/_cluster/explorer/index.vue +103 -75
  98. package/pages/c/_cluster/settings/performance.vue +49 -23
  99. package/pages/home.vue +331 -125
  100. package/pages/support/index.vue +1 -1
  101. package/plugins/dashboard-store/__tests__/mutations.test.ts +2 -0
  102. package/plugins/dashboard-store/actions.js +29 -19
  103. package/plugins/dashboard-store/getters.js +5 -2
  104. package/plugins/dashboard-store/mutations.js +4 -2
  105. package/plugins/floating-vue.js +1 -1
  106. package/plugins/steve/__tests__/mutations.test.ts +2 -1
  107. package/plugins/steve/steve-pagination-utils.ts +107 -14
  108. package/plugins/steve/subscribe.js +22 -8
  109. package/rancher-components/Banner/Banner.vue +12 -0
  110. package/rancher-components/Form/Checkbox/Checkbox.vue +27 -5
  111. package/rancher-components/Form/Radio/RadioButton.vue +0 -6
  112. package/rancher-components/Form/Radio/RadioGroup.vue +5 -1
  113. package/scripts/.gitlab/workflows/build-extension-catalog.gitlab-ci.yml +2 -2
  114. package/scripts/extension/parse-tag-name +2 -0
  115. package/scripts/test-plugins-build.sh +21 -5
  116. package/scripts/typegen.sh +1 -0
  117. package/store/index.js +47 -9
  118. package/store/type-map.utils.ts +14 -1
  119. package/tsconfig.json +7 -1
  120. package/types/resources/settings.d.ts +1 -1
  121. package/types/shell/index.d.ts +1403 -1523
  122. package/types/store/dashboard-store.types.ts +4 -0
  123. package/types/store/pagination.types.ts +13 -0
  124. package/types/store/vuex.d.ts +9 -1
  125. package/types/vue-shim.d.ts +3 -34
  126. package/utils/cluster.js +92 -1
  127. package/utils/pagination-utils.ts +17 -8
  128. package/utils/pagination-wrapper.ts +70 -0
  129. package/utils/string.js +6 -0
  130. package/utils/uiplugins.ts +18 -4
  131. package/vue.config.js +3 -4
@@ -1,16 +1,14 @@
1
1
  <script>
2
2
 
3
- import SortableTable from '@shell/components/SortableTable';
4
3
  import { MESSAGE, NAME, OBJECT, REASON } from '@shell/config/table-headers';
5
4
  import { EVENT } from '@shell/config/types';
6
- import { fetchClusterResources } from './explorer-utils';
5
+ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable';
6
+ import { STEVE_EVENT_OBJECT, STEVE_NAME_COL } from '@shell/config/pagination-table-headers';
7
+ import { headerFromSchemaColString } from '@shell/store/type-map.utils';
8
+ import { NAME as EXPLORER } from '@shell/config/product/explorer';
7
9
 
8
10
  export default {
9
- components: { SortableTable },
10
-
11
- async fetch() {
12
- this.events = await fetchClusterResources(this.$store, EVENT);
13
- },
11
+ components: { PaginatedResourceTable },
14
12
 
15
13
  data() {
16
14
  const reason = {
@@ -23,8 +21,7 @@ export default {
23
21
  reason,
24
22
  OBJECT,
25
23
  MESSAGE,
26
- NAME,
27
- {
24
+ NAME, {
28
25
  name: 'date',
29
26
  label: 'Date',
30
27
  labelKey: 'clusterIndexPage.sections.events.date.label',
@@ -36,9 +33,36 @@ export default {
36
33
  },
37
34
  ];
38
35
 
36
+ const schema = this.$store.getters['cluster/schemaFor'](EVENT);
37
+
38
+ const paginationHeaders = [
39
+ reason,
40
+ STEVE_EVENT_OBJECT,
41
+ MESSAGE,
42
+ {
43
+ ...STEVE_NAME_COL,
44
+ defaultSort: false,
45
+ },
46
+ headerFromSchemaColString('First Seen', schema, this.$store.getters, true),
47
+ {
48
+ ...headerFromSchemaColString('Last Seen', schema, this.$store.getters, true),
49
+ defaultSort: true,
50
+ },
51
+ headerFromSchemaColString('Count', schema, this.$store.getters, true),
52
+ ];
53
+
39
54
  return {
40
- events: [],
55
+ schema,
56
+ events: [],
41
57
  eventHeaders,
58
+ paginationHeaders,
59
+ allEventsLink: {
60
+ name: 'c-cluster-product-resource',
61
+ params: {
62
+ product: EXPLORER,
63
+ resource: EVENT,
64
+ }
65
+ }
42
66
  };
43
67
  },
44
68
 
@@ -63,16 +87,34 @@ export default {
63
87
  </script>
64
88
 
65
89
  <template>
66
- <SortableTable
67
- :loading="$fetchState.pending"
68
- :rows="events"
90
+ <PaginatedResourceTable
91
+ :schema="schema"
69
92
  :headers="eventHeaders"
93
+ :pagination-headers="paginationHeaders"
94
+
70
95
  key-field="id"
71
96
  :search="false"
72
97
  :table-actions="false"
73
98
  :row-actions="false"
74
- :paging="true"
99
+ :groupable="false"
75
100
  :rows-per-page="10"
76
- default-sort-by="date"
77
- />
101
+ >
102
+ <template v-slot:header-right>
103
+ <router-link
104
+ data-testid="events-link"
105
+ :to="allEventsLink"
106
+ class="events-link"
107
+ >
108
+ <span>{{ t('glance.eventsTable') }}</span>
109
+ </router-link>
110
+ </template>
111
+ </PaginatedResourceTable>
78
112
  </template>
113
+
114
+ <style lang="scss" scoped>
115
+ .events-link {
116
+ align-self: center;
117
+ padding-right: 20px;
118
+ min-width: 200px;
119
+ }
120
+ </style>
@@ -99,7 +99,7 @@ describe('page: cluster dashboard', () => {
99
99
  [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
100
100
  ]]
101
101
  ])('%p cluster - %p agent health box :', (_, agentId, isLocal, agentResources, statuses) => {
102
- it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
102
+ it.each(statuses)('should NOT show %p status due to missing canList permissions', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
103
103
  const options = clone(mountOptions);
104
104
 
105
105
  options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
@@ -138,41 +138,48 @@ describe('page: cluster dashboard', () => {
138
138
 
139
139
  describe.each([
140
140
  ['local', 'fleet', true, ['fleetDeployment', 'fleetStatefulSet'], [
141
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
142
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
143
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
144
- [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
145
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
146
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
147
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
141
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
142
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
143
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
144
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
145
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
146
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
147
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
148
148
  ]],
149
149
  ['downstream RKE2', 'fleet', false, ['fleetStatefulSet'], [
150
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
151
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
152
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
153
- [STATES_ENUM.WARNING, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
154
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
155
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
156
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
150
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
151
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
152
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
153
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
154
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
155
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
156
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
157
157
  ]],
158
158
  ['downstream RKE2', 'cattle', false, ['cattleDeployment'], [
159
- [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, '', 0, 0],
160
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, false, [{ status: 'False' }], 0, 0],
161
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, [{ status: 'True' }], 0, 0],
162
- [STATES_ENUM.UNHEALTHY, 'icon-warning', true, false, true, [{ status: 'True' }], 0, 0],
163
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 0],
164
- [STATES_ENUM.WARNING, 'icon-warning', true, false, false, [{ status: 'True' }], 0, 1],
165
- [STATES_ENUM.HEALTHY, 'icon-checkmark', true, false, false, [{ status: 'True' }], 1, 0],
159
+ [STATES_ENUM.IN_PROGRESS, 'icon-spinner', false, false, false, false, '', 0, 0],
160
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, false, [{ status: 'False' }], 0, 0],
161
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, true, false, [{ status: 'True' }], 0, 0],
162
+ [STATES_ENUM.UNHEALTHY, 'icon-warning', true, true, false, true, [{ status: 'True' }], 0, 0],
163
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 0],
164
+ [STATES_ENUM.WARNING, 'icon-warning', true, true, false, false, [{ status: 'True' }], 0, 1],
165
+ [STATES_ENUM.HEALTHY, 'icon-checkmark', false, true, false, false, [{ status: 'True' }], 1, 0],
166
166
  ]]
167
167
  ])('%p cluster - %p agent health box ::', (_, agentId, isLocal, agentResources, statuses) => {
168
- it.each(statuses)('should show %p status', (status, iconClass, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
168
+ it.each(statuses)('should show %p status', async(status, iconClass, clickable, isLoaded, disconnected, error, conditions, readyReplicas, unavailableReplicas) => {
169
+ let agentRoute = null;
170
+
169
171
  const options = clone(mountOptions);
170
172
 
171
173
  options.global.mocks.$store.getters.currentCluster.isLocal = isLocal;
172
174
 
173
- // let's pass the canList now
174
175
  options.global.mocks.$store.getters['cluster/canList'] = (type: string) => !!(type === WORKLOAD_TYPES.DEPLOYMENT) || !!(type === WORKLOAD_TYPES.STATEFUL_SET);
175
176
 
177
+ options.global.mocks.$router = {
178
+ push: (route: any) => {
179
+ agentRoute = route;
180
+ }
181
+ };
182
+
176
183
  const resources = agentResources.reduce((acc, r) => {
177
184
  const agent = {
178
185
  metadata: { state: { error } },
@@ -204,7 +211,12 @@ describe('page: cluster dashboard', () => {
204
211
 
205
212
  expect(box.element).toBeDefined();
206
213
  expect(box.element.classList).toContain(status);
214
+ expect(!!(box.element as any).$_popper).toBe(clickable);
207
215
  expect(icon.element.classList).toContain(iconClass);
216
+
217
+ await box.trigger('click');
218
+
219
+ expect(!!agentRoute).toBe(clickable);
208
220
  });
209
221
  });
210
222
 
@@ -125,6 +125,13 @@ export default {
125
125
  ROLES,
126
126
  ];
127
127
 
128
+ const clusterServiceIcons = {
129
+ [STATES_ENUM.IN_PROGRESS]: 'icon-spinner icon-spin',
130
+ [STATES_ENUM.HEALTHY]: 'icon-checkmark',
131
+ [STATES_ENUM.WARNING]: 'icon-warning',
132
+ [STATES_ENUM.UNHEALTHY]: 'icon-warning',
133
+ };
134
+
128
135
  return {
129
136
  nodeHeaders,
130
137
  constraints: [],
@@ -148,6 +155,8 @@ export default {
148
155
  clusterCounts,
149
156
  selectedTab: 'cluster-events',
150
157
  extensionCards: getApplicableExtensionEnhancements(this, ExtensionPoint.CARD, CardLocation.CLUSTER_DASHBOARD_CARD, this.$route),
158
+ canViewEvents: !!this.$store.getters['cluster/schemaFor'](EVENT),
159
+ clusterServiceIcons,
151
160
  };
152
161
  },
153
162
 
@@ -283,52 +292,51 @@ export default {
283
292
  clusterServices() {
284
293
  const services = [];
285
294
 
286
- CLUSTER_COMPONENTS.forEach((cs) => {
295
+ CLUSTER_COMPONENTS.forEach((name) => {
296
+ const component = this.getComponentStatus(name);
297
+
287
298
  services.push({
288
- name: cs,
289
- status: this.getComponentStatus(cs),
290
- labelKey: `clusterIndexPage.sections.componentStatus.${ cs }`,
299
+ name,
300
+ status: component.state,
301
+ labelKey: `clusterIndexPage.sections.componentStatus.component.${ name }.label`,
302
+ icon: this.clusterServiceIcons[component.state],
303
+ tooltip: component.tooltip,
304
+ goTo: () => null,
291
305
  });
292
306
  });
293
307
 
294
308
  if (this.cattleAgentNamespace) {
295
309
  services.push({
296
310
  name: 'cattle',
297
- status: this.cattleStatus,
298
- labelKey: 'clusterIndexPage.sections.componentStatus.cattle',
311
+ status: this.cattleAgent.state,
312
+ labelKey: 'clusterIndexPage.sections.componentStatus.component.cattle.label',
313
+ icon: this.clusterServiceIcons[this.cattleAgent.state],
314
+ tooltip: this.cattleAgent.tooltip,
315
+ goTo: () => this.goToClusterService(this.cattleAgent),
299
316
  });
300
317
  }
301
318
 
302
319
  if (this.fleetAgentNamespace) {
303
320
  services.push({
304
321
  name: 'fleet',
305
- status: this.fleetStatus,
306
- labelKey: 'clusterIndexPage.sections.componentStatus.fleet',
322
+ status: this.fleetAgent.state,
323
+ labelKey: 'clusterIndexPage.sections.componentStatus.component.fleet.label',
324
+ icon: this.clusterServiceIcons[this.fleetAgent.state],
325
+ tooltip: this.fleetAgent.tooltip,
326
+ goTo: () => this.goToClusterService(this.fleetAgent),
307
327
  });
308
328
  }
309
329
 
310
330
  return services;
311
331
  },
312
332
 
313
- cattleStatus() {
314
- const resource = this.cattleDeployment;
315
-
316
- if (resource === 'loading') {
317
- return STATES_ENUM.IN_PROGRESS;
318
- }
319
-
320
- if (!resource || this.disconnected || resource.status.conditions?.find((c) => c.status !== 'True') || resource.metadata.state?.error) {
321
- return STATES_ENUM.UNHEALTHY;
322
- }
333
+ cattleAgent() {
334
+ const resources = [this.cattleDeployment];
323
335
 
324
- if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
325
- return STATES_ENUM.WARNING;
326
- }
327
-
328
- return STATES_ENUM.HEALTHY;
336
+ return this.getAgentStatus(resources, { checkDisconnected: true });
329
337
  },
330
338
 
331
- fleetStatus() {
339
+ fleetAgent() {
332
340
  const resources = this.currentCluster.isLocal ? [
333
341
  /**
334
342
  * 'fleetStatefulSet' could take a while to be created by rancher.
@@ -339,23 +347,7 @@ export default {
339
347
  this.fleetStatefulSet
340
348
  ];
341
349
 
342
- if (resources.find((r) => r === 'loading')) {
343
- return STATES_ENUM.IN_PROGRESS;
344
- }
345
-
346
- for (const resource of resources) {
347
- if (!resource || resource.status.conditions?.find((c) => c.status !== 'True') || resource.metadata.state?.error) {
348
- return STATES_ENUM.UNHEALTHY;
349
- }
350
- }
351
-
352
- for (const resource of resources) {
353
- if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
354
- return STATES_ENUM.WARNING;
355
- }
356
- }
357
-
358
- return STATES_ENUM.HEALTHY;
350
+ return this.getAgentStatus(resources);
359
351
  },
360
352
 
361
353
  totalCountGaugeInput() {
@@ -482,16 +474,6 @@ export default {
482
474
  return !!this.currentCluster?.spec?.description;
483
475
  },
484
476
 
485
- allEventsLink() {
486
- return {
487
- name: 'c-cluster-product-resource',
488
- params: {
489
- product: EXPLORER,
490
- resource: EVENT,
491
- }
492
- };
493
- },
494
-
495
477
  allSecretsLink() {
496
478
  return {
497
479
  name: 'c-cluster-product-resource',
@@ -532,25 +514,61 @@ export default {
532
514
  }
533
515
  },
534
516
 
517
+ getAgentStatus(resources, opt = { checkDisconnected: false }) {
518
+ if (resources.find((resource) => resource === 'loading')) {
519
+ return { state: STATES_ENUM.IN_PROGRESS };
520
+ }
521
+
522
+ for (const resource of resources) {
523
+ if (
524
+ !resource ||
525
+ (opt.checkDisconnected && this.disconnected) || // cattle
526
+ resource.status.conditions?.find((c) => c.status !== 'True') ||
527
+ resource.metadata.state?.error
528
+ ) {
529
+ return {
530
+ resource,
531
+ tooltip: resource?.stateDescription || this.t(`clusterIndexPage.sections.componentStatus.tooltip.disconnected`),
532
+ state: STATES_ENUM.UNHEALTHY,
533
+ };
534
+ }
535
+ }
536
+
537
+ for (const resource of resources) {
538
+ if (resource.spec.replicas !== resource.status.readyReplicas || resource.status.unavailableReplicas > 0) {
539
+ return {
540
+ resource,
541
+ tooltip: resource?.stateDescription || this.t(`clusterIndexPage.sections.componentStatus.tooltip.unavailableReplicas`),
542
+ state: STATES_ENUM.WARNING,
543
+ };
544
+ }
545
+ }
546
+
547
+ return { state: STATES_ENUM.HEALTHY };
548
+ },
549
+
535
550
  getComponentStatus(field) {
536
551
  const matching = (this.currentCluster?.status?.componentStatuses || []).filter((s) => s.name.startsWith(field));
537
552
 
538
553
  // If there's no matching component status, it's "healthy"
539
554
  if ( !matching.length ) {
540
- return STATES_ENUM.HEALTHY;
555
+ return { state: STATES_ENUM.HEALTHY };
541
556
  }
542
557
 
543
- const count = matching.reduce((acc, status) => {
544
- const conditions = status.conditions.find((c) => c.status !== 'True');
558
+ const errorConditions = matching.reduce((acc, status) => {
559
+ const condition = status.conditions.find((c) => c.status !== 'True');
545
560
 
546
- return !conditions ? acc : acc + 1;
547
- }, 0);
561
+ return !condition ? acc : [...acc, condition];
562
+ }, []);
548
563
 
549
- if (count > 0) {
550
- return STATES_ENUM.UNHEALTHY;
564
+ if (errorConditions.length > 0) {
565
+ return {
566
+ tooltip: errorConditions[0].message,
567
+ state: STATES_ENUM.UNHEALTHY
568
+ };
551
569
  }
552
570
 
553
- return STATES_ENUM.HEALTHY;
571
+ return { state: STATES_ENUM.HEALTHY };
554
572
  },
555
573
 
556
574
  showActions() {
@@ -578,6 +596,23 @@ export default {
578
596
  await provCluster.goToHarvesterCluster();
579
597
  } catch {
580
598
  }
599
+ },
600
+
601
+ goToClusterService(agent) {
602
+ if (!agent.resource || agent.state === STATES_ENUM.HEALTHY) {
603
+ return;
604
+ }
605
+
606
+ this.$router.push({
607
+ name: 'c-cluster-product-resource-namespace-id',
608
+ params: {
609
+ cluster: this.currentCluster.id,
610
+ product: 'explorer',
611
+ resource: agent.resource.type,
612
+ namespace: agent.resource.metadata.namespace,
613
+ id: agent.resource.metadata.name,
614
+ }
615
+ });
581
616
  }
582
617
  },
583
618
  };
@@ -722,21 +757,15 @@ export default {
722
757
  <div
723
758
  v-for="(service, i) in clusterServices"
724
759
  :key="i"
760
+ v-clean-tooltip="service.tooltip"
725
761
  class="k8s-service-status"
726
762
  :class="{[service.status]: true }"
727
763
  :data-testid="`k8s-service-${ service.name }`"
764
+ @click="service.goTo"
728
765
  >
729
766
  <i
730
- v-if="service.status === STATES_ENUM.IN_PROGRESS"
731
- class="icon icon-spinner icon-spin"
732
- />
733
- <i
734
- v-else-if="service.status === STATES_ENUM.HEALTHY"
735
- class="icon icon-checkmark"
736
- />
737
- <i
738
- v-else
739
- class="icon icon-warning"
767
+ class="icon"
768
+ :class="service.icon"
740
769
  />
741
770
  <div class="label">
742
771
  {{ t(service.labelKey) }}
@@ -747,15 +776,11 @@ export default {
747
776
  <div class="mt-30">
748
777
  <Tabbed @changed="tabChange">
749
778
  <Tab
779
+ v-if="canViewEvents"
750
780
  name="cluster-events"
751
781
  :label="t('clusterIndexPage.sections.events.label')"
752
782
  :weight="2"
753
783
  >
754
- <span class="events-table-link">
755
- <router-link :to="allEventsLink">
756
- <span>{{ t('glance.eventsTable') }}</span>
757
- </router-link>
758
- </span>
759
784
  <EventsTable />
760
785
  </Tab>
761
786
  <Tab
@@ -916,7 +941,7 @@ export default {
916
941
  }
917
942
  }
918
943
 
919
- .events-table-link, .cert-table-link {
944
+ .cert-table-link {
920
945
  display: flex;
921
946
  justify-content: flex-end;
922
947
  margin-bottom: 20px;
@@ -948,6 +973,7 @@ export default {
948
973
 
949
974
  &.unhealthy {
950
975
  border-color: var(--error-border);
976
+ cursor: pointer;
951
977
 
952
978
  > I {
953
979
  color: var(--error)
@@ -955,6 +981,8 @@ export default {
955
981
  }
956
982
 
957
983
  &.warning {
984
+ cursor: pointer;
985
+
958
986
  > I {
959
987
  color: var(--warning)
960
988
  }
@@ -103,17 +103,17 @@ export default {
103
103
  resources.push(this.t('performance.serverPagination.resources.all'));
104
104
  } else {
105
105
  settings.resources.enableSome.enabled.forEach((resource) => {
106
- resources.push(resource);
106
+ resources.push(!!resource.length ? resource : `${ resource.resource } (${ resource.context })`);
107
107
  });
108
108
  if (settings.resources.enableSome.generic) {
109
109
  resources.push(this.t('performance.serverPagination.resources.generic', {}, true));
110
110
  }
111
111
  }
112
112
 
113
- storeResources.push(`${ store }: ${ resources.join(', ') }`);
113
+ storeResources.push(`Resources in store '${ store }': ${ resources.join(', ') }`);
114
114
  });
115
115
 
116
- return storeResources.join('. ');
116
+ return storeResources.join('<br><br>');
117
117
  }
118
118
  },
119
119
 
@@ -187,6 +187,13 @@ export default {
187
187
  body: this.t(`performance.${ l10n[property] }.incompatibleDescription`, {}, true),
188
188
  },
189
189
  });
190
+ },
191
+
192
+ setPaginationDefaults() {
193
+ this.value = {
194
+ ...this.value,
195
+ serverPagination: { ...DEFAULT_PERF_SETTING.serverPagination }
196
+ };
190
197
  }
191
198
  },
192
199
  };
@@ -202,7 +209,9 @@ export default {
202
209
  <div class="ui-perf-setting">
203
210
  <!-- Server Side Pagination -->
204
211
  <div class="mt-40">
205
- <h2>{{ t('performance.serverPagination.label') }}</h2>
212
+ <h2 id="ssp-setting">
213
+ {{ t('performance.serverPagination.label') }}
214
+ </h2>
206
215
  <p>{{ t('performance.serverPagination.description') }}</p>
207
216
  <Banner
208
217
  color="error"
@@ -225,9 +234,17 @@ export default {
225
234
  <p :class="{ 'text-muted': !value.serverPagination.enabled }">
226
235
  {{ t('performance.serverPagination.applicable') }}
227
236
  </p>
228
- <p :class="{ 'text-muted': !value.serverPagination.enabled }">
229
- {{ steveCacheApplicableResources }}
230
- </p>
237
+ <p
238
+ v-clean-html="steveCacheApplicableResources"
239
+ :class="{ 'text-muted': !value.serverPagination.enabled }"
240
+ />
241
+ <button
242
+ class="btn btn-sm role-primary"
243
+ style="width: fit-content;"
244
+ @click.prevent="setPaginationDefaults()"
245
+ >
246
+ {{ t('performance.serverPagination.populateDefaults') }}
247
+ </button>
231
248
  </div>
232
249
  <!-- Inactivity -->
233
250
  <div class="mt-20">
@@ -273,6 +290,11 @@ export default {
273
290
  <!-- Incremental Loading -->
274
291
  <div class="mt-40">
275
292
  <h2>{{ t('performance.incrementalLoad.label') }}</h2>
293
+ <Banner
294
+ color="warning"
295
+ >
296
+ <span v-clean-html="t(`performance.deprecatedForSSP`, { setting: t('performance.incrementalLoad.label') }, true)" />
297
+ </Banner>
276
298
  <p>{{ t('performance.incrementalLoad.description') }}</p>
277
299
  <Checkbox
278
300
  :value="value.incrementalLoading.enabled"
@@ -300,11 +322,12 @@ export default {
300
322
  <!-- Enable manual refresh list views -->
301
323
  <div class="mt-40">
302
324
  <h2 v-t="'performance.manualRefresh.label'" />
303
- <p>{{ t('performance.manualRefresh.description') }}</p>
304
325
  <Banner
305
- color="error"
306
- label-key="performance.experimental"
307
- />
326
+ color="warning"
327
+ >
328
+ <span v-clean-html="t(`performance.deprecatedForSSP`, { setting: t('performance.manualRefresh.label') }, true)" />
329
+ </Banner>
330
+ <p>{{ t('performance.manualRefresh.description') }}</p>
308
331
  <Checkbox
309
332
  :value="value.manualRefresh.enabled"
310
333
  :mode="mode"
@@ -331,11 +354,12 @@ export default {
331
354
  <!-- Enable GC of resources from store -->
332
355
  <div class="mt-40">
333
356
  <h2 v-t="'performance.gc.label'" />
334
- <p>{{ t('performance.gc.description') }}</p>
335
357
  <Banner
336
- color="error"
337
- label-key="performance.experimental"
338
- />
358
+ color="warning"
359
+ >
360
+ <span v-clean-html="t(`performance.deprecatedForSSP`, { setting: t('performance.gc.label') }, true)" />
361
+ </Banner>
362
+ <p>{{ t('performance.gc.description') }}</p>
339
363
  <Checkbox
340
364
  v-model:value="value.garbageCollection.enabled"
341
365
  :mode="mode"
@@ -411,11 +435,12 @@ export default {
411
435
  <!-- Force NS filter -->
412
436
  <div class="mt-40">
413
437
  <h2>{{ t('performance.nsFiltering.label') }}</h2>
414
- <p>{{ t('performance.nsFiltering.description') }}</p>
415
438
  <Banner
416
- color="error"
417
- label-key="performance.experimental"
418
- />
439
+ color="warning"
440
+ >
441
+ <span v-clean-html="t(`performance.deprecatedForSSP`, { setting: t('performance.nsFiltering.label') }, true)" />
442
+ </Banner>
443
+ <p>{{ t('performance.nsFiltering.description') }}</p>
419
444
  <Checkbox
420
445
  :value="value.forceNsFilterV2.enabled"
421
446
  :mode="mode"
@@ -428,11 +453,12 @@ export default {
428
453
  <!-- Advanced Websocket Worker -->
429
454
  <div class="mt-40">
430
455
  <h2>{{ t('performance.advancedWorker.label') }}</h2>
431
- <p>{{ t('performance.advancedWorker.description') }}</p>
432
456
  <Banner
433
- color="error"
434
- label-key="performance.experimental"
435
- />
457
+ color="warning"
458
+ >
459
+ <span v-clean-html="t(`performance.deprecatedForSSP`, { setting: t('performance.advancedWorker.label') }, true)" />
460
+ </Banner>
461
+ <p>{{ t('performance.advancedWorker.description') }}</p>
436
462
  <Checkbox
437
463
  v-model:value="value.advancedWorker.enabled"
438
464
  :mode="mode"