@rancher/shell 3.0.2-rc.2 → 3.0.2-rc.4

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 (172) hide show
  1. package/assets/styles/base/_basic.scss +7 -8
  2. package/assets/styles/global/_button.scss +10 -0
  3. package/assets/styles/global/_form.scss +2 -1
  4. package/assets/styles/global/_tooltip.scss +2 -2
  5. package/assets/styles/themes/_dark.scss +15 -3
  6. package/assets/styles/themes/_light.scss +7 -2
  7. package/assets/styles/vendor/vue-select.scss +4 -0
  8. package/assets/translations/en-us.yaml +66 -9
  9. package/assets/translations/zh-hans.yaml +2 -3
  10. package/components/AppModal.vue +50 -0
  11. package/components/BannerGraphic.vue +0 -42
  12. package/components/ButtonMultiAction.vue +1 -1
  13. package/components/Carousel.vue +88 -74
  14. package/components/CommunityLinks.vue +6 -1
  15. package/components/CopyToClipboardText.vue +3 -0
  16. package/components/Dialog.vue +20 -1
  17. package/components/GrowlManager.vue +9 -2
  18. package/components/LocaleSelector.vue +8 -1
  19. package/components/PaginatedResourceTable.vue +4 -7
  20. package/components/ProgressBarMulti.vue +14 -0
  21. package/components/PromptChangePassword.vue +3 -0
  22. package/components/Questions/Reference.vue +57 -28
  23. package/components/ResourceDetail/Masthead.vue +1 -1
  24. package/components/SelectIconGrid.vue +12 -1
  25. package/components/SideNav.vue +12 -38
  26. package/components/SortableTable/index.vue +1 -0
  27. package/components/Tabbed/index.vue +9 -1
  28. package/components/YamlEditor.vue +1 -0
  29. package/components/__tests__/Carousel.test.ts +56 -27
  30. package/components/auth/Principal.vue +5 -3
  31. package/components/fleet/FleetClusters.vue +82 -1
  32. package/components/fleet/FleetRepos.vue +13 -30
  33. package/components/fleet/ForceDirectedTreeChart/index.vue +2 -2
  34. package/components/form/ChangePassword.vue +2 -0
  35. package/components/form/ColorInput.vue +24 -1
  36. package/components/form/FileSelector.vue +2 -0
  37. package/components/form/KeyValue.vue +230 -160
  38. package/components/form/LabeledSelect.vue +2 -2
  39. package/components/form/PlusMinus.vue +14 -2
  40. package/components/form/ResourceLabeledSelect.vue +13 -53
  41. package/components/form/ResourceSelector.vue +1 -0
  42. package/components/form/ResourceTabs/index.vue +79 -36
  43. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
  44. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
  45. package/components/form/SSHKnownHosts/index.vue +101 -0
  46. package/components/form/SecretSelector.vue +2 -2
  47. package/components/form/Select.vue +1 -1
  48. package/components/form/SelectOrCreateAuthSecret.vue +43 -11
  49. package/components/form/__tests__/KeyValue.test.ts +1 -1
  50. package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
  51. package/components/formatter/FleetClusterSummaryGraph.vue +2 -2
  52. package/components/formatter/FleetSummaryGraph.vue +6 -7
  53. package/components/formatter/WorkloadHealthScale.vue +7 -0
  54. package/components/nav/Group.vue +30 -4
  55. package/components/nav/Header.vue +82 -114
  56. package/components/nav/HeaderPageActionMenu.vue +27 -131
  57. package/components/nav/NamespaceFilter.vue +1 -1
  58. package/components/nav/Type.vue +15 -0
  59. package/composables/focusTrap.ts +68 -0
  60. package/config/home-links.js +21 -13
  61. package/config/labels-annotations.js +2 -0
  62. package/config/page-actions.js +1 -0
  63. package/config/pagination-table-headers.js +15 -1
  64. package/config/product/explorer.js +7 -17
  65. package/config/table-headers.js +6 -0
  66. package/config/version.js +5 -1
  67. package/core/plugin.ts +41 -1
  68. package/core/plugins.js +125 -72
  69. package/core/types-provisioning.ts +91 -2
  70. package/core/types.ts +55 -0
  71. package/detail/__tests__/autoscaling.horizontalpodautoscaler.test.ts +12 -3
  72. package/detail/catalog.cattle.io.app.vue +1 -1
  73. package/detail/fleet.cattle.io.cluster.vue +3 -3
  74. package/detail/namespace.vue +13 -19
  75. package/detail/networking.k8s.io.ingress.vue +13 -53
  76. package/detail/provisioning.cattle.io.cluster.vue +12 -1
  77. package/detail/secret.vue +25 -0
  78. package/detail/workload/index.vue +3 -3
  79. package/dialog/AddCustomBadgeDialog.vue +5 -1
  80. package/edit/auth/ldap/__tests__/config.test.ts +18 -0
  81. package/edit/auth/ldap/config.vue +24 -0
  82. package/edit/auth/saml.vue +8 -6
  83. package/edit/fleet.cattle.io.gitrepo.vue +34 -23
  84. package/edit/logging-flow/index.vue +4 -19
  85. package/edit/networking.k8s.io.ingress/index.vue +18 -65
  86. package/edit/networking.k8s.io.networkpolicy/index.vue +4 -5
  87. package/edit/provisioning.cattle.io.cluster/index.vue +27 -8
  88. package/edit/provisioning.cattle.io.cluster/rke2.vue +31 -115
  89. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +2 -2
  90. package/edit/provisioning.cattle.io.cluster/tabs/networking/ACE.vue +14 -28
  91. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +25 -12
  92. package/edit/secret/index.vue +1 -1
  93. package/edit/secret/ssh.vue +21 -3
  94. package/edit/service.vue +1 -2
  95. package/list/networking.k8s.io.ingress.vue +1 -1
  96. package/list/node.vue +15 -8
  97. package/list/persistentvolume.vue +12 -4
  98. package/list/provisioning.cattle.io.cluster.vue +1 -0
  99. package/list/service.vue +1 -1
  100. package/list/workload.vue +4 -0
  101. package/mixins/chart.js +4 -1
  102. package/models/catalog.cattle.io.app.js +3 -1
  103. package/models/catalog.cattle.io.clusterrepo.js +56 -7
  104. package/models/fleet.cattle.io.bundle.js +0 -11
  105. package/models/fleet.cattle.io.cluster.js +17 -1
  106. package/models/fleet.cattle.io.gitrepo.js +88 -52
  107. package/models/provisioning.cattle.io.cluster.js +36 -1
  108. package/models/secret.js +5 -0
  109. package/models/service.js +1 -0
  110. package/models/workload.js +19 -1
  111. package/package.json +5 -4
  112. package/pages/account/index.vue +4 -0
  113. package/pages/c/_cluster/apps/charts/index.vue +4 -0
  114. package/pages/c/_cluster/explorer/ConfigBadge.vue +4 -2
  115. package/pages/c/_cluster/explorer/index.vue +13 -6
  116. package/pages/c/_cluster/fleet/GitRepoGraphConfig.js +3 -3
  117. package/pages/c/_cluster/fleet/index.vue +75 -89
  118. package/pages/c/_cluster/settings/links.vue +2 -2
  119. package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
  120. package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
  121. package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
  122. package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
  123. package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
  124. package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
  125. package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
  126. package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
  127. package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
  128. package/pages/c/_cluster/uiplugins/index.vue +50 -12
  129. package/pages/diagnostic.vue +17 -15
  130. package/pages/home.vue +32 -6
  131. package/plugins/clean-html.js +50 -0
  132. package/plugins/dashboard-store/resource-class.js +4 -0
  133. package/plugins/plugin.js +54 -49
  134. package/plugins/steve/mutations.js +1 -1
  135. package/plugins/steve/steve-class.js +8 -0
  136. package/plugins/steve/steve-pagination-utils.ts +3 -1
  137. package/rancher-components/Accordion/Accordion.vue +4 -4
  138. package/rancher-components/BadgeState/BadgeState.vue +7 -0
  139. package/rancher-components/Card/Card.vue +12 -0
  140. package/rancher-components/Form/Checkbox/Checkbox.vue +9 -2
  141. package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +18 -1
  142. package/rancher-components/Form/LabeledInput/LabeledInput.vue +19 -1
  143. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +39 -2
  144. package/rancher-components/RcButton/RcButton.vue +90 -0
  145. package/rancher-components/RcButton/index.ts +2 -0
  146. package/rancher-components/RcButton/types.ts +17 -0
  147. package/rancher-components/RcDropdown/RcDropdown.vue +122 -0
  148. package/rancher-components/RcDropdown/RcDropdownItem.vue +127 -0
  149. package/rancher-components/RcDropdown/RcDropdownSeparator.vue +6 -0
  150. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +42 -0
  151. package/rancher-components/RcDropdown/index.ts +4 -0
  152. package/rancher-components/RcDropdown/types.ts +22 -0
  153. package/rancher-components/RcDropdown/useDropdownCollection.ts +46 -0
  154. package/rancher-components/RcDropdown/useDropdownContext.ts +110 -0
  155. package/scripts/test-plugins-build.sh +2 -0
  156. package/scripts/typegen.sh +2 -0
  157. package/store/catalog.js +1 -1
  158. package/tsconfig.json +2 -1
  159. package/types/components/paginatedResourceTable.ts +25 -0
  160. package/types/components/resourceLabeledSelect.ts +48 -0
  161. package/types/resources/fleet.d.ts +17 -0
  162. package/types/shell/index.d.ts +61 -0
  163. package/utils/auth.js +5 -1
  164. package/utils/cluster.js +106 -0
  165. package/utils/fleet.ts +35 -3
  166. package/utils/ingress.ts +64 -0
  167. package/utils/uiplugins.ts +56 -44
  168. package/utils/validators/cron-schedule.js +7 -2
  169. package/utils/validators/formRules/__tests__/index.test.ts +53 -17
  170. package/utils/validators/formRules/index.ts +20 -5
  171. package/vue.config.js +1 -1
  172. package/components/RelatedWorkloadsTable.vue +0 -50
package/list/node.vue CHANGED
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import PaginatedResourceTable, { FetchPageSecondaryResourcesOpts } from '@shell/components/PaginatedResourceTable.vue';
2
+ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
3
3
  import Tag from '@shell/components/Tag.vue';
4
4
  import { Banner } from '@components/Banner';
5
5
  import { PODS } from '@shell/config/table-headers';
@@ -19,6 +19,7 @@ import { GROUP_RESOURCES, mapPref } from '@shell/store/prefs';
19
19
  import { COLUMN_BREAKPOINTS } from '@shell/types/store/type-map';
20
20
 
21
21
  import { mapGetters } from 'vuex';
22
+ import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
22
23
 
23
24
  export default defineComponent({
24
25
  name: 'ListNode',
@@ -185,19 +186,25 @@ export default defineComponent({
185
186
  row['displayLabels'] = !row.displayLabels;
186
187
  },
187
188
 
188
- fetchSecondaryResources(): { [key: string]: Promise<any>} {
189
- const hash: { [key: string]: Promise<any>} = {};
189
+ /**
190
+ * of type PagTableFetchSecondaryResources
191
+ */
192
+ async fetchSecondaryResources({ canPaginate }: PagTableFetchSecondaryResourcesOpts): PagTableFetchSecondaryResourcesReturns {
193
+ if (canPaginate) {
194
+ return;
195
+ }
196
+ const promises = [];
190
197
 
191
198
  if (this.canViewMgmtNodes) {
192
- hash.mgmtNodes = this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.NODE });
199
+ promises.push(this.$store.dispatch(`management/findAll`, { type: MANAGEMENT.NODE }));
193
200
  }
194
201
 
195
202
  if (this.canViewNormanNodes) {
196
- hash.normanNodes = this.$store.dispatch(`rancher/findAll`, { type: NORMAN.NODE });
203
+ promises.push(this.$store.dispatch(`rancher/findAll`, { type: NORMAN.NODE }));
197
204
  }
198
205
 
199
206
  if (this.canViewMachines) {
200
- hash.machines = this.$store.dispatch(`management/findAll`, { type: CAPI.MACHINE });
207
+ promises.push(this.$store.dispatch(`management/findAll`, { type: CAPI.MACHINE }));
201
208
  }
202
209
 
203
210
  if (this.canViewPods) {
@@ -205,7 +212,7 @@ export default defineComponent({
205
212
  this.$store.dispatch(`cluster/findAll`, { type: POD });
206
213
  }
207
214
 
208
- return hash;
215
+ await Promise.all(promises);
209
216
  },
210
217
 
211
218
  /**
@@ -215,7 +222,7 @@ export default defineComponent({
215
222
  *
216
223
  * So when we have a page.... use those entries as filters when fetching the other resources
217
224
  */
218
- async fetchPageSecondaryResources({ canPaginate, force, page }: FetchPageSecondaryResourcesOpts) {
225
+ async fetchPageSecondaryResources({ canPaginate, force, page }: PagTableFetchPageSecondaryResourcesOpts) {
219
226
  if (!page?.length) {
220
227
  return;
221
228
  }
@@ -1,9 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent } from 'vue';
3
- import PaginatedResourceTable, { FetchPageSecondaryResourcesOpts } from '@shell/components/PaginatedResourceTable.vue';
3
+ import PaginatedResourceTable from '@shell/components/PaginatedResourceTable.vue';
4
4
  import { PVC } from '@shell/config/types';
5
5
  import { ActionFindPageArgs } from '@shell/types/store/dashboard-store.types';
6
6
  import { FilterArgs, PaginationFilterField, PaginationParamFilter } from '@shell/types/store/pagination.types';
7
+ import { PagTableFetchPageSecondaryResourcesOpts, PagTableFetchSecondaryResourcesOpts, PagTableFetchSecondaryResourcesReturns } from '@shell/types/components/paginatedResourceTable';
7
8
 
8
9
  export default defineComponent({
9
10
  name: 'ListPersistentVolume',
@@ -38,8 +39,15 @@ export default defineComponent({
38
39
  },
39
40
 
40
41
  methods: {
41
- fetchSecondaryResources(): { [key: string]: Promise<any>} {
42
- return { pvc: this.$store.dispatch(`cluster/findAll`, { type: PVC }) };
42
+ /**
43
+ * of type PagTableFetchSecondaryResources
44
+ */
45
+ async fetchSecondaryResources({ canPaginate }: PagTableFetchSecondaryResourcesOpts): PagTableFetchSecondaryResourcesReturns {
46
+ if (canPaginate) {
47
+ return;
48
+ }
49
+
50
+ return this.$store.dispatch(`cluster/findAll`, { type: PVC });
43
51
  },
44
52
 
45
53
  /**
@@ -47,7 +55,7 @@ export default defineComponent({
47
55
  *
48
56
  * So when we have a page.... use those entries as filters when fetching the other resources
49
57
  */
50
- async fetchPageSecondaryResources({ canPaginate, force, page }: FetchPageSecondaryResourcesOpts) {
58
+ async fetchPageSecondaryResources({ canPaginate, force, page }: PagTableFetchPageSecondaryResourcesOpts) {
51
59
  if (!page?.length) {
52
60
  return;
53
61
  }
@@ -174,6 +174,7 @@ export default {
174
174
  // results are filtered so we wouldn't get the correct count on indicator...
175
175
  return { loadIndeterminate: true };
176
176
  }
177
+
177
178
  };
178
179
  </script>
179
180
 
package/list/service.vue CHANGED
@@ -24,7 +24,7 @@ export default {
24
24
 
25
25
  methods: {
26
26
  /**
27
- * opts: FetchSecondaryResourcesOpts
27
+ * of type PagTableFetchSecondaryResources
28
28
  */
29
29
  async fetchSecondaryResources(opts) {
30
30
  const inStore = this.$store.getters['currentStore']();
package/list/workload.vue CHANGED
@@ -159,6 +159,10 @@ export default {
159
159
  }
160
160
  },
161
161
 
162
+ typeDisplay() {
163
+ // Used by shell/components/ResourceList/index.vue to override list title (usually found via schema, which doesn't exist for this virtual type)
164
+ return this.$store.getters['type-map/labelFor'](this.schema || workloadSchema, 99);
165
+ },
162
166
  };
163
167
  </script>
164
168
 
package/mixins/chart.js CHANGED
@@ -182,9 +182,12 @@ export default {
182
182
  }
183
183
 
184
184
  if (this.existing && this.existing.upgradeAvailable === false) {
185
+ const manager = this.existing?.spec?.chart?.metadata?.annotations?.[CATALOG_ANNOTATIONS.MANAGED] || 'Rancher';
186
+
185
187
  warnings.unshift(this.t('catalog.install.warning.managed', {
186
188
  name: this.existing.name,
187
- version: this.chart ? this.query.versionName : null
189
+ version: this.chart ? this.query.versionName : null,
190
+ manager: manager === 'true' ? 'Rancher' : manager
188
191
  }, true));
189
192
  }
190
193
 
@@ -41,7 +41,9 @@ export default class CatalogApp extends SteveModel {
41
41
 
42
42
  get warnDeletionMessage() {
43
43
  if (this.upgradeAvailable === false) {
44
- return this.t('catalog.delete.warning.managed', { name: this.name });
44
+ const manager = this.spec?.chart?.metadata?.annotations?.[CATALOG_ANNOTATIONS.MANAGED] || 'Rancher';
45
+
46
+ return this.t('catalog.delete.warning.managed', { manager: manager === 'true' ? 'Rancher' : manager, name: this.name });
45
47
  }
46
48
 
47
49
  return null;
@@ -2,6 +2,7 @@ import { parse } from '@shell/utils/url';
2
2
  import { CATALOG } from '@shell/config/labels-annotations';
3
3
  import { insertAt } from '@shell/utils/array';
4
4
  import { CATALOG as CATALOG_TYPE } from '@shell/config/types';
5
+ import { colorForState, stateDisplay } from '@shell/plugins/dashboard-store/resource-class';
5
6
 
6
7
  import SteveModel from '@shell/plugins/steve/steve-class';
7
8
 
@@ -12,18 +13,40 @@ export default class ClusterRepo extends SteveModel {
12
13
  }
13
14
  }
14
15
 
16
+ get _isClusterRepoDisabled() {
17
+ return this.spec?.enabled === false;
18
+ }
19
+
15
20
  get _availableActions() {
16
21
  const out = super._availableActions;
17
22
 
18
23
  insertAt(out, 0, { divider: true });
19
24
 
20
- insertAt(out, 0, {
21
- action: 'refresh',
22
- label: this.t('action.refresh'),
23
- icon: 'icon icon-refresh',
24
- enabled: !!this.links.update,
25
- bulkable: true,
26
- });
25
+ if (this._isClusterRepoDisabled) {
26
+ insertAt(out, 1, {
27
+ action: 'enableClusterRepo',
28
+ label: this.t('action.enable'),
29
+ icon: 'icon icon-play',
30
+ enabled: true,
31
+ bulkable: true,
32
+ });
33
+ } else {
34
+ insertAt(out, 1, {
35
+ action: 'disableClusterRepo',
36
+ label: this.t('action.disable'),
37
+ icon: 'icon icon-pause',
38
+ enabled: true,
39
+ bulkable: true,
40
+ });
41
+
42
+ insertAt(out, 0, {
43
+ action: 'refresh',
44
+ label: this.t('action.refresh'),
45
+ icon: 'icon icon-refresh',
46
+ enabled: !!this.links.update,
47
+ bulkable: true,
48
+ });
49
+ }
27
50
 
28
51
  return out;
29
52
  }
@@ -39,6 +62,16 @@ export default class ClusterRepo extends SteveModel {
39
62
  this.$dispatch('catalog/load', { force: true, reset: true }, { root: true });
40
63
  }
41
64
 
65
+ async disableClusterRepo() {
66
+ this.spec.enabled = false;
67
+ await this.save();
68
+ }
69
+
70
+ async enableClusterRepo() {
71
+ this.spec.enabled = true;
72
+ await this.save();
73
+ }
74
+
42
75
  get isGit() {
43
76
  return !!this.spec?.gitRepo;
44
77
  }
@@ -157,6 +190,22 @@ export default class ClusterRepo extends SteveModel {
157
190
  } : undefined;
158
191
  }
159
192
 
193
+ get stateDisplay() {
194
+ if (this._isClusterRepoDisabled) {
195
+ return this.t('generic.disabled');
196
+ } else {
197
+ return stateDisplay(this.state);
198
+ }
199
+ }
200
+
201
+ get stateBackground() {
202
+ if (this._isClusterRepoDisabled) {
203
+ return 'badge-disabled';
204
+ } else {
205
+ return colorForState(this.state, this.stateObj?.error, this.stateObj?.transitioning).replace('text-', 'bg-');
206
+ }
207
+ }
208
+
160
209
  waitForOperation(operationId, timeout, interval = 2000) {
161
210
  return this.waitForTestFn(() => {
162
211
  if (!this.$getters['schemaFor'](CATALOG_TYPE.OPERATION)) {
@@ -6,17 +6,6 @@ import { FLEET } from '@shell/config/types';
6
6
  import { convertSelectorObj, matching } from '@shell/utils/selector';
7
7
 
8
8
  export default class FleetBundle extends SteveModel {
9
- get deploymentInfo() {
10
- const ready = this.status?.summary?.ready || 0;
11
- const total = this.status?.summary?.desiredReady || 0;
12
-
13
- return {
14
- ready,
15
- unready: total - ready,
16
- total
17
- };
18
- }
19
-
20
9
  get lastUpdateTime() {
21
10
  return this.status?.conditions?.[0].lastUpdateTime;
22
11
  }
@@ -1,5 +1,5 @@
1
1
  import { LOCAL_CLUSTER, MANAGEMENT, NORMAN } from '@shell/config/types';
2
- import { CAPI, FLEET as FLEET_LABELS } from '@shell/config/labels-annotations';
2
+ import { CAPI, FLEET as FLEET_LABELS, SYSTEM_LABELS } from '@shell/config/labels-annotations';
3
3
  import { _RKE2 } from '@shell/store/prefs';
4
4
  import SteveModel from '@shell/plugins/steve/steve-class';
5
5
  import { escapeHtml } from '@shell/utils/string';
@@ -190,6 +190,22 @@ export default class FleetCluster extends SteveModel {
190
190
  }
191
191
  }
192
192
 
193
+ get customLabels() {
194
+ const parsedLabels = [];
195
+
196
+ if (this.labels) {
197
+ for (const k in this.labels) {
198
+ const [prefix] = k.split('/');
199
+
200
+ if (!SYSTEM_LABELS.includes(prefix) && k !== CAPI.PROVIDER) {
201
+ parsedLabels.push(`${ k }=${ this.labels[k] }`);
202
+ }
203
+ }
204
+ }
205
+
206
+ return parsedLabels;
207
+ }
208
+
193
209
  async saveYaml(yaml) {
194
210
  await this._saveYaml(yaml);
195
211
 
@@ -1,5 +1,6 @@
1
1
  import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
2
2
  import jsyaml from 'js-yaml';
3
+ import isEmpty from 'lodash/isEmpty';
3
4
  import { escapeHtml } from '@shell/utils/string';
4
5
  import { FLEET } from '@shell/config/types';
5
6
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
@@ -7,7 +8,7 @@ import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
7
8
  import { set } from '@shell/utils/object';
8
9
  import SteveModel from '@shell/plugins/steve/steve-class';
9
10
  import {
10
- colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
11
+ colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, STATES_ENUM, stateSort,
11
12
  } from '@shell/plugins/dashboard-store/resource-class';
12
13
  import { NAME } from '@shell/config/product/explorer';
13
14
  import FleetUtils from '@shell/utils/fleet';
@@ -20,6 +21,26 @@ function quacksLikeAHash(str) {
20
21
  return false;
21
22
  }
22
23
 
24
+ function normalizeStateCounts(data) {
25
+ if (isEmpty(data)) {
26
+ return {
27
+ total: 0,
28
+ states: {},
29
+ };
30
+ }
31
+ const { desiredReady, ...rest } = data ;
32
+ const states = Object.entries(rest).reduce((res, [key, value]) => {
33
+ res[mapStateToEnum(key)] = value;
34
+
35
+ return res;
36
+ }, {});
37
+
38
+ return {
39
+ total: desiredReady,
40
+ states,
41
+ };
42
+ }
43
+
23
44
  export default class GitRepo extends SteveModel {
24
45
  applyDefaults() {
25
46
  const spec = this.spec || {};
@@ -157,7 +178,7 @@ export default class GitRepo extends SteveModel {
157
178
  }
158
179
 
159
180
  get github() {
160
- const match = this.spec.repo.match(/^https?:\/\/github\.com\/(.*?)(\.git)?\/*$/);
181
+ const match = (this.spec.repo || '').match(/^https?:\/\/github\.com\/(.*?)(\.git)?\/*$/);
161
182
 
162
183
  if (match) {
163
184
  return match[1];
@@ -175,7 +196,7 @@ export default class GitRepo extends SteveModel {
175
196
  }
176
197
 
177
198
  get repoDisplay() {
178
- let repo = this.spec.repo;
199
+ let repo = this.spec.repo || '';
179
200
 
180
201
  if (!repo) {
181
202
  return null;
@@ -305,18 +326,7 @@ export default class GitRepo extends SteveModel {
305
326
  }
306
327
 
307
328
  get bundles() {
308
- const all = this.$getters['all'](FLEET.BUNDLE);
309
-
310
- return all.filter((bundle) => bundle.repoName === this.name &&
311
- bundle.namespace === this.namespace &&
312
- bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
313
- }
314
-
315
- /**
316
- * Bundles with state of active
317
- */
318
- get bundlesReady() {
319
- return this.bundles?.filter((bundle) => bundle.state === 'active');
329
+ return this.$getters['matching'](FLEET.BUNDLE, { 'fleet.cattle.io/repo-name': this.name }, this.namespace);
320
330
  }
321
331
 
322
332
  get bundleDeployments() {
@@ -325,6 +335,65 @@ export default class GitRepo extends SteveModel {
325
335
  return bds.filter((bd) => bd.metadata?.labels?.['fleet.cattle.io/repo-name'] === this.name);
326
336
  }
327
337
 
338
+ get allBundlesStatuses() {
339
+ const bundleDeploymentCountsPerBundle = this.bundleDeployments.reduce((acc, bd) => {
340
+ const bundleId = FleetUtils.bundleIdFromBundleDeploymentLabels(bd.metadata?.labels);
341
+ const state = mapStateToEnum(FleetUtils.bundleDeploymentState(bd));
342
+
343
+ if (!acc[bundleId]) {
344
+ acc[bundleId] = {
345
+ total: 0,
346
+ states: { [STATES_ENUM.READY]: 0 },
347
+ };
348
+ }
349
+ acc[bundleId].total++;
350
+
351
+ if (!acc[bundleId].states[state]) {
352
+ acc[bundleId].states[state] = 0;
353
+ }
354
+ acc[bundleId].states[state]++;
355
+
356
+ return acc;
357
+ }, {});
358
+ const bundleIds = Object.keys(bundleDeploymentCountsPerBundle);
359
+
360
+ return bundleIds.reduce((acc, bundleId) => {
361
+ const state = primaryDisplayStatusFromCount(bundleDeploymentCountsPerBundle[bundleId].states);
362
+
363
+ if (!acc.states[state]) {
364
+ acc.states[state] = 0;
365
+ }
366
+ acc.states[state]++;
367
+
368
+ return acc;
369
+ }, { total: bundleIds.length, states: { [STATES_ENUM.READY]: 0 } } );
370
+ }
371
+
372
+ get allResourceStatuses() {
373
+ return normalizeStateCounts(this.status?.resourceCounts || {});
374
+ }
375
+
376
+ statusResourceCountsForCluster(clusterId) {
377
+ if (!this.targetClusters.some((c) => c.id === clusterId)) {
378
+ return {};
379
+ }
380
+
381
+ return this.bundleDeployments
382
+ .filter((bd) => FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels) === clusterId)
383
+ .map((bd) => FleetUtils.resourcesFromBundleDeploymentStatus(bd.status))
384
+ .flat()
385
+ .map((r) => r.state)
386
+ .reduce((prev, state) => {
387
+ if (!prev[state]) {
388
+ prev[state] = 0;
389
+ }
390
+ prev[state]++;
391
+ prev.desiredReady++;
392
+
393
+ return prev;
394
+ }, { desiredReady: 0 });
395
+ }
396
+
328
397
  get resourcesStatuses() {
329
398
  const bundleDeployments = this.bundleDeployments || [];
330
399
  const clusters = (this.targetClusters || []).reduce((res, c) => {
@@ -357,7 +426,7 @@ export default class GitRepo extends SteveModel {
357
426
  name: `c-cluster-product-resource${ r.namespace ? '-namespace' : '' }-id`,
358
427
  params: {
359
428
  product: NAME,
360
- cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
429
+ cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME], // explorer uses the "management" Cluster name, which differs from the Fleet Cluster name
361
430
  resource: type,
362
431
  namespace: r.namespace,
363
432
  id: r.name,
@@ -385,7 +454,6 @@ export default class GitRepo extends SteveModel {
385
454
  creationTimestamp: r.createdAt,
386
455
 
387
456
  // other properties
388
- clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
389
457
  stateBackground: color,
390
458
  stateDisplay: display,
391
459
  stateSort: stateSort(color, display),
@@ -408,42 +476,10 @@ export default class GitRepo extends SteveModel {
408
476
  };
409
477
  }
410
478
 
411
- get clusterResourceStatus() {
412
- const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => {
413
- const { clusterId, clusterLabel, state } = curr;
414
-
415
- if (!prev[clusterId]) {
416
- prev[clusterId] = {
417
- clusterLabel,
418
- resourceCounts: { [state]: 0, desiredReady: 0 }
419
-
420
- };
421
- }
422
-
423
- if (!prev[clusterId].resourceCounts[state]) {
424
- prev[clusterId].resourceCounts[state] = 0;
425
- }
426
-
427
- prev[clusterId].resourceCounts[state] += 1;
428
- prev[clusterId].resourceCounts.desiredReady += 1;
429
-
430
- return prev;
431
- }, {});
432
-
433
- const values = Object.keys(clusterStatuses).map((key) => {
434
- const { clusterLabel, resourceCounts } = clusterStatuses[key];
435
-
436
- return {
437
- clusterId: key,
438
- clusterLabel, // FLEET LABEL
439
- status: {
440
- displayStatus: primaryDisplayStatusFromCount(resourceCounts),
441
- resourceCounts: { ...resourceCounts }
442
- }
443
- };
444
- });
479
+ clusterState(clusterId) {
480
+ const resourceCounts = this.statusResourceCountsForCluster(clusterId);
445
481
 
446
- return values;
482
+ return primaryDisplayStatusFromCount(resourceCounts) || STATES_ENUM.ACTIVE;
447
483
  }
448
484
 
449
485
  get clustersList() {
@@ -178,7 +178,15 @@ export default class ProvCluster extends SteveModel {
178
178
  });
179
179
  }
180
180
 
181
- return actions.concat(out);
181
+ const all = actions.concat(out);
182
+
183
+ // If we have a helper that wants to modify the available actions, let it do it
184
+ if (this.customProvisionerHelper?.availableActions) {
185
+ // Provider can either modify the provided list or return one of its own
186
+ return this.customProvisionerHelper?.availableActions(this, all) || all;
187
+ }
188
+
189
+ return all;
182
190
  }
183
191
 
184
192
  get normanCluster() {
@@ -401,6 +409,11 @@ export default class ProvCluster extends SteveModel {
401
409
  }
402
410
 
403
411
  get provisionerDisplay() {
412
+ // Allow a model extension to override the display of the provisioner
413
+ if (this.customProvisionerHelper?.provisionerDisplay) {
414
+ return this.customProvisionerHelper?.provisionerDisplay(this);
415
+ }
416
+
404
417
  let provisioner = (this.provisioner || '').toLowerCase();
405
418
 
406
419
  // RKE provisioner can actually do K3s too...
@@ -496,6 +509,10 @@ export default class ProvCluster extends SteveModel {
496
509
  }
497
510
 
498
511
  get machineProviderDisplay() {
512
+ if (this.customProvisionerHelper?.machineProviderDisplay) {
513
+ return this.customProvisionerHelper?.machineProviderDisplay(this);
514
+ }
515
+
499
516
  if ( this.isImported ) {
500
517
  return null;
501
518
  }
@@ -954,6 +971,24 @@ export default class ProvCluster extends SteveModel {
954
971
  if ( res?._status === 204 ) {
955
972
  await this.$dispatch('ws.resource.remove', { data: this });
956
973
  }
974
+
975
+ // If this cluster has a custom provisioner, allow it to do custom deletion
976
+ if (this.customProvisionerHelper?.postDelete) {
977
+ return this.customProvisionerHelper?.postDelete(this);
978
+ }
979
+ }
980
+
981
+ /**
982
+ * Get the custom provisioner helper for this model
983
+ */
984
+ get customProvisionerHelper() {
985
+ // Find the first model extension that says it can be used for this model
986
+ return this.modelExtensions.find((modelExt) => modelExt.useFor ? modelExt.useFor(this) : false);
987
+ }
988
+
989
+ get groupByParent() {
990
+ // Customer helper can report if the cluster has a parent cluster
991
+ return this.customProvisionerHelper?.parentCluster?.(this) || this.t('resourceTable.groupLabel.notInACluster');
957
992
  }
958
993
 
959
994
  get hasError() {
package/models/secret.js CHANGED
@@ -49,6 +49,11 @@ export default class Secret extends SteveModel {
49
49
  return this._type === TYPES.CLOUD_CREDENTIAL || (this.metadata.namespace === 'cattle-global-data' && this.metadata.generateName === 'cc-');
50
50
  }
51
51
 
52
+ // For Fleet SSH secrets - does the secret have the 'known_hosts' data key?
53
+ get supportsSshKnownHosts() {
54
+ return this._type === TYPES.SSH && 'known_hosts' in this.data;
55
+ }
56
+
52
57
  get issuer() {
53
58
  const { metadata:{ annotations = {} } } = this;
54
59
 
package/models/service.js CHANGED
@@ -138,6 +138,7 @@ export default class extends SteveModel {
138
138
 
139
139
  async fetchPods() {
140
140
  if (this.podRelationship) {
141
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
141
142
  await this.$dispatch('cluster/findMatching', {
142
143
  type: POD,
143
144
  selector: this.podRelationship.selector,
@@ -554,6 +554,7 @@ export default class Workload extends WorkloadService {
554
554
  if (podRelationship) {
555
555
  const pods = this.$getters['podsByNamespace'](this.metadata.namespace);
556
556
 
557
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
557
558
  return pods.filter((obj) => {
558
559
  return matches(obj, podRelationship.selector);
559
560
  });
@@ -594,6 +595,23 @@ export default class Workload extends WorkloadService {
594
595
  return (get(this, 'metadata.relationships') || []).filter((relationship) => relationship.toType === WORKLOAD_TYPES.JOB);
595
596
  }
596
597
 
598
+ /**
599
+ * Ensure the store has all matching jobs
600
+ */
601
+ async matchingJobs() {
602
+ if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
603
+ return undefined;
604
+ }
605
+
606
+ // This will be 1 request per relationship, though there's not likely to be many per cron job
607
+ return Promise.all(this.jobRelationships.map((obj) => {
608
+ return this.$dispatch('find', { type: WORKLOAD_TYPES.JOB, id: obj.toId });
609
+ }));
610
+ }
611
+
612
+ /**
613
+ * Expects all required pods are fetched upfront
614
+ */
597
615
  get jobs() {
598
616
  if (this.type !== WORKLOAD_TYPES.CRON_JOB) {
599
617
  return undefined;
@@ -643,12 +661,12 @@ export default class Workload extends WorkloadService {
643
661
  }
644
662
 
645
663
  async matchingPods() {
664
+ // Used in conjunction with `matches/match/label selectors`. Requires https://github.com/rancher/dashboard/issues/10417 to fix
646
665
  const all = await this.$dispatch('findAll', { type: POD });
647
666
  const allInNamespace = all.filter((pod) => pod.metadata.namespace === this.metadata.namespace);
648
667
 
649
668
  const selector = convertSelectorObj(this.spec.selector);
650
669
 
651
- // See https://github.com/rancher/dashboard/issues/10417, all pods bad, need to replace local selector somehow
652
670
  return matching(allInNamespace, selector);
653
671
  }
654
672