@rancher/shell 3.0.5-rc.8 → 3.0.5-rc.9

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 (171) hide show
  1. package/assets/styles/base/_color.scss +4 -1
  2. package/assets/styles/global/_tooltip.scss +7 -4
  3. package/assets/styles/themes/_dark.scss +11 -0
  4. package/assets/styles/themes/_light.scss +13 -1
  5. package/assets/styles/themes/_modern.scss +22 -0
  6. package/assets/translations/en-us.yaml +136 -14
  7. package/assets/translations/zh-hans.yaml +0 -1
  8. package/chart/monitoring/grafana/index.vue +8 -2
  9. package/components/ActionMenuShell.vue +3 -1
  10. package/components/Cron/CronExpressionEditor.vue +299 -0
  11. package/components/Cron/CronExpressionEditorModal.vue +247 -0
  12. package/components/Cron/CronTooltip.vue +87 -0
  13. package/components/Cron/types.ts +13 -0
  14. package/components/ForceDirectedTreeChart/composable.ts +11 -0
  15. package/components/PromptModal.vue +1 -1
  16. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  17. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  18. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  19. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  20. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  21. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  22. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  23. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  24. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
  25. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  26. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  27. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  28. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  29. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  30. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  31. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  32. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
  33. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  34. package/components/Resource/Detail/Metadata/composables.ts +1 -4
  35. package/components/Resource/Detail/Metadata/index.vue +1 -0
  36. package/components/Resource/Detail/Preview/Content.vue +63 -0
  37. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  38. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  39. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  40. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  41. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  42. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  43. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  44. package/components/Resource/Detail/SpacedRow.vue +1 -0
  45. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
  46. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  47. package/components/Resource/Detail/TitleBar/composables.ts +1 -3
  48. package/components/Resource/Detail/TitleBar/index.vue +2 -29
  49. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  50. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  51. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  52. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  53. package/components/ResourceDetail/index.vue +1 -26
  54. package/components/ResourceTable.vue +24 -0
  55. package/components/SortableTable/index.vue +7 -1
  56. package/components/SortableTable/paging.js +3 -0
  57. package/components/Tabbed/Tab.vue +43 -1
  58. package/components/Tabbed/index.vue +3 -1
  59. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  60. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  61. package/components/auth/login/saml.vue +86 -0
  62. package/components/form/LabeledSelect.vue +8 -8
  63. package/components/form/ResourceTabs/composable.ts +54 -0
  64. package/components/form/ResourceTabs/index.vue +10 -7
  65. package/components/form/Select.vue +13 -10
  66. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  67. package/components/form/__tests__/Select.test.ts +134 -0
  68. package/composables/useExtensionManager.ts +17 -0
  69. package/config/home-links.js +12 -0
  70. package/config/labels-annotations.js +0 -1
  71. package/config/page-actions.js +0 -1
  72. package/config/product/explorer.js +3 -1
  73. package/config/product/fleet.js +2 -7
  74. package/config/product/manager.js +0 -5
  75. package/config/query-params.js +1 -0
  76. package/config/router/navigation-guards/clusters.js +2 -1
  77. package/config/router/navigation-guards/products.js +1 -1
  78. package/core/extension-manager-impl.js +518 -0
  79. package/core/plugins.js +35 -468
  80. package/core/types.ts +8 -2
  81. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  82. package/detail/catalog.cattle.io.app.vue +7 -4
  83. package/detail/fleet.cattle.io.bundle.vue +1 -5
  84. package/detail/fleet.cattle.io.cluster.vue +3 -2
  85. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  86. package/detail/fleet.cattle.io.helmop.vue +78 -49
  87. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  88. package/dialog/GenericPrompt.vue +1 -1
  89. package/dialog/ImportDialog.vue +9 -2
  90. package/dialog/InstallExtensionDialog.vue +18 -10
  91. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  92. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  93. package/edit/cloudcredential.vue +31 -17
  94. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  95. package/edit/fleet.cattle.io.cluster.vue +19 -0
  96. package/edit/fleet.cattle.io.gitrepo.vue +23 -16
  97. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  98. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  99. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  100. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
  101. package/edit/resources.cattle.io.restore.vue +5 -8
  102. package/list/__tests__/workload.test.ts +1 -0
  103. package/list/workload.vue +8 -1
  104. package/machine-config/components/GCEImage.vue +6 -5
  105. package/machine-config/google.vue +11 -6
  106. package/mixins/__tests__/chart.test.ts +139 -1
  107. package/mixins/chart.js +58 -18
  108. package/models/__tests__/namespace.test.ts +69 -0
  109. package/models/apps.statefulset.js +8 -10
  110. package/models/chart.js +5 -1
  111. package/models/fleet-application.js +16 -46
  112. package/models/fleet.cattle.io.bundle.js +1 -38
  113. package/models/fleet.cattle.io.gitrepo.js +4 -0
  114. package/models/fleet.cattle.io.helmop.js +4 -0
  115. package/models/management.cattle.io.project.js +12 -0
  116. package/models/namespace.js +30 -0
  117. package/models/workload.js +3 -0
  118. package/package.json +10 -10
  119. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
  120. package/pages/c/_cluster/apps/charts/chart.vue +29 -20
  121. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  122. package/pages/c/_cluster/apps/charts/install.vue +6 -5
  123. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  124. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  125. package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
  126. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  127. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  128. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  129. package/pages/c/_cluster/uiplugins/index.vue +221 -363
  130. package/pages/home.vue +1 -9
  131. package/plugins/dashboard-store/resource-class.js +49 -0
  132. package/public/index.html +2 -1
  133. package/rancher-components/Card/Card.vue +1 -1
  134. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  135. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  136. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  137. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  138. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  139. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  140. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  141. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  142. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
  143. package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
  144. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
  145. package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
  146. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  147. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  148. package/rancher-components/Pill/RcTag/index.ts +1 -0
  149. package/rancher-components/Pill/RcTag/types.ts +9 -0
  150. package/rancher-components/Pill/types.ts +1 -0
  151. package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
  152. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  153. package/store/__tests__/catalog.test.ts +63 -0
  154. package/store/catalog.js +2 -2
  155. package/store/type-map.js +3 -15
  156. package/types/extension-manager.ts +26 -0
  157. package/types/shell/index.d.ts +121 -16
  158. package/utils/__tests__/product.test.ts +129 -0
  159. package/utils/__tests__/resource.test.ts +87 -0
  160. package/utils/alertmanagerconfig.js +2 -2
  161. package/utils/auth.js +3 -76
  162. package/utils/product.ts +39 -0
  163. package/utils/resource.ts +35 -0
  164. package/utils/select.js +0 -24
  165. package/utils/validators/formRules/__tests__/index.test.ts +3 -0
  166. package/utils/validators/formRules/index.ts +2 -1
  167. package/vue.config.js +1 -1
  168. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  169. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  170. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  171. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -29,7 +29,10 @@ describe('chartMixin', () => {
29
29
  return () => 'repo';
30
30
  },
31
31
  'catalog/chart': () => {
32
- return { id: chartId };
32
+ return {
33
+ id: chartId,
34
+ matchingInstalledApps: []
35
+ };
33
36
  },
34
37
  'i18n/t': () => jest.fn()
35
38
  }
@@ -101,4 +104,139 @@ describe('chartMixin', () => {
101
104
  expect(warnings).toHaveLength(expected as number);
102
105
  }
103
106
  );
107
+
108
+ describe('fetchChart', () => {
109
+ it('should call catalog/version with showDeprecated', async() => {
110
+ const mockStore = {
111
+ dispatch: jest.fn(() => Promise.resolve()),
112
+ getters: {
113
+ currentCluster: () => {},
114
+ isRancher: () => true,
115
+ 'catalog/repo': () => {
116
+ return () => 'repo';
117
+ },
118
+ 'catalog/chart': () => {
119
+ return {
120
+ id: 'chart-id', versions: [{ version: '1.0.0' }], matchingInstalledApps: []
121
+ };
122
+ },
123
+ 'catalog/version': jest.fn(),
124
+ 'prefs/get': () => {},
125
+ 'i18n/t': () => jest.fn()
126
+ }
127
+ };
128
+
129
+ const DummyComponent = {
130
+ mixins: [ChartMixin],
131
+ template: '<div></div>',
132
+ };
133
+
134
+ const wrapper = mount(
135
+ DummyComponent,
136
+ {
137
+ global: {
138
+ mocks: {
139
+ $store: mockStore,
140
+ $route: {
141
+ query: {
142
+ chart: 'chart_name',
143
+ deprecated: 'true'
144
+ }
145
+ }
146
+ }
147
+ }
148
+ });
149
+
150
+ await wrapper.vm.fetchChart();
151
+
152
+ expect(mockStore.getters['catalog/version']).toHaveBeenCalledWith(expect.objectContaining({ showDeprecated: true }));
153
+ });
154
+ });
155
+
156
+ describe('action', () => {
157
+ const DummyComponent = {
158
+ mixins: [ChartMixin],
159
+ template: '<div></div>',
160
+ };
161
+
162
+ const mockStore = {
163
+ dispatch: jest.fn(() => Promise.resolve()),
164
+ getters: { 'i18n/t': (key: string) => key }
165
+ };
166
+
167
+ it('should return "install" action when not installed', () => {
168
+ const wrapper = mount(DummyComponent, {
169
+ data: () => ({ existing: null }),
170
+ global: { mocks: { $store: mockStore } }
171
+ });
172
+
173
+ expect(wrapper.vm.action).toStrictEqual({
174
+ name: 'install',
175
+ tKey: 'install',
176
+ icon: 'icon-plus',
177
+ });
178
+ });
179
+
180
+ it('should return "editVersion" action when installed and on same version', () => {
181
+ const wrapper = mount(DummyComponent, {
182
+ data: () => ({
183
+ existing: { spec: { chart: { metadata: { version: '1.0.0' } } } },
184
+ version: { version: '1.0.0' }
185
+ }),
186
+ global: {
187
+ mocks: {
188
+ $store: mockStore,
189
+ $route: { query: {} }
190
+ }
191
+ }
192
+ });
193
+
194
+ expect(wrapper.vm.action).toStrictEqual({
195
+ name: 'editVersion',
196
+ tKey: 'edit',
197
+ icon: 'icon-edit',
198
+ });
199
+ });
200
+
201
+ it('should return "upgrade" action when installed and on a newer version', () => {
202
+ const wrapper = mount(DummyComponent, {
203
+ data: () => ({
204
+ existing: { spec: { chart: { metadata: { version: '1.0.0' } } } },
205
+ version: { version: '1.0.1' }
206
+ }),
207
+ global: {
208
+ mocks: {
209
+ $store: mockStore,
210
+ $route: { query: {} }
211
+ }
212
+ }
213
+ });
214
+
215
+ expect(wrapper.vm.action).toStrictEqual({
216
+ name: 'upgradeVersion',
217
+ tKey: 'upgrade',
218
+ icon: 'icon-upgrade-alt',
219
+ });
220
+ });
221
+ it('should return "downgrade" action when installed and on an older version', () => {
222
+ const wrapper = mount(DummyComponent, {
223
+ data: () => ({
224
+ existing: { spec: { chart: { metadata: { version: '1.0.1' } } } },
225
+ version: { version: '1.0.0' }
226
+ }),
227
+ global: {
228
+ mocks: {
229
+ $store: mockStore,
230
+ $route: { query: {} }
231
+ }
232
+ }
233
+ });
234
+
235
+ expect(wrapper.vm.action).toStrictEqual({
236
+ name: 'downgrade',
237
+ tKey: 'downgrade',
238
+ icon: 'icon-history',
239
+ });
240
+ });
241
+ });
104
242
  });
package/mixins/chart.js CHANGED
@@ -1,6 +1,5 @@
1
1
 
2
2
  import { mapGetters } from 'vuex';
3
-
4
3
  import {
5
4
  REPO_TYPE, REPO, CHART, VERSION, NAMESPACE, NAME, DESCRIPTION as DESCRIPTION_QUERY, DEPRECATED as DEPRECATED_QUERY, HIDDEN, _FLAGGED, _CREATE, _EDIT
6
5
  } from '@shell/config/query-params';
@@ -9,10 +8,9 @@ import { SHOW_PRE_RELEASE, mapPref } from '@shell/store/prefs';
9
8
  import { NAME as EXPLORER } from '@shell/config/product/explorer';
10
9
  import { NAME as MANAGER } from '@shell/config/product/manager';
11
10
  import { OPA_GATE_KEEPER_ID } from '@shell/pages/c/_cluster/gatekeeper/index.vue';
12
-
13
11
  import { formatSi, parseSi } from '@shell/utils/units';
14
12
  import { CAPI, CATALOG } from '@shell/config/types';
15
- import { isPrerelease } from '@shell/utils/version';
13
+ import { isPrerelease, compare } from '@shell/utils/version';
16
14
  import difference from 'lodash/difference';
17
15
  import { LINUX, APP_UPGRADE_STATUS } from '@shell/store/catalog';
18
16
  import { clone } from '@shell/utils/object';
@@ -230,11 +228,27 @@ export default {
230
228
  },
231
229
 
232
230
  action() {
233
- if (this.existing) {
234
- return this.currentVersion === this.targetVersion ? 'update' : 'upgrade';
231
+ if (!this.existing) {
232
+ return {
233
+ name: 'install', tKey: 'install', icon: 'icon-plus'
234
+ };
235
+ }
236
+
237
+ if (this.currentVersion === this.targetVersion) {
238
+ return {
239
+ name: 'editVersion', tKey: 'edit', icon: 'icon-edit'
240
+ };
241
+ }
242
+
243
+ if (compare(this.currentVersion, this.targetVersion) < 0) {
244
+ return {
245
+ name: 'upgradeVersion', tKey: 'upgrade', icon: 'icon-upgrade-alt'
246
+ };
235
247
  }
236
248
 
237
- return 'install';
249
+ return {
250
+ name: 'downgrade', tKey: 'downgrade', icon: 'icon-history'
251
+ };
238
252
  },
239
253
 
240
254
  isChartTargeted() {
@@ -276,7 +290,10 @@ export default {
276
290
  async fetchChart() {
277
291
  this.versionInfoError = null;
278
292
 
279
- await this.$store.dispatch('catalog/load'); // not the problem
293
+ await Promise.all([
294
+ this.$store.dispatch('catalog/load'),
295
+ this.$store.dispatch('cluster/findAll', { type: CATALOG.APP })
296
+ ]);
280
297
 
281
298
  this.fetchStoreChart();
282
299
 
@@ -316,6 +333,15 @@ export default {
316
333
  this.mode = _CREATE;
317
334
  this.existing = null;
318
335
  }
336
+ } else if (this.chart) {
337
+ const matching = this.chart.matchingInstalledApps;
338
+
339
+ if (matching.length === 1) {
340
+ this.existing = matching[0];
341
+ this.mode = _EDIT;
342
+ } else {
343
+ this.mode = _CREATE;
344
+ }
319
345
  } else {
320
346
  // Regular create
321
347
 
@@ -345,10 +371,11 @@ export default {
345
371
 
346
372
  try {
347
373
  this.version = this.$store.getters['catalog/version']({
348
- repoType: this.query.repoType,
349
- repoName: this.query.repoName,
350
- chartName: this.query.chartName,
351
- versionName: this.query.versionName
374
+ repoType: this.query.repoType,
375
+ repoName: this.query.repoName,
376
+ chartName: this.query.chartName,
377
+ versionName: this.query.versionName,
378
+ showDeprecated: this.showDeprecated
352
379
  });
353
380
  } catch (e) {
354
381
  console.error('Unable to fetch Version: ', e); // eslint-disable-line no-console
@@ -503,18 +530,24 @@ export default {
503
530
  version: this.query.versionName,
504
531
  };
505
532
 
533
+ const query = {
534
+ [REPO_TYPE]: provider.repoType,
535
+ [REPO]: provider.repoName,
536
+ [CHART]: provider.name,
537
+ [VERSION]: provider.version,
538
+ };
539
+
540
+ if (this.showDeprecated) {
541
+ query[DEPRECATED_QUERY] = this.query.deprecated;
542
+ }
543
+
506
544
  return {
507
545
  name: install ? 'c-cluster-apps-charts-install' : 'c-cluster-apps-charts-chart',
508
546
  params: {
509
547
  cluster: this.$route.params.cluster,
510
548
  product: this.$store.getters['productId'],
511
549
  },
512
- query: {
513
- [REPO_TYPE]: provider.repoType,
514
- [REPO]: provider.repoName,
515
- [CHART]: provider.name,
516
- [VERSION]: provider.version,
517
- }
550
+ query
518
551
  };
519
552
  },
520
553
 
@@ -530,13 +563,20 @@ export default {
530
563
  },
531
564
 
532
565
  clusterToolsLocation() {
566
+ const query = {};
567
+
568
+ if (this.showDeprecated) {
569
+ query[DEPRECATED_QUERY] = this.query.deprecated;
570
+ }
571
+
533
572
  return {
534
573
  name: `c-cluster-explorer-tools`,
535
574
  params: {
536
575
  product: EXPLORER,
537
576
  cluster: this.$store.getters['clusterId'],
538
577
  resource: CATALOG.APP,
539
- }
578
+ },
579
+ query
540
580
  };
541
581
  },
542
582
 
@@ -186,4 +186,73 @@ describe('class Namespace', () => {
186
186
  it.todo('should return the resourceQuota');
187
187
  it.todo('should set the resourceQuota as reactive Vue property');
188
188
  it.todo('should reset project with cleanForNew');
189
+
190
+ describe('glance', () => {
191
+ it('should return projectGlance instead of namespace when namespace is in a project', () => {
192
+ const t = jest.fn((key) => key);
193
+ const ctx = { rootGetters: { 'i18n/t': t } };
194
+ const namespace = new Namespace({}, ctx);
195
+
196
+ const project = {
197
+ detailLocation: 'project-detail',
198
+ nameDisplay: 'My Project',
199
+ };
200
+
201
+ jest.spyOn(namespace, 'project', 'get').mockReturnValue(project);
202
+ Object.defineProperty(namespace, '_glance', { get: jest.fn(() => [{ name: 'namespace' }, { name: 'other' }]) });
203
+
204
+ const result = namespace.glance;
205
+
206
+ expect(result).toHaveLength(2);
207
+ expect(result[0].name).toBe('project');
208
+ expect(result[0].label).toBe('component.resource.detail.glance.project');
209
+ expect(result[0].formatter).toBe('Link');
210
+ expect(result[0].formatterOpts?.to).toBe('project-detail');
211
+ expect(result[0].content).toBe('My Project');
212
+ expect(result[1].name).toBe('other');
213
+ });
214
+
215
+ it('should remove namespace from glance when namespace is not in a project', () => {
216
+ const namespace = new Namespace({});
217
+
218
+ jest.spyOn(namespace, 'project', 'get').mockReturnValue(null);
219
+ Object.defineProperty(namespace, '_glance', { get: jest.fn(() => [{ name: 'namespace' }, { name: 'other' }]) });
220
+
221
+ const result = namespace.glance;
222
+
223
+ expect(result).toHaveLength(1);
224
+ expect(result[0].name).toBe('other');
225
+ });
226
+ });
227
+
228
+ describe('projectGlance', () => {
229
+ it('should return undefined if namespace is not in a project', () => {
230
+ const namespace = new Namespace({});
231
+
232
+ jest.spyOn(namespace, 'project', 'get').mockReturnValue(null);
233
+
234
+ expect(namespace.projectGlance).toBeUndefined();
235
+ });
236
+
237
+ it('should return project glance information if namespace is in a project', () => {
238
+ const t = jest.fn((key) => key);
239
+ const ctx = { rootGetters: { 'i18n/t': t } };
240
+ const namespace = new Namespace({}, ctx);
241
+
242
+ const project = {
243
+ detailLocation: 'project-detail',
244
+ nameDisplay: 'My Project',
245
+ };
246
+
247
+ jest.spyOn(namespace, 'project', 'get').mockReturnValue(project);
248
+
249
+ const result = namespace.projectGlance;
250
+
251
+ expect(result?.name).toBe('project');
252
+ expect(result?.label).toBe('component.resource.detail.glance.project');
253
+ expect(result?.formatter).toBe('Link');
254
+ expect(result?.formatterOpts.to).toBe('project-detail');
255
+ expect(result?.content).toBe('My Project');
256
+ });
257
+ });
189
258
  });
@@ -1,5 +1,5 @@
1
1
  import Workload from './workload';
2
- import { WORKLOAD_TYPES, POD, WORKLOAD_TYPE_TO_KIND_MAPPING } from '@shell/config/types';
2
+ import { WORKLOAD_TYPES, WORKLOAD_TYPE_TO_KIND_MAPPING } from '@shell/config/types';
3
3
 
4
4
  export default class StatefulSet extends Workload {
5
5
  async rollBack(cluster, statefulSet, revision) {
@@ -21,16 +21,14 @@ export default class StatefulSet extends Workload {
21
21
  await this.rollBackWorkload(cluster, statefulSet, 'statefulsets', body);
22
22
  }
23
23
 
24
- // we need to provide a new pods getter for statefulsets because the relationship
25
- // done on the parent model "workload" is not correct
24
+ /**
25
+ * See fetchPods description for more info
26
+ */
26
27
  get pods() {
27
- const relationships = this.metadata?.relationships || [];
28
- const podRelationship = relationships.filter((relationship) => relationship.toType === POD)[0];
29
-
30
- if (podRelationship) {
31
- const pods = this.$getters['podsByNamespace'](this.metadata.namespace);
32
-
33
- return pods.filter((pod) => {
28
+ if (this.podMatchExpression) {
29
+ // Given https://github.com/rancher/dashboard/issues/7555 we want to avoid scenarios where we show pods that have an applicable label but aren't applicable (?!)
30
+ // super.pods is the pods that match the statefulsets podSelector, so start from that and then filter further by pod's owner
31
+ return super.pods.filter((pod) => {
34
32
  // a bit of a duplication of podRelationship, but always safe to check...
35
33
  if (pod.metadata?.ownerReferences?.length) {
36
34
  const ownerReferencesStatefulSet = pod.metadata?.ownerReferences?.find((own) => own.kind === WORKLOAD_TYPE_TO_KIND_MAPPING[WORKLOAD_TYPES.STATEFUL_SET]);
package/models/chart.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { compatibleVersionsFor, APP_UPGRADE_STATUS } from '@shell/store/catalog';
2
2
  import {
3
- REPO_TYPE, REPO, CHART, VERSION, _FLAGGED, HIDE_SIDE_NAV, CATEGORY, TAG
3
+ REPO_TYPE, REPO, CHART, VERSION, _FLAGGED, HIDE_SIDE_NAV, CATEGORY, TAG, DEPRECATED as DEPRECATED_QUERY
4
4
  } from '@shell/config/query-params';
5
5
  import { BLANK_CLUSTER } from '@shell/store/store-types.js';
6
6
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -29,6 +29,10 @@ export default class Chart extends SteveModel {
29
29
  [VERSION]: version,
30
30
  };
31
31
 
32
+ if (this.deprecated) {
33
+ out[DEPRECATED_QUERY] = true;
34
+ }
35
+
32
36
  if ( from ) {
33
37
  out[from] = _FLAGGED;
34
38
  }
@@ -1,7 +1,7 @@
1
1
  import { matching, convertSelectorObj } from '@shell/utils/selector';
2
2
  import isEmpty from 'lodash/isEmpty';
3
3
  import { escapeHtml } from '@shell/utils/string';
4
- import { FLEET, MANAGEMENT } from '@shell/config/types';
4
+ import { FLEET } from '@shell/config/types';
5
5
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
6
6
  import { addObject, addObjects, findBy } from '@shell/utils/array';
7
7
  import SteveModel from '@shell/plugins/steve/steve-class';
@@ -29,8 +29,19 @@ function normalizeStateCounts(data) {
29
29
  }
30
30
 
31
31
  export default class FleetApplication extends SteveModel {
32
- get currentUser() {
33
- return this.$rootGetters['auth/v3User'] || {};
32
+ async getCurrentUser() {
33
+ const user = this.$rootGetters['auth/v3User'];
34
+
35
+ if (user?.id) {
36
+ return user;
37
+ }
38
+
39
+ const res = await this.$dispatch('rancher/request', {
40
+ url: '/v3/users?me=true',
41
+ method: 'get',
42
+ }, { root: true });
43
+
44
+ return res?.data?.[0] || {};
34
45
  }
35
46
 
36
47
  pause() {
@@ -48,10 +59,6 @@ export default class FleetApplication extends SteveModel {
48
59
  delete this.metadata.labels[FLEET_ANNOTATIONS.CREATED_BY_USER_ID];
49
60
  }
50
61
 
51
- if (this.metadata?.labels?.[FLEET_ANNOTATIONS.CREATED_BY_USER_NAME]) {
52
- delete this.metadata.labels[FLEET_ANNOTATIONS.CREATED_BY_USER_NAME];
53
- }
54
-
55
62
  super.goToClone();
56
63
  }
57
64
 
@@ -144,11 +151,11 @@ export default class FleetApplication extends SteveModel {
144
151
  }
145
152
 
146
153
  statusResourceCountsForCluster(clusterId) {
147
- if (!this.targetClusters.some((c) => c.id === clusterId)) {
154
+ if (!(this.targetClusters || []).some((c) => c.id === clusterId)) {
148
155
  return {};
149
156
  }
150
157
 
151
- return this.status?.perClusterResourceCounts[clusterId] || { desiredReady: 0 };
158
+ return this.status?.perClusterResourceCounts?.[clusterId] || { desiredReady: 0 };
152
159
  }
153
160
 
154
161
  get resourcesStatuses() {
@@ -224,43 +231,6 @@ export default class FleetApplication extends SteveModel {
224
231
  return primaryDisplayStatusFromCount(resourceCounts) || STATES_ENUM.ACTIVE;
225
232
  }
226
233
 
227
- get authorId() {
228
- return this.metadata?.labels?.[FLEET_ANNOTATIONS.CREATED_BY_USER_ID];
229
- }
230
-
231
- get author() {
232
- if (this.authorId) {
233
- return this.$rootGetters['management/byId'](MANAGEMENT.USER, this.authorId);
234
- }
235
-
236
- return null;
237
- }
238
-
239
- get createdBy() {
240
- const displayName = this.metadata?.labels?.[FLEET_ANNOTATIONS.CREATED_BY_USER_NAME];
241
-
242
- if (!displayName) {
243
- return null;
244
- }
245
-
246
- return {
247
- displayName,
248
- location: !this.author ? null : {
249
- name: 'c-cluster-product-resource-id',
250
- params: {
251
- cluster: '_',
252
- product: 'auth',
253
- resource: MANAGEMENT.USER,
254
- id: this.author.id,
255
- }
256
- }
257
- };
258
- }
259
-
260
- get showCreatedBy() {
261
- return !!this.createdBy;
262
- }
263
-
264
234
  get clustersList() {
265
235
  return this.$getters['all'](FLEET.CLUSTER);
266
236
  }
@@ -1,7 +1,7 @@
1
1
  import { escapeHtml, ucFirst } from '@shell/utils/string';
2
2
  import SteveModel from '@shell/plugins/steve/steve-class';
3
3
  import { addObject, addObjects, findBy } from '@shell/utils/array';
4
- import { FLEET, MANAGEMENT } from '@shell/config/types';
4
+ import { FLEET } from '@shell/config/types';
5
5
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
6
6
  import { convertSelectorObj, matching } from '@shell/utils/selector';
7
7
 
@@ -129,41 +129,4 @@ export default class FleetBundle extends SteveModel {
129
129
  );
130
130
  }
131
131
  }
132
-
133
- get authorId() {
134
- return this.metadata?.labels?.[FLEET_ANNOTATIONS.CREATED_BY_USER_ID];
135
- }
136
-
137
- get author() {
138
- if (this.authorId) {
139
- return this.$rootGetters['management/byId'](MANAGEMENT.USER, this.authorId);
140
- }
141
-
142
- return null;
143
- }
144
-
145
- get createdBy() {
146
- const displayName = this.metadata?.labels?.[FLEET_ANNOTATIONS.CREATED_BY_USER_NAME];
147
-
148
- if (!displayName) {
149
- return null;
150
- }
151
-
152
- return {
153
- displayName,
154
- location: !this.author ? null : {
155
- name: 'c-cluster-product-resource-id',
156
- params: {
157
- cluster: '_',
158
- product: 'auth',
159
- resource: MANAGEMENT.USER,
160
- id: this.author.id,
161
- }
162
- }
163
- };
164
- }
165
-
166
- get showCreatedBy() {
167
- return !!this.createdBy;
168
- }
169
132
  }
@@ -209,4 +209,8 @@ export default class GitRepo extends FleetApplication {
209
209
  display: this.commitDisplay
210
210
  };
211
211
  }
212
+
213
+ get fullDetailPageOverride() {
214
+ return true;
215
+ }
212
216
  }
@@ -199,4 +199,8 @@ export default class HelmOp extends FleetApplication {
199
199
  get bundleDeployments() {
200
200
  return this.$getters['matching'](FLEET.BUNDLE_DEPLOYMENT, { [FLEET_ANNOTATIONS.HELM_NAME]: this.name });
201
201
  }
202
+
203
+ get fullDetailPageOverride() {
204
+ return true;
205
+ }
202
206
  }
@@ -179,4 +179,16 @@ export default class Project extends HybridModel {
179
179
  get confirmRemove() {
180
180
  return true;
181
181
  }
182
+
183
+ get glance() {
184
+ const glance = [...this._glance];
185
+
186
+ const namespaceIndex = glance.findIndex((item) => item.name === 'namespace');
187
+
188
+ if (namespaceIndex > -1) {
189
+ glance.splice(namespaceIndex, 1);
190
+ }
191
+
192
+ return glance;
193
+ }
182
194
  }
@@ -269,4 +269,34 @@ export default class Namespace extends SteveModel {
269
269
  get hideDetailLocation() {
270
270
  return !!this.$rootGetters['currentProduct'].hideNamespaceLocation;
271
271
  }
272
+
273
+ get glance() {
274
+ const glance = [...this._glance];
275
+
276
+ const namespaceIndex = glance.findIndex((item) => item.name === 'namespace');
277
+
278
+ if (namespaceIndex > -1) {
279
+ glance.splice(namespaceIndex, 1, this.projectGlance);
280
+ }
281
+
282
+ // projectGlance could be undefined
283
+ return glance.filter(Boolean);
284
+ }
285
+
286
+ get projectGlance() {
287
+ // Not all namespaces are in a project
288
+ if (!this.project) {
289
+ return undefined;
290
+ }
291
+
292
+ return {
293
+ name: 'project',
294
+ label: this.t('component.resource.detail.glance.project'),
295
+ formatter: 'Link',
296
+ formatterOpts: {
297
+ to: this.project.detailLocation, row: {}, options: { internal: true }
298
+ },
299
+ content: this.project.nameDisplay
300
+ };
301
+ }
272
302
  }
@@ -595,6 +595,9 @@ export default class Workload extends WorkloadService {
595
595
  return selector;
596
596
  }
597
597
 
598
+ /**
599
+ * Match Expression version of the podSelector
600
+ */
598
601
  get podMatchExpression() {
599
602
  return this.podSelector ? parse(this.podSelector) : null;
600
603
  }