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

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 (177) hide show
  1. package/assets/brand/classic/metadata.json +3 -0
  2. package/assets/styles/app.scss +1 -0
  3. package/assets/styles/base/_color.scss +16 -0
  4. package/assets/styles/base/_helpers.scss +10 -0
  5. package/assets/styles/base/_variables.scss +1 -1
  6. package/assets/styles/fonts/_icons.scss +1 -32
  7. package/assets/styles/global/_layout.scss +1 -1
  8. package/assets/styles/themes/_dark.scss +262 -260
  9. package/assets/styles/themes/_light.scss +538 -515
  10. package/assets/styles/themes/_modern.scss +914 -0
  11. package/assets/translations/en-us.yaml +84 -25
  12. package/chart/__tests__/S3.test.ts +2 -1
  13. package/cloud-credential/generic.vue +18 -10
  14. package/cloud-credential/harvester.vue +1 -9
  15. package/components/AdvancedSection.vue +8 -0
  16. package/components/ChartReadme.vue +17 -7
  17. package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +1 -26
  18. package/components/Drawer/ResourceDetailDrawer/composables.ts +0 -23
  19. package/components/Drawer/ResourceDetailDrawer/index.vue +17 -4
  20. package/components/InstallHelmCharts.vue +656 -0
  21. package/components/LazyImage.vue +60 -4
  22. package/components/LocaleSelector.vue +7 -2
  23. package/components/Markdown.vue +4 -0
  24. package/components/Resource/Detail/Masthead/composable.ts +16 -0
  25. package/components/Resource/Detail/Masthead/index.vue +37 -0
  26. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -5
  27. package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
  28. package/components/Resource/Detail/Metadata/composables.ts +9 -7
  29. package/components/Resource/Detail/Metadata/index.vue +17 -2
  30. package/components/Resource/Detail/Page.vue +35 -21
  31. package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
  32. package/components/Resource/Detail/TitleBar/composables.ts +2 -3
  33. package/components/Resource/Detail/TitleBar/index.vue +10 -1
  34. package/components/ResourceDetail/index.vue +569 -74
  35. package/components/SlideInPanelManager.vue +10 -3
  36. package/components/SortableTable/index.vue +4 -4
  37. package/components/Tabbed/index.vue +29 -3
  38. package/components/__tests__/LazyImage.spec.ts +121 -0
  39. package/components/fleet/FleetStatus.vue +4 -0
  40. package/components/form/ClusterAppearance.vue +5 -0
  41. package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
  42. package/components/form/ProjectMemberEditor.vue +1 -1
  43. package/components/form/ResourceLabeledSelect.vue +19 -6
  44. package/components/form/ResourceTabs/index.vue +20 -0
  45. package/components/form/SecretSelector.vue +9 -0
  46. package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
  47. package/components/formatter/FleetApplicationSource.vue +25 -17
  48. package/components/nav/Favorite.vue +4 -0
  49. package/components/nav/NotificationCenter/Notification.vue +1 -27
  50. package/components/nav/WindowManager/index.vue +3 -3
  51. package/config/labels-annotations.js +1 -2
  52. package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
  53. package/detail/__tests__/workload.test.ts +164 -0
  54. package/detail/configmap.vue +33 -75
  55. package/detail/projectsecret.vue +11 -0
  56. package/detail/provisioning.cattle.io.cluster.vue +350 -324
  57. package/detail/secret.vue +49 -308
  58. package/detail/workload/index.vue +38 -21
  59. package/dialog/InstallExtensionDialog.vue +8 -5
  60. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  61. package/edit/fleet.cattle.io.gitrepo.vue +5 -6
  62. package/edit/fleet.cattle.io.helmop.vue +78 -56
  63. package/edit/logging.banzaicloud.io.output/index.vue +1 -1
  64. package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
  65. package/edit/networking.k8s.io.ingress/Certificate.vue +9 -11
  66. package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
  67. package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
  68. package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
  69. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
  70. package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
  71. package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
  72. package/edit/provisioning.cattle.io.cluster/rke2.vue +21 -13
  73. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
  74. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
  75. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
  76. package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
  77. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
  78. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +30 -31
  79. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
  80. package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
  81. package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
  82. package/edit/workload/index.vue +5 -14
  83. package/list/provisioning.cattle.io.cluster.vue +1 -69
  84. package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
  85. package/machine-config/google.vue +9 -1
  86. package/machine-config/vmwarevsphere.vue +7 -17
  87. package/mixins/chart.js +0 -2
  88. package/mixins/resource-fetch-api-pagination.js +3 -4
  89. package/models/__tests__/chart.test.ts +111 -80
  90. package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
  91. package/models/__tests__/node.test.ts +7 -63
  92. package/models/catalog.cattle.io.app.js +1 -1
  93. package/models/catalog.cattle.io.operation.js +1 -1
  94. package/models/chart.js +36 -20
  95. package/models/cloudcredential.js +2 -163
  96. package/models/cluster/node.js +7 -7
  97. package/models/cluster.x-k8s.io.machine.js +3 -3
  98. package/models/compliance.cattle.io.clusterscan.js +2 -2
  99. package/models/configmap.js +4 -0
  100. package/models/constraints.gatekeeper.sh.constraint.js +1 -1
  101. package/models/fleet-application.js +0 -17
  102. package/models/fleet.cattle.io.gitrepo.js +15 -1
  103. package/models/fleet.cattle.io.helmop.js +26 -22
  104. package/models/management.cattle.io.setting.js +4 -0
  105. package/models/persistentvolumeclaim.js +1 -1
  106. package/models/pod.js +2 -2
  107. package/models/provisioning.cattle.io.cluster.js +16 -40
  108. package/models/rke.cattle.io.etcdsnapshot.js +1 -1
  109. package/models/secret.js +4 -0
  110. package/models/storage.k8s.io.storageclass.js +2 -2
  111. package/models/workload.js +3 -3
  112. package/package.json +11 -10
  113. package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
  114. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
  115. package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
  116. package/pages/c/_cluster/apps/charts/chart.vue +422 -174
  117. package/pages/c/_cluster/apps/charts/install.vue +1 -1
  118. package/pages/c/_cluster/explorer/projectsecret.vue +3 -13
  119. package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
  120. package/pages/c/_cluster/fleet/index.vue +103 -44
  121. package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
  122. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
  123. package/pages/c/_cluster/uiplugins/index.vue +36 -25
  124. package/plugins/dashboard-store/actions.js +42 -22
  125. package/plugins/dashboard-store/resource-class.js +31 -0
  126. package/plugins/steve/__tests__/getters.test.ts +1 -1
  127. package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
  128. package/plugins/steve/getters.js +8 -2
  129. package/plugins/steve/resourceWatcher.js +10 -3
  130. package/plugins/steve/subscribe.js +192 -19
  131. package/plugins/steve/worker/web-worker.advanced.js +2 -0
  132. package/rancher-components/Card/Card.vue +0 -18
  133. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
  134. package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
  135. package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
  136. package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
  137. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
  138. package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
  139. package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
  140. package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
  141. package/rancher-components/Pill/types.ts +2 -0
  142. package/rancher-components/RcButton/RcButton.vue +1 -1
  143. package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
  144. package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
  145. package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
  146. package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
  147. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
  148. package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
  149. package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
  150. package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
  151. package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
  152. package/store/__tests__/catalog.test.ts +93 -1
  153. package/store/aws.js +19 -8
  154. package/store/catalog.js +8 -3
  155. package/types/resources/settings.d.ts +1 -1
  156. package/types/shell/index.d.ts +28 -28
  157. package/types/uiplugins.ts +73 -0
  158. package/utils/__tests__/back-off.test.ts +354 -0
  159. package/utils/__tests__/kontainer.test.ts +19 -0
  160. package/utils/__tests__/uiplugins.test.ts +84 -0
  161. package/utils/back-off.ts +176 -0
  162. package/utils/dynamic-importer.js +8 -0
  163. package/utils/kontainer.ts +3 -5
  164. package/utils/style.ts +3 -0
  165. package/utils/uiplugins.ts +29 -2
  166. package/utils/validators/__tests__/setting.test.js +92 -0
  167. package/utils/validators/formRules/__tests__/index.test.ts +88 -7
  168. package/utils/validators/formRules/index.ts +83 -8
  169. package/utils/validators/setting.js +17 -0
  170. package/cloud-credential/__tests__/harvester.test.ts +0 -18
  171. package/components/ResourceDetail/__tests__/index.test.ts +0 -135
  172. package/components/ResourceDetail/legacy.vue +0 -562
  173. package/components/formatter/CloudCredExpired.vue +0 -69
  174. package/pages/explorer/resource/detail/configmap.vue +0 -42
  175. package/pages/explorer/resource/detail/projectsecret.vue +0 -9
  176. package/pages/explorer/resource/detail/secret.vue +0 -63
  177. package/utils/aws.js +0 -0
@@ -0,0 +1,224 @@
1
+ import HelmOp from '@shell/models/fleet.cattle.io.helmop.js';
2
+
3
+ describe('class HelmOp', () => {
4
+ let instance;
5
+
6
+ describe('source getter', () => {
7
+ it('should return correct source for SOURCE_TYPE.REPO (HTTPS)', () => {
8
+ instance = new HelmOp({
9
+ spec: {
10
+ helm: {
11
+ repo: 'https://charts.rancher.io/fleet',
12
+ chart: 'fleet-agent'
13
+ }
14
+ }
15
+ });
16
+
17
+ const source = instance.source;
18
+
19
+ expect(source.value).toBe('https://charts.rancher.io/fleet');
20
+ expect(source.display).toBe('charts.rancher.io/fleet');
21
+ expect(source.icon).toBe('icon icon-application');
22
+ expect(source.showLink).toBe(true);
23
+ });
24
+
25
+ it('should return correct source for SOURCE_TYPE.REPO (GitHub HTTPS .git)', () => {
26
+ instance = new HelmOp({
27
+ spec: {
28
+ helm: {
29
+ repo: 'https://github.com/rancher/fleet.git',
30
+ chart: 'fleet'
31
+ }
32
+ }
33
+ });
34
+
35
+ const source = instance.source;
36
+
37
+ expect(source.value).toBe('https://github.com/rancher/fleet.git');
38
+ expect(source.display).toBe('rancher/fleet');
39
+ expect(source.icon).toBe('icon icon-application');
40
+ expect(source.showLink).toBe(true);
41
+ });
42
+
43
+ it('should return correct source for SOURCE_TYPE.REPO (GitHub SSH)', () => {
44
+ instance = new HelmOp({
45
+ spec: {
46
+ helm: {
47
+ repo: 'git@github.com:rancher/fleet.git',
48
+ chart: 'fleet'
49
+ }
50
+ }
51
+ });
52
+
53
+ const source = instance.source;
54
+
55
+ expect(source.value).toBe('https://github.com/rancher/fleet');
56
+ expect(source.display).toBe('rancher/fleet');
57
+ expect(source.icon).toBe('icon icon-application');
58
+ expect(source.showLink).toBe(true);
59
+ });
60
+
61
+ it('should return correct source for SOURCE_TYPE.OCI', () => {
62
+ instance = new HelmOp({ spec: { helm: { repo: 'oci://ghcr.io/rancher/some-chart' } } });
63
+
64
+ const source = instance.source;
65
+
66
+ expect(source.value).toBe('oci://ghcr.io');
67
+ expect(source.display).toBe('oci://ghcr.io');
68
+ expect(source.icon).toBe('icon icon-application');
69
+ expect(source.showLink).toBe(false);
70
+ });
71
+
72
+ it('should return correct source for SOURCE_TYPE.TARBALL', () => {
73
+ instance = new HelmOp({ spec: { helm: { chart: 'https://github.com/rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz' } } });
74
+
75
+ const source = instance.source;
76
+
77
+ expect(source.value).toBe('https://github.com/rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz');
78
+ expect(source.display).toBe('rancher/fleet-helm-charts/releases/download/fleet-0.12.1-beta.2/fleet-0.12.1-beta.2.tgz');
79
+ expect(source.icon).toBe('icon icon-application');
80
+ expect(source.showLink).toBe(true);
81
+ });
82
+
83
+ it('should handle missing helm spec gracefully', () => {
84
+ instance = new HelmOp({ spec: {} });
85
+ const source = instance.source;
86
+
87
+ expect(source.value).toBe('');
88
+ expect(source.display).toBeNull();
89
+ expect(source.icon).toBe('icon icon-application');
90
+ expect(source.showLink).toBe(false);
91
+ });
92
+ });
93
+
94
+ describe('sourceSub getter', () => {
95
+ it('should display chart name and desired version when both are present (SOURCE_TYPE.REPO)', () => {
96
+ instance = new HelmOp({
97
+ spec: {
98
+ helm: {
99
+ repo: 'https://charts.rancher.io/fleet',
100
+ chart: 'fleet-agent',
101
+ version: '0.12.x'
102
+ }
103
+ }
104
+ });
105
+
106
+ const sourceSub = instance.sourceSub;
107
+
108
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.x');
109
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.x');
110
+ });
111
+
112
+ it('should display chart name and desired version when both are present (SOURCE_TYPE.OCI)', () => {
113
+ instance = new HelmOp({
114
+ spec: {
115
+ helm: {
116
+ repo: 'oci://ghcr.io/rancher/some-chart',
117
+ version: '1.0.0'
118
+ }
119
+ }
120
+ });
121
+
122
+ const sourceSub = instance.sourceSub;
123
+
124
+ expect(sourceSub.value).toBe('rancher/some-chart : 1.0.0');
125
+ expect(sourceSub.display).toBe('rancher/some-chart : 1.0.0');
126
+ });
127
+
128
+ it('should display only installed version when only it is present', () => {
129
+ instance = new HelmOp({
130
+ status: { version: '0.12.3' },
131
+ spec: { helm: {} }
132
+ });
133
+
134
+ const sourceSub = instance.sourceSub;
135
+
136
+ expect(sourceSub.value).toBe('0.12.3');
137
+ expect(sourceSub.display).toBe('0.12.3');
138
+ });
139
+
140
+ it('should display semantic version when installed version is missing', () => {
141
+ instance = new HelmOp({
142
+ spec: {
143
+ helm: {
144
+ version: '0.12.x',
145
+ chart: 'test-chart'
146
+ }
147
+ }
148
+ });
149
+
150
+ const sourceSub = instance.sourceSub;
151
+
152
+ expect(sourceSub.value).toBe('0.12.x');
153
+ expect(sourceSub.display).toBe('0.12.x');
154
+ });
155
+
156
+ it('should display "semantic -> installed" when both versions are present (no chart)', () => {
157
+ instance = new HelmOp({
158
+ spec: { helm: { version: '0.12.x' } },
159
+ status: { version: '0.12.5' }
160
+ });
161
+
162
+ const sourceSub = instance.sourceSub;
163
+
164
+ expect(sourceSub.value).toBe('0.12.x -> 0.12.5');
165
+ expect(sourceSub.display).toBe('0.12.x -> 0.12.5');
166
+ });
167
+
168
+ it('should display chart and "semantic -> installed" when all are present', () => {
169
+ instance = new HelmOp({
170
+ spec: {
171
+ helm: {
172
+ repo: 'https://charts.rancher.io/fleet',
173
+ chart: 'fleet-agent',
174
+ version: '0.12.x'
175
+ }
176
+ },
177
+ status: { version: '0.12.5' }
178
+ });
179
+
180
+ const sourceSub = instance.sourceSub;
181
+
182
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.x -> 0.12.5');
183
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.x -> 0.12.5');
184
+ });
185
+
186
+ it('should display chart and only semantic version when all are present but semantic version is equal to installed version (no duplicate info)', () => {
187
+ instance = new HelmOp({
188
+ spec: {
189
+ helm: {
190
+ repo: 'https://charts.rancher.io/fleet',
191
+ chart: 'fleet-agent',
192
+ version: '0.12.3'
193
+ }
194
+ },
195
+ status: { version: '0.12.3' }
196
+ });
197
+
198
+ const sourceSub = instance.sourceSub;
199
+
200
+ expect(sourceSub.value).toBe('fleet-agent : 0.12.3');
201
+ expect(sourceSub.display).toBe('fleet-agent : 0.12.3');
202
+ });
203
+
204
+ it('should return empty string when no version or chart information is available', () => {
205
+ instance = new HelmOp({
206
+ spec: { helm: {} },
207
+ status: {}
208
+ });
209
+
210
+ const sourceSub = instance.sourceSub;
211
+
212
+ expect(sourceSub.value).toBe('');
213
+ expect(sourceSub.display).toBe('');
214
+ });
215
+
216
+ it('should correctly handle missing helm spec', () => {
217
+ instance = new HelmOp({ spec: {}, status: {} });
218
+ const sourceSub = instance.sourceSub;
219
+
220
+ expect(sourceSub.value).toBe('');
221
+ expect(sourceSub.display).toBe('');
222
+ });
223
+ });
224
+ });
@@ -1,74 +1,18 @@
1
- import Node from '@shell/models/management.cattle.io.node';
1
+ import Node from '@shell/models/cluster/node';
2
2
 
3
3
  describe('class Node', () => {
4
- const foo = 'foo';
5
- const bar = 'bar';
6
- const t = jest.fn(() => bar);
7
- const ctx = { rootGetters: { 'i18n/t': t } };
8
-
9
4
  const resetMocks = () => {
10
5
  // Clear all mock function calls:
11
6
  jest.clearAllMocks();
12
7
  };
13
8
 
14
- it('should not return addresses if they are not present in the resource status', () => {
15
- const node = new Node({ status: {} });
9
+ it.each([
10
+ ['1200', 1200],
11
+ ['1k', 1000]
12
+ ])('given %p status pod capacity value from the backend, should parse the value correctly as %p', (value, result) => {
13
+ const node = new Node({ status: { capacity: { pods: value } } });
16
14
 
17
- expect(node.addresses).toStrictEqual([]);
15
+ expect(node.podCapacity).toStrictEqual(result);
18
16
  resetMocks();
19
17
  });
20
-
21
- describe('should return addresses', () => {
22
- const addresses = [foo];
23
-
24
- it('if they are present directly on the resource status', () => {
25
- const node = new Node({ status: { addresses } });
26
-
27
- expect(node.addresses).toStrictEqual(addresses);
28
- });
29
- });
30
-
31
- describe('should return an internalIp', () => {
32
- const addresses = [{ type: 'InternalIP', address: foo }];
33
-
34
- it('if addresses includes an object with an appropriate type and address', () => {
35
- const node = new Node({ status: { addresses } });
36
-
37
- expect(node.internalIp).toStrictEqual(foo);
38
- });
39
- });
40
-
41
- describe('should return an externalIp', () => {
42
- const addresses = [{ type: 'ExternalIP', address: foo }];
43
-
44
- it('if addresses includes an object with an appropriate type and address', () => {
45
- const node = new Node({ status: { addresses } });
46
-
47
- expect(node.externalIp).toStrictEqual(foo);
48
- });
49
- it('if internalNodeStatus.addresses includes an object with an appropriate type and address', () => {
50
- const node = new Node({ status: { internalNodeStatus: { addresses } } });
51
-
52
- expect(node.externalIp).toStrictEqual(foo);
53
- });
54
- });
55
-
56
- describe('should return an appropriate message', () => {
57
- it('if there is no internalIp to display', () => {
58
- const node = new Node({ status: {} }, ctx);
59
-
60
- expect(node.internalIp).toStrictEqual(bar);
61
- expect(t).toHaveBeenCalledTimes(1);
62
- expect(t).toHaveBeenCalledWith('generic.none');
63
- resetMocks();
64
- });
65
- it('if there is no externalIp to display', () => {
66
- const node = new Node({ status: {} }, ctx);
67
-
68
- expect(node.externalIp).toStrictEqual(bar);
69
- expect(t).toHaveBeenCalledTimes(1);
70
- expect(t).toHaveBeenCalledWith('generic.none');
71
- resetMocks();
72
- });
73
- });
74
18
  });
@@ -31,7 +31,7 @@ export default class CatalogApp extends SteveModel {
31
31
  const upgrade = {
32
32
  action: 'goToUpgrade',
33
33
  enabled: true,
34
- icon: 'icon icon-fw icon-edit',
34
+ icon: 'icon icon-edit',
35
35
  label: this.t('catalog.install.action.goToUpgrade'),
36
36
  };
37
37
 
@@ -17,7 +17,7 @@ export default class CatalogOperation extends SteveModel {
17
17
  const openLogs = {
18
18
  action: 'openLogs',
19
19
  enabled: true,
20
- icon: 'icon icon-fw icon-chevron-right',
20
+ icon: 'icon icon-chevron-right',
21
21
  label: this.t('action.openLogs'),
22
22
  total: 1,
23
23
  };
package/models/chart.js CHANGED
@@ -4,7 +4,7 @@ import {
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';
7
- import { CATALOG } from '@shell/config/types';
7
+ import { CATALOG, ZERO_TIME } from '@shell/config/types';
8
8
  import { CATALOG as CATALOG_ANNOTATIONS } from '@shell/config/labels-annotations';
9
9
  import day from 'dayjs';
10
10
 
@@ -121,42 +121,58 @@ export default class Chart extends SteveModel {
121
121
  */
122
122
  get cardContent() {
123
123
  if (!this._cardContent) {
124
- const subHeaderItems = [
125
- {
124
+ const latestVersion = this.versions?.[0] || {};
125
+ const subHeaderItems = [];
126
+
127
+ if (latestVersion) {
128
+ const hasZeroTime = latestVersion.created === ZERO_TIME;
129
+
130
+ subHeaderItems.push({
126
131
  icon: 'icon-version-alt',
127
132
  iconTooltip: { key: 'tableHeaders.version' },
128
- label: this.versions[0].version
129
- },
130
- {
133
+ label: latestVersion.version
134
+ });
135
+
136
+ const lastUpdatedItem = {
131
137
  icon: 'icon-refresh-alt',
132
138
  iconTooltip: { key: 'tableHeaders.lastUpdated' },
133
- label: day(this.versions[0].created).format('MMM D, YYYY')
139
+ label: hasZeroTime ? this.t('generic.na') : day(latestVersion.created).format('MMM D, YYYY')
140
+ };
141
+
142
+ if (hasZeroTime) {
143
+ lastUpdatedItem.labelTooltip = this.t('catalog.charts.appChartCard.subHeaderItem.missingVersionDate');
134
144
  }
135
- ];
145
+
146
+ subHeaderItems.push(lastUpdatedItem);
147
+ }
148
+
136
149
  const footerItems = [
137
150
  {
138
- type: REPO,
139
- icon: 'icon-repository-alt',
140
- iconTooltip: { key: 'tableHeaders.repoName' },
141
- labels: [this.repoNameDisplay]
151
+ type: REPO,
152
+ icon: 'icon-repository-alt',
153
+ iconTooltip: { key: 'tableHeaders.repoName' },
154
+ labels: [this.repoNameDisplay],
155
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.repo') }, true)
142
156
  }
143
157
  ];
144
158
 
145
159
  if (this.categories.length) {
146
160
  footerItems.push( {
147
- type: CATEGORY,
148
- icon: 'icon-category-alt',
149
- iconTooltip: { key: 'generic.category' },
150
- labels: this.categories
161
+ type: CATEGORY,
162
+ icon: 'icon-category-alt',
163
+ iconTooltip: { key: 'generic.category' },
164
+ labels: this.categories,
165
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.category') }, true)
151
166
  });
152
167
  }
153
168
 
154
169
  if (this.tags.length) {
155
170
  footerItems.push({
156
- type: TAG,
157
- icon: 'icon-tag-alt',
158
- iconTooltip: { key: 'generic.tags' },
159
- labels: this.tags
171
+ type: TAG,
172
+ icon: 'icon-tag-alt',
173
+ iconTooltip: { key: 'generic.tags' },
174
+ labels: this.tags,
175
+ labelTooltip: this.t('catalog.charts.findSimilar.message', { type: this.t('catalog.charts.findSimilar.types.tag') }, true)
160
176
  });
161
177
  }
162
178
 
@@ -1,65 +1,11 @@
1
- import { CAPI, CLOUD_CREDENTIALS } from '@shell/config/labels-annotations';
1
+ import { CAPI } from '@shell/config/labels-annotations';
2
2
  import { fullFields, prefixFields, simplify, suffixFields } from '@shell/store/plugins';
3
3
  import { isEmpty, set } from '@shell/utils/object';
4
- import { MANAGEMENT, SECRET } from '@shell/config/types';
4
+ import { SECRET } from '@shell/config/types';
5
5
  import { escapeHtml } from '@shell/utils/string';
6
6
  import NormanModel from '@shell/plugins/steve/norman-class';
7
- import { DATE_FORMAT, TIME_FORMAT } from '@shell/store/prefs';
8
- import day from 'dayjs';
9
-
10
- const harvesterProvider = 'harvester';
11
-
12
- const renew = {
13
- [harvesterProvider]: {
14
- renew: ({ cloudCredential, $ctx }) => {
15
- return renew[harvesterProvider].renewBulk(
16
- { cloudCredentials: [cloudCredential], $ctx }
17
- );
18
- },
19
- renewBulk: async({ cloudCredentials, $ctx }) => {
20
- // A harvester cloud credential (at the moment) is a kubeconfig complete with expiring token
21
- // So to renew we just need to generate a new kubeconfig and save it to the cc (similar to shell/cloud-credential/harvester.vue)
22
- await Promise.all(cloudCredentials.map(async(cc) => {
23
- try {
24
- if (!cc.harvestercredentialConfig?.clusterId) {
25
- throw new Error(`credential has no matching harvester cluster`);
26
- }
27
- const mgmtCluster = $ctx.rootGetters['management/byId'](MANAGEMENT.CLUSTER, cc.harvestercredentialConfig.clusterId);
28
-
29
- if (!mgmtCluster) {
30
- throw new Error(`cannot find harvester cluster`);
31
- }
32
-
33
- const kubeconfigContent = await mgmtCluster.generateKubeConfig();
34
-
35
- cc.setData('kubeconfigContent', kubeconfigContent);
36
-
37
- await cc.save();
38
- } catch (error) {
39
- console.error(`Unable to refresh harvester cloud credential '${ cc.id }'`, error); // eslint-disable-line no-console
40
- }
41
- }));
42
- }
43
- }
44
- };
45
7
 
46
8
  export default class CloudCredential extends NormanModel {
47
- get _availableActions() {
48
- const out = super._availableActions;
49
-
50
- out.splice(0, 0, { divider: true });
51
- out.splice(0, 0, {
52
- action: 'renew',
53
- enabled: this.canRenew,
54
- bulkable: this.canBulkRenew,
55
- bulkAction: 'renewBulk',
56
- icon: 'icon icon-fw icon-refresh',
57
- label: this.t('manager.cloudCredentials.renew'),
58
- });
59
-
60
- return out;
61
- }
62
-
63
9
  get hasSensitiveData() {
64
10
  return true;
65
11
  }
@@ -231,111 +177,4 @@ export default class CloudCredential extends NormanModel {
231
177
  get doneRoute() {
232
178
  return 'c-cluster-manager-secret';
233
179
  }
234
-
235
- get canRenew() {
236
- return !!renew[this.provider]?.renew && this.expires !== undefined && this.canUpdate;
237
- }
238
-
239
- get canBulkRenew() {
240
- return !!renew[this.provider]?.renewBulk;
241
- }
242
-
243
- get expiresForSort() {
244
- // Why not just `expires`? Ensures the correct sort order of expired --> expiring --> never expires
245
- // (instead of 'never expired' --> 'expired' --> 'expiring')
246
- return this.expires !== undefined ? this.expires : Number.MAX_SAFE_INTEGER;
247
- }
248
-
249
- get expires() {
250
- const expires = this.annotations[CLOUD_CREDENTIALS.EXPIRATION];
251
-
252
- if (typeof expires === 'string') {
253
- return parseInt(expires);
254
- } else if (typeof expires === 'number') {
255
- return expires;
256
- }
257
-
258
- return undefined; // Weird things happen if this isn't a number
259
- }
260
-
261
- get expireData() {
262
- if (typeof this.expiresIn !== 'number') {
263
- return null;
264
- }
265
-
266
- const sevenDays = 1000 * 60 * 60 * 24 * 7;
267
-
268
- if (this.expiresIn === 0) {
269
- return {
270
- expired: true,
271
- expiring: false,
272
- };
273
- } else if (this.expiresIn < sevenDays) {
274
- return {
275
- expired: false,
276
- expiring: true,
277
- };
278
- } else if (this.expiresIn) {
279
- return {
280
- expired: false,
281
- expiring: false,
282
- };
283
- }
284
-
285
- return null;
286
- }
287
-
288
- get expiresString() {
289
- if (this.expires === undefined) {
290
- return '';
291
- }
292
-
293
- if (this.expireData.expired) {
294
- return this.t('manager.cloudCredentials.expired');
295
- }
296
-
297
- const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
298
- const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
299
-
300
- return day(this.expires).format(`${ dateFormat } ${ timeFormat }`);
301
- }
302
-
303
- get expiresIn() {
304
- if (this.expires === undefined) {
305
- return null;
306
- }
307
-
308
- const timeThen = this.expires;
309
- const timeNow = Date.now();
310
-
311
- const expiresIn = timeThen - timeNow;
312
-
313
- return expiresIn < 0 ? 0 : expiresIn;
314
- }
315
-
316
- renew() {
317
- const renewFn = renew[this.provider]?.renew;
318
-
319
- if (!renewFn) {
320
- console.error('No fn renew function for ', this.provider); // eslint-disable-line no-console
321
- }
322
-
323
- return renewFn({
324
- cloudCredential: this,
325
- $ctx: this.$ctx
326
- });
327
- }
328
-
329
- async renewBulk(cloudCredentials = []) {
330
- const renewBulkFn = renew[this.provider]?.renewBulk;
331
-
332
- if (!renewBulkFn) {
333
- console.error('No fn renew bulk function for ', this.provider); // eslint-disable-line no-console
334
- }
335
-
336
- return renewBulkFn({
337
- cloudCredentials,
338
- $ctx: this.$ctx
339
- });
340
- }
341
180
  }
@@ -16,7 +16,7 @@ export default class ClusterNode extends SteveModel {
16
16
  const cordon = {
17
17
  action: 'cordon',
18
18
  enabled: !!normanAction.cordon,
19
- icon: 'icon icon-fw icon-pause',
19
+ icon: 'icon icon-pause',
20
20
  label: 'Cordon',
21
21
  total: 1,
22
22
  bulkable: true
@@ -25,7 +25,7 @@ export default class ClusterNode extends SteveModel {
25
25
  const uncordon = {
26
26
  action: 'uncordon',
27
27
  enabled: !!normanAction.uncordon,
28
- icon: 'icon icon-fw icon-play',
28
+ icon: 'icon icon-play',
29
29
  label: 'Uncordon',
30
30
  total: 1,
31
31
  bulkable: true
@@ -34,7 +34,7 @@ export default class ClusterNode extends SteveModel {
34
34
  const drain = {
35
35
  action: 'drain',
36
36
  enabled: !!normanAction.drain,
37
- icon: 'icon icon-fw icon-dot-open',
37
+ icon: 'icon icon-dot-open',
38
38
  label: this.t('drainNode.action'),
39
39
  bulkable: true,
40
40
  bulkAction: 'drain'
@@ -43,7 +43,7 @@ export default class ClusterNode extends SteveModel {
43
43
  const stopDrain = {
44
44
  action: 'stopDrain',
45
45
  enabled: !!normanAction.stopDrain,
46
- icon: 'icon icon-fw icon-x',
46
+ icon: 'icon icon-x',
47
47
  label: this.t('drainNode.actionStop'),
48
48
  bulkable: true,
49
49
  };
@@ -51,14 +51,14 @@ export default class ClusterNode extends SteveModel {
51
51
  const openSsh = {
52
52
  action: 'openSsh',
53
53
  enabled: !!this.provisionedMachine?.links?.shell,
54
- icon: 'icon icon-fw icon-chevron-right',
54
+ icon: 'icon icon-chevron-right',
55
55
  label: 'SSH Shell',
56
56
  };
57
57
 
58
58
  const downloadKeys = {
59
59
  action: 'downloadKeys',
60
60
  enabled: !!this.provisionedMachine?.links?.sshkeys,
61
- icon: 'icon icon-fw icon-download',
61
+ icon: 'icon icon-download',
62
62
  label: this.t('node.actions.downloadSSHKey'),
63
63
  };
64
64
 
@@ -227,7 +227,7 @@ export default class ClusterNode extends SteveModel {
227
227
  }
228
228
 
229
229
  get podCapacity() {
230
- return Number.parseInt(this.status.capacity?.pods);
230
+ return parseSi(this.status.capacity?.pods);
231
231
  }
232
232
 
233
233
  get podConsumed() {
@@ -60,13 +60,13 @@ export default class CapiMachine extends SteveModel {
60
60
  const openSsh = {
61
61
  action: 'openSsh',
62
62
  enabled: !!this.links.shell && this.isRunning,
63
- icon: 'icon icon-fw icon-chevron-right',
63
+ icon: 'icon icon-chevron-right',
64
64
  label: 'SSH Shell',
65
65
  };
66
66
  const downloadKeys = {
67
67
  action: 'downloadKeys',
68
68
  enabled: !!this.links.sshkeys,
69
- icon: 'icon icon-fw icon-download',
69
+ icon: 'icon icon-download',
70
70
  label: this.t('node.actions.downloadSSHKey'),
71
71
  };
72
72
  const forceRemove = {
@@ -80,7 +80,7 @@ export default class CapiMachine extends SteveModel {
80
80
  action: 'toggleScaleDownModal',
81
81
  bulkAction: 'toggleScaleDownModal',
82
82
  enabled: !!this.canScaleDown,
83
- icon: 'icon icon-minus icon-fw',
83
+ icon: 'icon icon-minus',
84
84
  label: this.t('node.actions.scaleDown'),
85
85
  bulkable: true
86
86
  };