@rancher/shell 3.0.5-rc.8 → 3.0.5

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 (199) 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 +147 -19
  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/PodSecurityAdmission.vue +2 -0
  16. package/components/PromptModal.vue +1 -1
  17. package/components/Resource/Detail/Card/__tests__/StateCard.test.ts +1 -0
  18. package/components/Resource/Detail/CopyToClipboard.vue +78 -0
  19. package/components/Resource/Detail/FetchLoader/__tests__/composables.test.ts +69 -0
  20. package/components/Resource/Detail/FetchLoader/composables.ts +27 -0
  21. package/components/Resource/Detail/Metadata/Annotations/__tests__/index.test.ts +1 -1
  22. package/components/Resource/Detail/Metadata/Annotations/index.vue +1 -1
  23. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +13 -61
  24. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/index.test.ts +33 -6
  25. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +24 -38
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +25 -5
  27. package/components/Resource/Detail/Metadata/KeyValue.vue +12 -23
  28. package/components/Resource/Detail/Metadata/KeyValueRow.vue +144 -0
  29. package/components/Resource/Detail/Metadata/Labels/__tests__/index.test.ts +1 -0
  30. package/components/Resource/Detail/Metadata/Labels/index.vue +1 -0
  31. package/components/Resource/Detail/Metadata/__tests__/KeyValue.test.ts +30 -32
  32. package/components/Resource/Detail/Metadata/__tests__/KeyValueRow.test.ts +108 -0
  33. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +0 -3
  34. package/components/Resource/Detail/Metadata/__tests__/index.test.ts +12 -5
  35. package/components/Resource/Detail/Metadata/composables.ts +1 -4
  36. package/components/Resource/Detail/Metadata/index.vue +1 -0
  37. package/components/Resource/Detail/Preview/Content.vue +63 -0
  38. package/components/Resource/Detail/Preview/Preview.vue +128 -0
  39. package/components/Resource/Detail/Preview/__tests__/Content.spec.ts +71 -0
  40. package/components/Resource/Detail/Preview/__tests__/Preview.spec.ts +121 -0
  41. package/components/Resource/Detail/ResourcePopover/ResourcePopoverCard.vue +141 -0
  42. package/components/Resource/Detail/ResourcePopover/__tests__/ResourcePopoverCard.test.ts +136 -0
  43. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +245 -0
  44. package/components/Resource/Detail/ResourcePopover/index.vue +226 -0
  45. package/components/Resource/Detail/SpacedRow.vue +1 -0
  46. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +0 -5
  47. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +1 -1
  48. package/components/Resource/Detail/TitleBar/composables.ts +1 -3
  49. package/components/Resource/Detail/TitleBar/index.vue +2 -29
  50. package/components/Resource/Detail/ViewOptions/composable.ts +9 -0
  51. package/components/Resource/Detail/ViewOptions/index.vue +41 -0
  52. package/components/Resource/Detail/__tests__/CopyToClipboard.spec.ts +82 -0
  53. package/components/ResourceDetail/Masthead/legacy.vue +0 -19
  54. package/components/ResourceDetail/index.vue +1 -26
  55. package/components/ResourceTable.vue +24 -0
  56. package/components/SortableTable/index.vue +7 -1
  57. package/components/SortableTable/paging.js +3 -0
  58. package/components/Tabbed/Tab.vue +43 -1
  59. package/components/Tabbed/index.vue +3 -1
  60. package/components/__tests__/Cron/CronExpressionEditor.test.ts +151 -0
  61. package/components/__tests__/Cron/CronExpressionEditorModal.test.ts +81 -0
  62. package/components/auth/login/saml.vue +86 -0
  63. package/components/form/LabeledSelect.vue +8 -8
  64. package/components/form/ProjectMemberEditor.vue +2 -0
  65. package/components/form/ResourceTabs/composable.ts +54 -0
  66. package/components/form/ResourceTabs/index.vue +10 -7
  67. package/components/form/Select.vue +13 -10
  68. package/components/form/__tests__/LabeledSelect.test.ts +133 -0
  69. package/components/form/__tests__/Select.test.ts +134 -0
  70. package/components/nav/Header.vue +6 -5
  71. package/composables/useExtensionManager.ts +17 -0
  72. package/config/home-links.js +12 -0
  73. package/config/labels-annotations.js +0 -1
  74. package/config/page-actions.js +0 -1
  75. package/config/product/explorer.js +3 -1
  76. package/config/product/fleet.js +2 -7
  77. package/config/product/manager.js +0 -5
  78. package/config/query-params.js +1 -0
  79. package/config/router/navigation-guards/clusters.js +2 -1
  80. package/config/router/navigation-guards/products.js +1 -1
  81. package/config/store.js +2 -0
  82. package/core/extension-manager-impl.js +518 -0
  83. package/core/plugins.js +35 -468
  84. package/core/types.ts +8 -2
  85. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +1 -0
  86. package/detail/catalog.cattle.io.app.vue +7 -4
  87. package/detail/fleet.cattle.io.bundle.vue +1 -5
  88. package/detail/fleet.cattle.io.cluster.vue +3 -2
  89. package/detail/fleet.cattle.io.gitrepo.vue +76 -49
  90. package/detail/fleet.cattle.io.helmop.vue +78 -49
  91. package/dialog/AddonConfigConfirmationDialog.vue +1 -1
  92. package/dialog/GenericPrompt.vue +1 -1
  93. package/dialog/ImportDialog.vue +9 -2
  94. package/dialog/InstallExtensionDialog.vue +18 -10
  95. package/dialog/SloDialog.vue +1 -1
  96. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +2 -1
  97. package/edit/__tests__/resources.cattle.io.restore.test.ts +106 -0
  98. package/edit/auth/oidc.vue +106 -6
  99. package/edit/auth/saml.vue +5 -5
  100. package/edit/cloudcredential.vue +31 -17
  101. package/edit/constraints.gatekeeper.sh.constraint/index.vue +10 -2
  102. package/edit/fleet.cattle.io.cluster.vue +19 -0
  103. package/edit/fleet.cattle.io.gitrepo.vue +23 -16
  104. package/edit/monitoring.coreos.com.alertmanagerconfig/index.vue +12 -11
  105. package/edit/monitoring.coreos.com.alertmanagerconfig/receiverConfig.vue +11 -1
  106. package/edit/provisioning.cattle.io.cluster/index.vue +14 -19
  107. package/edit/provisioning.cattle.io.cluster/rke2.vue +11 -3
  108. package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +1 -0
  109. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +1 -0
  110. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +1 -0
  111. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +1 -0
  112. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -0
  113. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +6 -0
  114. package/edit/resources.cattle.io.restore.vue +5 -8
  115. package/initialize/install-plugins.js +1 -3
  116. package/list/__tests__/workload.test.ts +1 -0
  117. package/list/workload.vue +8 -1
  118. package/machine-config/components/GCEImage.vue +6 -5
  119. package/machine-config/google.vue +11 -6
  120. package/mixins/__tests__/auth-config.test.ts +4 -6
  121. package/mixins/__tests__/chart.test.ts +139 -1
  122. package/mixins/auth-config.js +33 -10
  123. package/mixins/chart.js +58 -18
  124. package/models/__tests__/namespace.test.ts +69 -0
  125. package/models/apps.statefulset.js +8 -10
  126. package/models/chart.js +5 -1
  127. package/models/fleet-application.js +16 -46
  128. package/models/fleet.cattle.io.bundle.js +1 -38
  129. package/models/fleet.cattle.io.gitrepo.js +4 -0
  130. package/models/fleet.cattle.io.helmop.js +4 -0
  131. package/models/management.cattle.io.cluster.js +1 -1
  132. package/models/management.cattle.io.project.js +12 -0
  133. package/models/namespace.js +30 -0
  134. package/models/workload.js +4 -1
  135. package/package.json +10 -10
  136. package/pages/auth/login.vue +8 -3
  137. package/pages/auth/logout.vue +6 -5
  138. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +26 -11
  139. package/pages/c/_cluster/apps/charts/chart.vue +29 -20
  140. package/pages/c/_cluster/apps/charts/index.vue +1 -0
  141. package/pages/c/_cluster/apps/charts/install.vue +6 -5
  142. package/pages/c/_cluster/explorer/tools/__tests__/index.test.ts +102 -12
  143. package/pages/c/_cluster/explorer/tools/index.vue +145 -254
  144. package/pages/c/_cluster/manager/cloudCredential/index.vue +18 -1
  145. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +12 -2
  146. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +1 -1
  147. package/pages/c/_cluster/uiplugins/__tests__/index.spec.ts +318 -0
  148. package/pages/c/_cluster/uiplugins/index.vue +221 -363
  149. package/pages/home.vue +1 -9
  150. package/plugins/axios.js +3 -2
  151. package/plugins/dashboard-store/resource-class.js +49 -0
  152. package/plugins/ember-cookie.js +7 -3
  153. package/plugins/steve/subscribe.js +4 -2
  154. package/public/index.html +2 -1
  155. package/rancher-components/Card/Card.vue +1 -1
  156. package/rancher-components/Form/Checkbox/Checkbox.vue +1 -1
  157. package/rancher-components/Form/Radio/RadioButton.vue +1 -1
  158. package/rancher-components/Form/Radio/RadioGroup.vue +1 -1
  159. package/rancher-components/LabeledTooltip/LabeledTooltip.vue +1 -11
  160. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.test.ts +53 -0
  161. package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +65 -0
  162. package/rancher-components/Pill/RcCounterBadge/index.ts +1 -0
  163. package/rancher-components/Pill/RcCounterBadge/types.ts +7 -0
  164. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +1 -1
  165. package/rancher-components/Pill/RcStatusBadge/index.ts +1 -1
  166. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +3 -3
  167. package/rancher-components/Pill/RcStatusIndicator/types.ts +1 -1
  168. package/rancher-components/Pill/RcTag/RcTag.test.ts +64 -0
  169. package/rancher-components/Pill/RcTag/RcTag.vue +94 -0
  170. package/rancher-components/Pill/RcTag/index.ts +1 -0
  171. package/rancher-components/Pill/RcTag/types.ts +9 -0
  172. package/rancher-components/Pill/types.ts +1 -0
  173. package/rancher-components/RcItemCard/RcItemCard.vue +1 -0
  174. package/rancher-components/RcItemCard/RcItemCardAction.vue +12 -0
  175. package/scripts/test-plugins-build.sh +0 -1
  176. package/store/__tests__/catalog.test.ts +63 -0
  177. package/store/__tests__/cookies.test.ts +72 -0
  178. package/store/auth.js +33 -10
  179. package/store/catalog.js +2 -2
  180. package/store/cookies.ts +30 -0
  181. package/store/prefs.js +10 -5
  182. package/store/type-map.js +3 -15
  183. package/types/extension-manager.ts +26 -0
  184. package/types/shell/index.d.ts +123 -27
  185. package/utils/__tests__/product.test.ts +129 -0
  186. package/utils/__tests__/resource.test.ts +87 -0
  187. package/utils/alertmanagerconfig.js +2 -2
  188. package/utils/auth.js +4 -77
  189. package/utils/product.ts +39 -0
  190. package/utils/resource.ts +35 -0
  191. package/utils/select.js +0 -24
  192. package/utils/validators/formRules/__tests__/index.test.ts +3 -0
  193. package/utils/validators/formRules/index.ts +2 -1
  194. package/vue.config.js +1 -1
  195. package/components/Resource/Detail/Metadata/Rectangle.vue +0 -34
  196. package/components/Resource/Detail/Metadata/__tests__/Rectangle.test.ts +0 -24
  197. package/components/ResourceDetail/Masthead/__tests__/legacy.test.ts +0 -65
  198. package/utils/cookie-universal.js +0 -10
  199. /package/components/{ForceDirectedTreeChart.vue → ForceDirectedTreeChart/index.vue} +0 -0
@@ -624,6 +624,7 @@ export default {
624
624
  >
625
625
  <AppChartCardFooter
626
626
  :items="card.footerItems"
627
+ :clickable="true"
627
628
  @click:item="handleFooterItemClick"
628
629
  />
629
630
  </template>
@@ -629,19 +629,19 @@ export default {
629
629
  step1Description() {
630
630
  const descriptionKey = this.steps.find((s) => s.name === 'basics').descriptionKey;
631
631
 
632
- return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action, existing: !!this.existing }, '');
632
+ return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action.name, existing: !!this.existing }, '');
633
633
  },
634
634
 
635
635
  step2Description() {
636
636
  const descriptionKey = this.steps.find((s) => s.name === 'helmValues').descriptionKey;
637
637
 
638
- return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action, existing: !!this.existing }, '');
638
+ return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action.name, existing: !!this.existing }, '');
639
639
  },
640
640
 
641
641
  step3Description() {
642
642
  const descriptionKey = this.steps.find((s) => s.name === 'helmCli').descriptionKey;
643
643
 
644
- return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action, existing: !!this.existing }, '');
644
+ return this.$store.getters['i18n/withFallback'](descriptionKey, { action: this.action.name, existing: !!this.existing }, '');
645
645
  },
646
646
 
647
647
  steps() {
@@ -1299,7 +1299,8 @@ export default {
1299
1299
  :edit-first-step="true"
1300
1300
  :banner-title="stepperName"
1301
1301
  :banner-title-subtext="stepperSubtext"
1302
- :finish-mode="action"
1302
+ :finish-mode="action.name"
1303
+ :header-mode="action.tKey"
1303
1304
  class="wizard"
1304
1305
  :class="{'windowsIncompatible': windowsIncompatible}"
1305
1306
  @cancel="cancel"
@@ -1427,7 +1428,7 @@ export default {
1427
1428
  <Checkbox
1428
1429
  v-model:value="showCommandStep"
1429
1430
  class="mb-20"
1430
- :label="t('catalog.install.steps.helmCli.checkbox', { action, existing: !!existing })"
1431
+ :label="t('catalog.install.steps.helmCli.checkbox', { action: action.name, existing: !!existing })"
1431
1432
  />
1432
1433
 
1433
1434
  <Checkbox
@@ -2,23 +2,29 @@ import { clone } from '@shell/utils/object';
2
2
  import ClusterTools from '@shell/pages/c/_cluster/explorer/tools/index.vue';
3
3
  import { shallowMount } from '@vue/test-utils';
4
4
  import { MANAGEMENT } from '@shell/config/types';
5
+ import { RcItemCard } from '@components/RcItemCard';
6
+ import { APP_UPGRADE_STATUS } from '@shell/store/catalog';
5
7
 
6
8
  describe('page: cluster tools', () => {
7
9
  const mountOptions = {
8
10
  computed: {
9
- options: () => [
11
+ appChartCards: () => [
10
12
  {
11
- chart: {
12
- id: 'cluster/rancher-charts/rancher-alerting-drivers',
13
- iconName: 'icon',
14
- },
15
- app: {}
13
+ id: 'cluster/rancher-charts/rancher-alerting-drivers',
14
+ header: { title: { text: 'Rancher Alerting Drivers' } },
15
+ content: { text: 'Some description' },
16
+ image: { src: '' },
17
+ subHeaderItems: [],
18
+ installedApp: { spec: { chart: { metadata: { version: '1.0.0' } } } },
19
+ rawChart: { versions: [{ version: '1.0.0' }], blocked: false }
16
20
  }
17
21
  ]
18
22
  },
19
- global: {
23
+ methods: { getCardActions: (ClusterTools as any).methods.getCardActions },
24
+ global: {
20
25
  mocks: {
21
26
  $route: { query: {} },
27
+ t: (key: string) => key,
22
28
  $fetchState: {
23
29
  pending: false, error: true, timestamp: Date.now()
24
30
  },
@@ -36,7 +42,7 @@ describe('page: cluster tools', () => {
36
42
  }
37
43
  },
38
44
  currentCluster: { id: 'cluster', status: { provider: 'provider' } },
39
- 'i18n/t': jest.fn(),
45
+ 'i18n/t': (key: string) => key,
40
46
  }
41
47
  }
42
48
  },
@@ -44,10 +50,10 @@ describe('page: cluster tools', () => {
44
50
  };
45
51
 
46
52
  it('should show apps catalog', async() => {
47
- const wrapper = shallowMount(ClusterTools, mountOptions);
53
+ const wrapper = shallowMount(ClusterTools, { ...mountOptions, methods: { getCardActions: () => [] } });
48
54
 
49
55
  await (ClusterTools as any).fetch.call(wrapper.vm);
50
- const cards = wrapper.find('[data-testid^="cluster-tools-app"]');
56
+ const cards = wrapper.findComponent(RcItemCard);
51
57
 
52
58
  expect(cards.exists()).toBe(true);
53
59
  });
@@ -61,11 +67,95 @@ describe('page: cluster tools', () => {
61
67
  }
62
68
  };
63
69
 
64
- const wrapper = shallowMount(ClusterTools, options);
70
+ const wrapper = shallowMount(ClusterTools, { ...options, methods: { getCardActions: () => [] } });
65
71
 
66
72
  await (ClusterTools as any).fetch.call(wrapper.vm);
67
- const cards = wrapper.find('[data-testid^="cluster-tools-app"]');
73
+ const cards = wrapper.findComponent(RcItemCard);
68
74
 
69
75
  expect(cards.exists()).toBe(true);
70
76
  });
77
+
78
+ describe('getCardActions', () => {
79
+ it('should return "install" action for an uninstalled chart', () => {
80
+ const wrapper = shallowMount(ClusterTools, mountOptions);
81
+ const card = {
82
+ installedApp: null,
83
+ rawChart: { blocked: false }
84
+ };
85
+ const actions = wrapper.vm.getCardActions(card);
86
+
87
+ expect(actions).toHaveLength(1);
88
+ expect(actions[0]).toStrictEqual({
89
+ label: 'catalog.tools.action.install',
90
+ action: 'install',
91
+ icon: 'icon-plus',
92
+ enabled: true
93
+ });
94
+ });
95
+
96
+ it('should return all actions for an installed chart', () => {
97
+ const wrapper = shallowMount(ClusterTools, mountOptions);
98
+ const card = {
99
+ installedApp: {
100
+ spec: { chart: { metadata: { version: '1.0.1' } } },
101
+ upgradeAvailable: APP_UPGRADE_STATUS.SINGLE_UPGRADE
102
+ },
103
+ rawChart: { versions: [{ version: '1.0.2' }, { version: '1.0.1' }, { version: '1.0.0' }] }
104
+ };
105
+ const actions = wrapper.vm.getCardActions(card);
106
+
107
+ expect(actions).toHaveLength(5); // Upgrade, Edit, Downgrade, separator, Remove
108
+ expect(actions[0]).toStrictEqual({
109
+ label: 'catalog.tools.action.upgrade',
110
+ icon: 'icon-upgrade-alt',
111
+ action: 'upgrade'
112
+ });
113
+ expect(actions[1]).toStrictEqual({
114
+ label: 'catalog.tools.action.edit',
115
+ icon: 'icon-edit',
116
+ action: 'edit'
117
+ });
118
+ expect(actions[2]).toStrictEqual({
119
+ label: 'catalog.tools.action.downgrade',
120
+ icon: 'icon-downgrade-alt',
121
+ action: 'downgrade'
122
+ });
123
+ expect(actions[3]).toStrictEqual({ divider: true });
124
+ expect(actions[4]).toStrictEqual({
125
+ label: 'catalog.tools.action.remove',
126
+ icon: 'icon-delete',
127
+ action: 'remove'
128
+ });
129
+ });
130
+
131
+ it('should not show upgrade if not available', () => {
132
+ const wrapper = shallowMount(ClusterTools, mountOptions);
133
+ const card = {
134
+ installedApp: {
135
+ spec: { chart: { metadata: { version: '1.0.2' } } },
136
+ upgradeAvailable: APP_UPGRADE_STATUS.NO_UPGRADE
137
+ },
138
+ rawChart: { versions: [{ version: '1.0.2' }, { version: '1.0.1' }, { version: '1.0.0' }] }
139
+ };
140
+ const actions = wrapper.vm.getCardActions(card);
141
+
142
+ expect(actions.find((a) => a.action === 'upgrade')).toBeUndefined();
143
+ expect(actions).toHaveLength(4); // Edit, Downgrade, separator, Remove
144
+ });
145
+
146
+ it('should not show downgrade if not available', () => {
147
+ const wrapper = shallowMount(ClusterTools, mountOptions);
148
+ const card = {
149
+ installedApp: {
150
+ spec: { chart: { metadata: { version: '1.0.0' } } },
151
+ upgradeAvailable: APP_UPGRADE_STATUS.SINGLE_UPGRADE
152
+ },
153
+ rawChart: { versions: [{ version: '1.0.1' }, { version: '1.0.0' }] }
154
+ };
155
+ const actions = wrapper.vm.getCardActions(card);
156
+
157
+ expect(actions.find((a) => a.action === 'downgrade')).toBeUndefined();
158
+ expect(actions).toHaveLength(4); // Upgrade, Edit, separator, Remove
159
+ });
160
+ });
71
161
  });
@@ -2,18 +2,20 @@
2
2
  import { mapGetters } from 'vuex';
3
3
  import Loading from '@shell/components/Loading';
4
4
  import { _FLAGGED, DEPRECATED as DEPRECATED_QUERY, HIDDEN, FROM_TOOLS } from '@shell/config/query-params';
5
- import { filterAndArrangeCharts } from '@shell/store/catalog';
5
+ import { filterAndArrangeCharts, APP_UPGRADE_STATUS } from '@shell/store/catalog';
6
6
  import { CATALOG, NORMAN } from '@shell/config/types';
7
7
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
8
- import LazyImage from '@shell/components/LazyImage';
9
8
  import { isAlternate } from '@shell/utils/platform';
10
9
  import IconMessage from '@shell/components/IconMessage';
11
- import TypeDescription from '@shell/components/TypeDescription';
12
10
  import TabTitle from '@shell/components/TabTitle';
11
+ import { get } from '@shell/utils/object';
12
+ import { RcItemCard } from '@components/RcItemCard';
13
+ import AppChartCardSubHeader from '@shell/pages/c/_cluster/apps/charts/AppChartCardSubHeader';
14
+ import AppChartCardFooter from '@shell/pages/c/_cluster/apps/charts/AppChartCardFooter';
13
15
 
14
16
  export default {
15
17
  components: {
16
- LazyImage, Loading, IconMessage, TypeDescription, TabTitle
18
+ Loading, IconMessage, TabTitle, RcItemCard, AppChartCardSubHeader, AppChartCardFooter
17
19
  },
18
20
 
19
21
  async fetch() {
@@ -72,7 +74,7 @@ export default {
72
74
  return out;
73
75
  },
74
76
 
75
- options() {
77
+ appChartCards() {
76
78
  const clusterProvider = this.currentCluster.status.provider || 'other';
77
79
  const enabledCharts = (this.allCharts || []);
78
80
 
@@ -86,14 +88,28 @@ export default {
86
88
 
87
89
  charts = charts.filter((c) => c.sideLabel !== 'Experimental');
88
90
 
89
- const chartsWithApps = charts.map((chart) => {
90
- return {
91
- chart,
92
- app: this.installedAppForChart[chart.id],
91
+ return charts.map((chart) => {
92
+ const installedApp = this.installedAppForChart[chart.id];
93
+ const card = {
94
+ id: chart.id,
95
+ header: {
96
+ title: { text: chart.chartNameDisplay },
97
+ statuses: chart.cardContent.statuses
98
+ },
99
+ subHeaderItems: chart.cardContent.subHeaderItems.slice(0, 1),
100
+ footerItems: chart.deploysOnWindows ? [{
101
+ icon: 'icon-tag-alt',
102
+ iconTooltip: { key: 'generic.tags' },
103
+ labels: [this.t('catalog.charts.deploysOnWindows')],
104
+ }] : [],
105
+ image: { src: chart.versions[0].icon, alt: { text: this.t('catalog.charts.iconAlt', { app: get(chart, 'chartNameDisplay') }) } },
106
+ content: { text: chart.chartDescription },
107
+ rawChart: chart,
108
+ installedApp,
93
109
  };
94
- });
95
110
 
96
- return chartsWithApps;
111
+ return card;
112
+ });
97
113
  },
98
114
  },
99
115
 
@@ -114,6 +130,77 @@ export default {
114
130
  },
115
131
 
116
132
  methods: {
133
+ getCardActions(card) {
134
+ const { installedApp, rawChart } = card;
135
+
136
+ if (installedApp) {
137
+ const actions = [];
138
+ const upgradeAvailable = installedApp.upgradeAvailable === APP_UPGRADE_STATUS.SINGLE_UPGRADE;
139
+
140
+ if (upgradeAvailable) {
141
+ actions.push({
142
+ label: this.t('catalog.tools.action.upgrade'),
143
+ icon: 'icon-upgrade-alt',
144
+ action: 'upgrade',
145
+ });
146
+ }
147
+
148
+ actions.push({
149
+ label: this.t('catalog.tools.action.edit'),
150
+ icon: 'icon-edit',
151
+ action: 'edit',
152
+ });
153
+
154
+ const currentVersion = installedApp.spec.chart.metadata.version;
155
+ const versions = rawChart.versions;
156
+ const currentIndex = versions.findIndex((v) => v.version === currentVersion);
157
+
158
+ if (currentIndex !== -1 && currentIndex < versions.length - 1) {
159
+ actions.push({
160
+ label: this.t('catalog.tools.action.downgrade'),
161
+ icon: 'icon-downgrade-alt',
162
+ action: 'downgrade',
163
+ });
164
+ }
165
+
166
+ actions.push({ divider: true });
167
+
168
+ actions.push({
169
+ label: this.t('catalog.tools.action.remove'),
170
+ icon: 'icon-delete',
171
+ action: 'remove',
172
+ });
173
+
174
+ return actions;
175
+ }
176
+
177
+ return [
178
+ {
179
+ label: this.t('catalog.tools.action.install'),
180
+ action: 'install',
181
+ icon: 'icon-plus',
182
+ enabled: !rawChart.blocked
183
+ }
184
+ ];
185
+ },
186
+ upgrade(app, chart) {
187
+ const latestVersion = chart.versions[0].version;
188
+
189
+ this.edit(app, latestVersion);
190
+ },
191
+
192
+ downgrade(app, chart) {
193
+ const currentVersion = app.spec.chart.metadata.version;
194
+ const versions = chart.versions;
195
+ const currentIndex = versions.findIndex((v) => v.version === currentVersion);
196
+
197
+ if (currentIndex !== -1 && currentIndex < versions.length - 1) {
198
+ const downgradeVersion = versions[currentIndex + 1].version;
199
+
200
+ this.edit(app, downgradeVersion);
201
+ }
202
+ },
203
+
117
204
  edit(app, version) {
118
205
  app.goToUpgrade(version, true);
119
206
  },
@@ -132,260 +219,49 @@ export default {
132
219
  install(chart) {
133
220
  chart.goToInstall(FROM_TOOLS);
134
221
  },
135
-
136
- openV1Tool(id) {
137
- const cluster = this.$store.getters['currentCluster'];
138
- const route = {
139
- name: 'c-cluster-explorer-tools-pages-page',
140
- params: {
141
- cluster: cluster.id,
142
- product: 'explorer',
143
- page: id,
144
- }
145
- };
146
-
147
- this.$router.replace(route);
148
- },
149
222
  }
150
223
  };
151
224
  </script>
152
225
 
153
- <style lang="scss" scoped>
154
- $margin: 10px;
155
- $logo: 50px;
156
-
157
- .grid {
158
- display: flex;
159
- justify-content: flex-start;
160
- flex-wrap: wrap;
161
- margin: 0 -1*$margin;
162
-
163
- @media only screen and (min-width: map-get($breakpoints, '--viewport-4')) {
164
- .item {
165
- width: 100%;
166
- }
167
- }
168
- @media only screen and (min-width: map-get($breakpoints, '--viewport-7')) {
169
- .item {
170
- width: 100%;
171
- }
172
- }
173
- @media only screen and (min-width: map-get($breakpoints, '--viewport-9')) {
174
- .item {
175
- width: calc(50% - 2 * #{$margin});
176
- }
177
- }
178
- @media only screen and (min-width: map-get($breakpoints, '--viewport-12')) {
179
- .item {
180
- width: calc(33.33333% - 2 * #{$margin});
181
- }
182
- }
183
-
184
- .item {
185
- display: grid;
186
- grid-template-areas: "logo name-version name-version"
187
- "description description description"
188
- "state state action";
189
- grid-template-columns: $logo auto min-content;
190
- grid-template-rows: 50px 55px 35px;
191
- row-gap: $margin;
192
- column-gap: $margin;
193
-
194
- margin: $margin;
195
- padding: $margin;
196
- position: relative;
197
- border: 1px solid var(--border);
198
- border-radius: calc( 1.5 * var(--border-radius));
199
-
200
- .logo {
201
- grid-area: logo;
202
- text-align: center;
203
- width: $logo;
204
- height: $logo;
205
- border-radius: calc(2 * var(--border-radius));
206
- overflow: hidden;
207
- background-color: white;
208
-
209
- img {
210
- width: $logo - 4px;
211
- height: $logo - 4px;
212
- object-fit: contain;
213
- position: relative;
214
- top: 2px;
215
- }
216
-
217
- > i {
218
- background-color: var(--box-bg);
219
- border-radius: 50%;
220
- font-size: 32px;
221
- line-height: 50px;
222
- width: 50px;
223
- }
224
- }
225
-
226
- .name-version {
227
- grid-area: name-version;
228
- padding: 10px 0 0 0;
229
- }
230
-
231
- .name {
232
- white-space: nowrap;
233
- overflow: hidden;
234
- text-overflow: ellipsis;
235
- margin: 0;
236
- }
237
-
238
- .os-label {
239
- position: absolute;
240
- top: 10px;
241
- right: 10px;
242
- padding: 3px;
243
- font-size: 12px;
244
- line-height: 12px;
245
- background-color: var(--primary);
246
- color: var(--primary-text);
247
- }
248
-
249
- .version {
250
- color: var(--muted);
251
- white-space: nowrap;
252
- overflow: hidden;
253
- text-overflow: ellipsis;
254
- font-size: 0.9em;
255
- margin-top: 4px;
256
- }
257
-
258
- .description {
259
- grid-area: description;
260
- }
261
-
262
- .description-content {
263
- display: -webkit-box;
264
- -webkit-box-orient: vertical;
265
- -webkit-line-clamp: 3;
266
- line-clamp: 3;
267
- overflow: hidden;
268
- text-overflow: ellipsis;
269
- color: var(--text-muted);
270
- }
271
-
272
- .state {
273
- grid-area: state;
274
- }
275
-
276
- .action {
277
- grid-area: action;
278
- white-space: nowrap;
279
-
280
- button {
281
- height: 30px;
282
- }
283
- }
284
- }
285
- }
286
- </style>
287
-
288
226
  <template>
289
227
  <Loading v-if="$fetchState.pending" />
290
- <div v-else-if="options.length">
291
- <h1>
228
+ <div v-else-if="appChartCards.length">
229
+ <h1 class="mmb-6">
292
230
  <TabTitle>{{ t('catalog.tools.header') }}</TabTitle>
293
231
  </h1>
294
- <TypeDescription
295
- resource="chart"
296
- />
297
232
 
298
- <div class="grid">
299
- <div
300
- v-for="opt in options"
301
- :key="opt.chart.id"
302
- class="item"
303
- :data-testid="`cluster-tools-app-${opt.chart.id}`"
233
+ <div
234
+ class="tools-app-chart-cards"
235
+ data-testid="tools-app-chart-cards"
236
+ >
237
+ <rc-item-card
238
+ v-for="card in appChartCards"
239
+ :id="card.id"
240
+ :key="card.id"
241
+ :header="card.header"
242
+ :image="card.image"
243
+ :content="card.content"
244
+ :actions="getCardActions(card)"
245
+ :class="{ 'single-card': appChartCards.length === 1 }"
246
+ @upgrade="() => upgrade(card.installedApp, card.rawChart)"
247
+ @downgrade="() => downgrade(card.installedApp, card.rawChart)"
248
+ @edit="() => edit(card.installedApp)"
249
+ @remove="(payload) => remove(card.installedApp, payload.event)"
250
+ @install="() => install(card.rawChart)"
304
251
  >
305
- <div
306
- class="logo"
252
+ <template
253
+ v-once
254
+ #item-card-sub-header
307
255
  >
308
- <i
309
- v-if="opt.chart.iconName"
310
- class="icon"
311
- :class="opt.chart.iconName"
312
- :alt="t('catalog.tools.iconAlt', { app: opt.chart.chartNameDisplay })"
313
- />
314
- <LazyImage
315
- v-else
316
- :alt="t('catalog.tools.iconAlt', { app: opt.chart.chartNameDisplay })"
317
- :src="opt.chart.icon"
318
- />
319
- </div>
320
- <div class="name-version">
321
- <div>
322
- <h3 class="name">
323
- {{ opt.chart.chartNameDisplay }}
324
- </h3>
325
- <label
326
- v-if="opt.chart.deploysOnWindows"
327
- class="os-label"
328
- >{{ t('catalog.charts.deploysOnWindows') }}</label>
329
- </div>
330
- <div class="version">
331
- <template v-if="opt.app && opt.app.upgradeAvailableVersion">
332
- v{{ opt.app.currentVersion }} <b><i class="icon icon-chevron-right" /> v{{ opt.app.upgradeAvailableVersion }}</b>
333
- </template>
334
- <template v-else-if="opt.app">
335
- v{{ opt.app.currentVersion }}
336
- </template>
337
- <template v-else-if="opt.chart.versions.length">
338
- v{{ opt.chart.versions[0].version }}
339
- </template>
340
- </div>
341
- </div>
342
- <div class="description">
343
- <div
344
- v-clean-html="opt.chart.chartDescription"
345
- class="description-content"
346
- />
347
- </div>
348
- <div class="action">
349
- <template v-if="opt.blocked">
350
- <button
351
- v-clean-html="t('catalog.tools.action.install')"
352
- role="button"
353
- :aria-label="t('catalog.tools.action.install')"
354
- disabled="true"
355
- class="btn btn-sm role-primary"
356
- />
357
- </template>
358
- <template v-else-if="opt.app">
359
- <button
360
- class="btn btn-sm role-secondary"
361
- role="button"
362
- :aria-label="t('catalog.tools.action.remove')"
363
- @click="remove(opt.app, $event)"
364
- >
365
- <i
366
- class="icon icon-delete icon-lg"
367
- :alt="t('catalog.tools.action.remove')"
368
- />
369
- </button>
370
- <button
371
- v-clean-html="t('catalog.tools.action.edit')"
372
- role="button"
373
- :aria-label="t('catalog.tools.action.edit')"
374
- class="btn btn-sm role-secondary"
375
- @click="edit(opt.app)"
376
- />
377
- </template>
378
- <template v-else>
379
- <button
380
- v-clean-html="t('catalog.tools.action.install')"
381
- role="button"
382
- :aria-label="t('catalog.tools.action.install')"
383
- class="btn btn-sm role-primary"
384
- @click="install(opt.chart)"
385
- />
386
- </template>
387
- </div>
388
- </div>
256
+ <AppChartCardSubHeader :items="card.subHeaderItems" />
257
+ </template>
258
+ <template
259
+ v-once
260
+ #item-card-footer
261
+ >
262
+ <AppChartCardFooter :items="card.footerItems" />
263
+ </template>
264
+ </rc-item-card>
389
265
  </div>
390
266
  </div>
391
267
  <div v-else>
@@ -395,3 +271,18 @@ export default {
395
271
  />
396
272
  </div>
397
273
  </template>
274
+
275
+ <style lang="scss" scoped>
276
+ .tools-app-chart-cards {
277
+ display: grid;
278
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
279
+ grid-gap: var(--gap-md);
280
+ width: 100%;
281
+ height: max-content;
282
+ overflow: hidden;
283
+
284
+ .single-card {
285
+ max-width: 500px;
286
+ }
287
+ }
288
+ </style>