@rancher/shell 3.0.0 → 3.0.1-rc.2

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 (92) hide show
  1. package/assets/brand/harvester/favicon.png +0 -0
  2. package/assets/brand/harvester/metadata.json +3 -0
  3. package/assets/images/pl/harvester.svg +1 -0
  4. package/assets/translations/en-us.yaml +26 -8
  5. package/assets/translations/zh-hans.yaml +1 -1
  6. package/components/BrandImage.vue +5 -1
  7. package/components/CopyToClipboardText.vue +2 -0
  8. package/components/CruResourceFooter.vue +1 -0
  9. package/components/DetailTop.vue +1 -1
  10. package/components/ExplorerMembers.vue +5 -1
  11. package/components/ExplorerProjectsNamespaces.vue +39 -15
  12. package/components/HardwareResourceGauge.vue +12 -2
  13. package/components/InputOrDisplay.vue +6 -2
  14. package/components/LandingPagePreference.vue +2 -2
  15. package/components/MessageLink.vue +1 -1
  16. package/components/ResourceDetail/Masthead.vue +22 -1
  17. package/components/ResourceDetail/index.vue +2 -8
  18. package/components/ResourceTable.vue +14 -6
  19. package/components/ResourceYaml.vue +1 -1
  20. package/components/SideNav.vue +6 -4
  21. package/components/TableDataUserIcon.vue +1 -1
  22. package/components/fleet/FleetRepos.vue +0 -7
  23. package/components/form/ArrayList.vue +5 -1
  24. package/components/form/ArrayListSelect.vue +5 -1
  25. package/components/form/KeyValue.vue +1 -1
  26. package/components/form/LabeledSelect.vue +26 -6
  27. package/components/form/Password.vue +7 -1
  28. package/components/form/UnitInput.vue +10 -1
  29. package/components/form/__tests__/UnitInput.test.ts +1 -0
  30. package/components/formatter/ClusterProvider.vue +3 -3
  31. package/components/formatter/ImagePercentageBar.vue +1 -1
  32. package/components/formatter/Si.vue +5 -1
  33. package/components/formatter/Translate.vue +1 -1
  34. package/components/nav/Header.vue +29 -5
  35. package/components/nav/NamespaceFilter.vue +5 -8
  36. package/components/nav/TopLevelMenu.vue +16 -14
  37. package/config/labels-annotations.js +2 -0
  38. package/config/table-headers.js +15 -0
  39. package/config/types.js +3 -0
  40. package/detail/fleet.cattle.io.bundle.vue +5 -68
  41. package/detail/fleet.cattle.io.gitrepo.vue +2 -1
  42. package/directives/clean-tooltip.js +4 -4
  43. package/edit/constraints.gatekeeper.sh.constraint/index.vue +5 -2
  44. package/edit/logging-flow/Match.vue +75 -42
  45. package/edit/logging-flow/index.vue +89 -10
  46. package/edit/logging.banzaicloud.io.output/index.vue +2 -2
  47. package/edit/management.cattle.io.project.vue +2 -2
  48. package/edit/namespace.vue +1 -1
  49. package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +17 -7
  50. package/edit/provisioning.cattle.io.cluster/index.vue +2 -1
  51. package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -3
  52. package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +20 -6
  53. package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +1 -1
  54. package/list/harvesterhci.io.management.cluster.vue +244 -0
  55. package/list/namespace.vue +16 -4
  56. package/models/__tests__/management.cattle.io.cluster.test.ts +45 -3
  57. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +0 -86
  58. package/models/fleet.cattle.io.bundle.js +3 -1
  59. package/models/fleet.cattle.io.gitrepo.js +46 -48
  60. package/models/k8s.cni.cncf.io.networkattachmentdefinition.js +88 -0
  61. package/models/management.cattle.io.cluster.js +26 -5
  62. package/models/management.cattle.io.setting.js +25 -0
  63. package/models/provisioning.cattle.io.cluster.js +5 -14
  64. package/models/storage.k8s.io.storageclass.js +15 -4
  65. package/package.json +8 -6
  66. package/pages/auth/login.vue +8 -2
  67. package/pages/auth/setup.vue +1 -1
  68. package/pages/c/_cluster/fleet/index.vue +2 -4
  69. package/pages/c/_cluster/settings/brand.vue +4 -1
  70. package/pages/prefs.vue +22 -10
  71. package/plugins/dashboard-store/resource-class.js +11 -3
  72. package/plugins/steve/steve-pagination-utils.ts +1 -1
  73. package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -2
  74. package/scripts/extension/parse-tag-name +19 -12
  75. package/scripts/publish-shell.sh +10 -2
  76. package/scripts/test-plugins-build.sh +8 -9
  77. package/scripts/typegen.sh +2 -0
  78. package/store/features.js +1 -0
  79. package/store/i18n.js +5 -1
  80. package/store/prefs.js +8 -0
  81. package/types/resources/fleet.d.ts +40 -0
  82. package/types/shell/index.d.ts +524 -396
  83. package/utils/auth.js +1 -1
  84. package/utils/create-yaml.js +1 -1
  85. package/utils/favicon.js +2 -0
  86. package/utils/fleet.ts +159 -0
  87. package/utils/gc/gc.ts +1 -1
  88. package/utils/v-sphere.ts +31 -0
  89. package/utils/validators/cron-schedule.js +1 -1
  90. package/utils/validators/formRules/index.ts +1 -1
  91. package/utils/version.js +1 -1
  92. package/vue.config.js +2 -2
@@ -0,0 +1,244 @@
1
+ <script>
2
+ import BrandImage from '@shell/components/BrandImage';
3
+ import TypeDescription from '@shell/components/TypeDescription';
4
+ import ResourceTable from '@shell/components/ResourceTable';
5
+ import Masthead from '@shell/components/ResourceList/Masthead';
6
+ import Loading from '@shell/components/Loading';
7
+ import { HARVESTER_NAME as VIRTUAL } from '@shell/config/features';
8
+ import { CAPI, HCI, MANAGEMENT } from '@shell/config/types';
9
+ import { isHarvesterCluster } from '@shell/utils/cluster';
10
+ import { allHash } from '@shell/utils/promise';
11
+
12
+ export default {
13
+ components: {
14
+ BrandImage,
15
+ ResourceTable,
16
+ Masthead,
17
+ TypeDescription,
18
+ Loading
19
+ },
20
+
21
+ props: {
22
+ schema: {
23
+ type: Object,
24
+ required: true,
25
+ },
26
+ useQueryParamsForSimpleFiltering: {
27
+ type: Boolean,
28
+ default: false
29
+ }
30
+ },
31
+
32
+ async fetch() {
33
+ const inStore = this.$store.getters['currentProduct'].inStore;
34
+
35
+ const hash = await allHash({
36
+ hciClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: HCI.CLUSTER }),
37
+ mgmtClusters: this.$store.dispatch(`${ inStore }/findAll`, { type: MANAGEMENT.CLUSTER })
38
+ });
39
+
40
+ this.hciClusters = hash.hciClusters;
41
+ this.mgmtClusters = hash.mgmtClusters;
42
+ },
43
+
44
+ data() {
45
+ const resource = CAPI.RANCHER_CLUSTER;
46
+
47
+ return {
48
+ navigating: false,
49
+ VIRTUAL,
50
+ hciDashboard: HCI.DASHBOARD,
51
+ resource,
52
+ hResource: HCI.CLUSTER,
53
+ hciClusters: [],
54
+ mgmtClusters: []
55
+ };
56
+ },
57
+
58
+ computed: {
59
+ realSchema() {
60
+ return this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER);
61
+ },
62
+
63
+ importLocation() {
64
+ return {
65
+ name: 'c-cluster-product-resource-create',
66
+ params: {
67
+ product: this.$store.getters['currentProduct'].name,
68
+ resource: this.schema.id,
69
+ },
70
+ };
71
+ },
72
+
73
+ canCreateCluster() {
74
+ const schema = this.$store.getters['management/schemaFor'](CAPI.RANCHER_CLUSTER);
75
+
76
+ return !!schema?.collectionMethods.find((x) => x.toLowerCase() === 'post');
77
+ },
78
+
79
+ rows() {
80
+ return this.hciClusters.filter((c) => {
81
+ const cluster = this.mgmtClusters.find((cluster) => cluster?.metadata?.name === c?.status?.clusterName);
82
+
83
+ return isHarvesterCluster(cluster);
84
+ });
85
+ },
86
+
87
+ typeDisplay() {
88
+ return this.t(`typeLabel."${ HCI.CLUSTER }"`, { count: this.row?.length || 0 });
89
+ },
90
+ },
91
+
92
+ methods: {
93
+ async goToCluster(row) {
94
+ const timeout = setTimeout(() => {
95
+ // Don't show loading indicator for quickly fetched plugins
96
+ this.navigating = row.id;
97
+ }, 1000);
98
+
99
+ try {
100
+ await row.goToCluster();
101
+
102
+ clearTimeout(timeout);
103
+ this.navigating = false;
104
+ } catch {
105
+ // The error handling is carried out within goToCluster, but just in case something happens before the promise chain can catch it...
106
+ clearTimeout(timeout);
107
+ this.navigating = false;
108
+ }
109
+ }
110
+ }
111
+ };
112
+ </script>
113
+
114
+ <template>
115
+ <Loading v-if="$fetchState.pending" />
116
+ <div v-else>
117
+ <Masthead
118
+ :schema="realSchema"
119
+ :resource="resource"
120
+ :is-creatable="false"
121
+ :type-display="typeDisplay"
122
+ >
123
+ <template #typeDescription>
124
+ <TypeDescription :resource="hResource" />
125
+ </template>
126
+
127
+ <template
128
+ v-if="canCreateCluster"
129
+ slot="extraActions"
130
+ >
131
+ <n-link
132
+ :to="importLocation"
133
+ class="btn role-primary"
134
+ >
135
+ {{ t('cluster.importAction') }}
136
+ </n-link>
137
+ </template>
138
+ </Masthead>
139
+
140
+ <ResourceTable
141
+ v-if="rows && rows.length"
142
+ :schema="schema"
143
+ :rows="rows"
144
+ :is-creatable="true"
145
+ :namespaced="false"
146
+ :use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
147
+ >
148
+ <template #col:name="{row}">
149
+ <td>
150
+ <span class="cluster-link">
151
+ <a
152
+ v-if="row.isReady"
153
+ class="link"
154
+ :disabled="navigating"
155
+ @click="goToCluster(row)"
156
+ >{{ row.nameDisplay }}</a>
157
+ <span v-else>
158
+ {{ row.nameDisplay }}
159
+ </span>
160
+ <i
161
+ class="icon icon-spinner icon-spin ml-5"
162
+ :class="{'navigating': navigating === row.id}"
163
+ />
164
+ </span>
165
+ </td>
166
+ </template>
167
+
168
+ <template #cell:harvester="{row}">
169
+ <n-link
170
+ class="btn btn-sm role-primary"
171
+ :to="row.detailLocation"
172
+ >
173
+ {{ t('harvesterManager.manage') }}
174
+ </n-link>
175
+ </template>
176
+ </ResourceTable>
177
+ <div v-else>
178
+ <div class="no-clusters">
179
+ {{ t('harvesterManager.cluster.none') }}
180
+ </div>
181
+ <hr class="info-section">
182
+ <div class="logo">
183
+ <BrandImage
184
+ file-name="harvester.png"
185
+ height="64"
186
+ />
187
+ </div>
188
+ <div class="tagline">
189
+ <div>{{ t('harvesterManager.cluster.description') }}</div>
190
+ </div>
191
+ <div class="tagline sub-tagline">
192
+ <div v-clean-html="t('harvesterManager.cluster.learnMore', {}, true)" />
193
+ </div>
194
+ </div>
195
+ </div>
196
+ </template>
197
+
198
+ <style lang="scss" scoped>
199
+ .cluster-link {
200
+ display: flex;
201
+ align-items: center;
202
+
203
+ .icon {
204
+ // Use visibility to avoid the columns re-adjusting when the icon is shown
205
+ visibility: hidden;
206
+
207
+ &.navigating {
208
+ visibility: visible;
209
+ }
210
+ }
211
+
212
+ }
213
+ .no-clusters {
214
+ text-align: center;
215
+ }
216
+
217
+ .info-section {
218
+ margin-top: 60px;
219
+ }
220
+
221
+ .logo {
222
+ display: flex;
223
+ justify-content: center;
224
+ margin: 60px 0 40px 0;
225
+ }
226
+
227
+ .tagline {
228
+ display: flex;
229
+ justify-content: center;
230
+ margin-top: 30px;
231
+
232
+ > div {
233
+ font-size: 16px;
234
+ line-height: 22px;
235
+ max-width: 80%;
236
+ text-align: center;
237
+ }
238
+ }
239
+
240
+ .link {
241
+ cursor: pointer;
242
+ }
243
+
244
+ </style>
@@ -1,7 +1,8 @@
1
1
  <script>
2
2
  import { mapGetters } from 'vuex';
3
+ import { NS_SNAPSHOT_QUOTA } from '../config/table-headers';
3
4
  import ResourceTable from '@shell/components/ResourceTable';
4
-
5
+ import { HCI } from '@shell/config/types';
5
6
  export default {
6
7
  name: 'ListNamespace',
7
8
  components: { ResourceTable },
@@ -27,13 +28,23 @@ export default {
27
28
  default: false
28
29
  }
29
30
  },
30
- data() {
31
- return { asddsa: true };
32
- },
33
31
 
34
32
  computed: {
35
33
  ...mapGetters(['currentProduct']),
34
+ hasHarvesterResourceQuotaSchema() {
35
+ const inStore = this.$store.getters['currentProduct'].inStore;
36
+
37
+ return !!this.$store.getters[`${ inStore }/schemaFor`](HCI.RESOURCE_QUOTA);
38
+ },
39
+ headers() {
40
+ const headersFromSchema = this.$store.getters['type-map/headersFor'](this.schema);
36
41
 
42
+ if (this.hasHarvesterResourceQuotaSchema) {
43
+ headersFromSchema.splice(2, 0, NS_SNAPSHOT_QUOTA);
44
+ }
45
+
46
+ return headersFromSchema;
47
+ },
37
48
  filterRow() {
38
49
  if (this.currentProduct.hideSystemResources) {
39
50
  return this.rows.filter( (N) => {
@@ -56,6 +67,7 @@ export default {
56
67
  v-bind="$attrs"
57
68
  :rows="filterRow"
58
69
  :groupable="false"
70
+ :headers="headers"
59
71
  :schema="schema"
60
72
  key-field="_key"
61
73
  :loading="loading"
@@ -4,12 +4,34 @@ jest.mock('@shell/utils/clipboard', () => {
4
4
  return { copyTextToClipboard: jest.fn(() => Promise.resolve({})) };
5
5
  });
6
6
 
7
+ const importedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'rke2' } };
8
+
9
+ const provisionedRKE2ClusterInfo = { status: { driver: 'rke2', provider: 'imported' } };
10
+
11
+ const importedK3sClusterInfo = { status: { driver: 'k3s', provider: 'k3s' } };
12
+
13
+ const provisionedK3sClusterInfo = { status: { driver: 'k3s', provider: 'imported' } };
14
+
15
+ const importedAksClusterInfo = { spec: { aksConfig: { imported: true } }, status: { provider: 'aks', driver: 'AKS' } };
16
+
17
+ const provisionedAksClusterInfo = { spec: { aksConfig: { imported: false } }, status: { provider: 'aks', driver: 'AKS' } };
18
+
19
+ const importedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
20
+
21
+ const provisionedRKE1ClusterInfo = { status: { provider: 'rke', driver: 'rancherKubernetesEngine' } };
22
+
23
+ const localRKE1ClusterInfo = { status: { provider: 'rke', driver: 'imported' } };
24
+
25
+ const localRKE2ClusterInfo = { status: { provider: 'rke2', driver: 'rke2' } };
26
+
27
+ const localEKSClusterInfo = { status: { provider: 'eks', driver: 'imported' } };
28
+
7
29
  describe('class MgmtCluster', () => {
8
30
  describe('provisioner', () => {
9
31
  const testCases = [
10
- [{ provider: 'rke', driver: 'imported' }, 'rke'],
11
- [{ provider: 'k3s', driver: 'K3S' }, 'k3s'],
12
- [{ provider: 'aks', driver: 'AKS' }, 'aks'],
32
+ [{ provider: 'rke', driver: 'imported' }, 'imported'],
33
+ [{ provider: 'k3s', driver: 'K3S' }, 'K3S'],
34
+ [{ provider: 'aks', driver: 'AKS' }, 'AKS'],
13
35
  [{}, 'imported'],
14
36
  ];
15
37
 
@@ -20,4 +42,24 @@ describe('class MgmtCluster', () => {
20
42
  }
21
43
  );
22
44
  });
45
+
46
+ describe('isImported', () => {
47
+ it.each([
48
+ [importedRKE2ClusterInfo, true],
49
+ [provisionedRKE2ClusterInfo, false],
50
+ [importedK3sClusterInfo, true],
51
+ [provisionedK3sClusterInfo, false],
52
+ [importedAksClusterInfo, true],
53
+ [provisionedAksClusterInfo, false],
54
+ [importedRKE1ClusterInfo, true],
55
+ [provisionedRKE1ClusterInfo, false],
56
+ [localRKE1ClusterInfo, true],
57
+ [localRKE2ClusterInfo, true],
58
+ [localEKSClusterInfo, true]
59
+ ])('should return isImported based on props data', (clusterData, expected) => {
60
+ const cluster = new MgmtCluster(clusterData);
61
+
62
+ expect(cluster.isImported).toBe(expected);
63
+ });
64
+ });
23
65
  });
@@ -1,31 +1,6 @@
1
1
  import ProvCluster from '@shell/models/provisioning.cattle.io.cluster';
2
2
 
3
3
  describe('class ProvCluster', () => {
4
- const importedClusterInfo = {
5
- clusterName: 'test', provisioner: 'imported', mgmt: { spec: { gkeConfig: {} } }, spec: {}
6
- };
7
- const importedGkeClusterInfo = {
8
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: true } } }
9
- };
10
- const importedAksClusterInfo = {
11
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { aksConfig: { imported: true } } }
12
- };
13
- const importedEksClusterInfo = {
14
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { eksConfig: { imported: true } } }
15
- };
16
- const notImportedGkeClusterInfo = {
17
- clusterName: 'test', provisioner: 'rke2', mgmt: { spec: { gkeConfig: { imported: false } }, rkeConfig: {} }
18
- };
19
- const importedClusterInfoWithProviderForEmberParam = {
20
- clusterName: 'test', provisioner: 'rke2', mgmt: { providerForEmberParam: 'import' }
21
- };
22
- const localClusterInfo = {
23
- clusterName: 'test', provisioner: 'imported', mgmt: { isLocal: true, spec: { gkeConfig: {} } }, spec: {}
24
- };
25
- const doRke2Info = {
26
- clusterName: 'test', provisioner: 'rke2', mgmt: { isLocal: false, providerForEmberParam: 'import' }, spec: { rkeConfig: {} }
27
- };
28
-
29
4
  const gkeClusterWithPrivateEndpoint = {
30
5
  clusterName: 'test',
31
6
  provisioner: 'GKE',
@@ -76,67 +51,6 @@ describe('class ProvCluster', () => {
76
51
  });
77
52
  });
78
53
 
79
- describe('isImported', () => {
80
- const testCases = [
81
- [importedClusterInfo, true],
82
- [importedGkeClusterInfo, true],
83
- [importedAksClusterInfo, true],
84
- [importedEksClusterInfo, true],
85
- [notImportedGkeClusterInfo, false],
86
- [importedClusterInfoWithProviderForEmberParam, true],
87
- [localClusterInfo, false],
88
- [doRke2Info, false],
89
- [{}, false],
90
- ];
91
- const resetMocks = () => {
92
- // Clear all mock function calls:
93
- jest.clearAllMocks();
94
- };
95
-
96
- it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Boolean) => {
97
- const cluster = new ProvCluster({ spec: clusterData.spec });
98
-
99
- jest.spyOn(cluster, 'mgmt', 'get').mockReturnValue(
100
- clusterData.mgmt
101
- );
102
- jest.spyOn(cluster, 'provisioner', 'get').mockReturnValue(
103
- clusterData.provisioner
104
- );
105
-
106
- expect(cluster.isImported).toBe(expected);
107
- resetMocks();
108
- }
109
- );
110
- });
111
-
112
- describe('mgmt', () => {
113
- const testCases = [
114
- [importedClusterInfo, importedClusterInfo.mgmt],
115
- [importedGkeClusterInfo, importedGkeClusterInfo.mgmt],
116
- [importedAksClusterInfo, importedAksClusterInfo.mgmt],
117
- [importedEksClusterInfo, importedEksClusterInfo.mgmt],
118
- [notImportedGkeClusterInfo, notImportedGkeClusterInfo.mgmt],
119
- [importedClusterInfoWithProviderForEmberParam, importedClusterInfoWithProviderForEmberParam.mgmt],
120
- [localClusterInfo, localClusterInfo.mgmt],
121
- [doRke2Info, doRke2Info.mgmt],
122
- [{}, null],
123
- ];
124
-
125
- const resetMocks = () => {
126
- // Clear all mock function calls:
127
- jest.clearAllMocks();
128
- };
129
-
130
- it.each(testCases)('should return the isImported value properly based on the props data', (clusterData: Object, expected: Object) => {
131
- const clusterMock = jest.fn(() => clusterData.mgmt);
132
- const ctx = { rootGetters: { 'management/byId': clusterMock } };
133
- const cluster = new ProvCluster({ status: { clusterName: clusterData.clusterName } }, ctx);
134
-
135
- expect(cluster.mgmt).toBe(expected);
136
- resetMocks();
137
- });
138
- });
139
-
140
54
  describe('hasError', () => {
141
55
  const conditionsWithoutError = [
142
56
  {
@@ -30,7 +30,9 @@ export default class FleetBundle extends SteveModel {
30
30
  }
31
31
 
32
32
  get repoName() {
33
- return this.metadata.labels['fleet.cattle.io/repo-name'];
33
+ const labels = this.metadata?.labels || {};
34
+
35
+ return labels['fleet.cattle.io/repo-name'];
34
36
  }
35
37
 
36
38
  get targetClusters() {
@@ -1,15 +1,16 @@
1
1
  import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
2
2
  import jsyaml from 'js-yaml';
3
- import { escapeHtml, randomStr } from '@shell/utils/string';
3
+ import { escapeHtml } from '@shell/utils/string';
4
4
  import { FLEET } from '@shell/config/types';
5
5
  import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
6
6
  import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
7
7
  import { set } from '@shell/utils/object';
8
8
  import SteveModel from '@shell/plugins/steve/steve-class';
9
9
  import {
10
- STATES_ENUM, colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
10
+ colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
11
11
  } from '@shell/plugins/dashboard-store/resource-class';
12
12
  import { NAME } from '@shell/config/product/explorer';
13
+ import FleetUtils from '@shell/utils/fleet';
13
14
 
14
15
  function quacksLikeAHash(str) {
15
16
  if (str.match(/^[a-f0-9]{40,}$/i)) {
@@ -325,35 +326,29 @@ export default class GitRepo extends SteveModel {
325
326
  }
326
327
 
327
328
  get resourcesStatuses() {
328
- const clusters = this.targetClusters || [];
329
- const resources = this.status?.resources || [];
330
- const conditions = this.status?.conditions || [];
329
+ const bundleDeployments = this.bundleDeployments || [];
330
+ const clusters = (this.targetClusters || []).reduce((res, c) => {
331
+ res[c.id] = c;
332
+
333
+ return res;
334
+ }, {});
331
335
 
332
336
  const out = [];
333
337
 
334
- for (const c of clusters) {
335
- const clusterBundleDeploymentResources = this.bundleDeployments
336
- .find((bd) => bd.metadata?.labels?.[FLEET_ANNOTATIONS.CLUSTER] === c.metadata.name)
337
- ?.status?.resources || [];
338
+ for (const bd of bundleDeployments) {
339
+ const clusterId = FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels);
340
+ const c = clusters[clusterId];
338
341
 
339
- resources.forEach((r, i) => {
340
- let namespacedName = r.name;
342
+ if (!c) {
343
+ continue;
344
+ }
341
345
 
342
- if (r.namespace) {
343
- namespacedName = `${ r.namespace }:${ r.name }`;
344
- }
346
+ const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
345
347
 
346
- let state = r.state;
347
- const perEntry = r.perClusterState?.find((x) => x.clusterId === c.id);
348
- const tooMany = r.perClusterState?.length >= 10 || false;
349
-
350
- if (perEntry) {
351
- state = perEntry.state;
352
- } else if (tooMany) {
353
- state = STATES_ENUM.UNKNOWN;
354
- } else {
355
- state = STATES_ENUM.READY;
356
- }
348
+ resources.forEach((r) => {
349
+ const id = FleetUtils.resourceId(r);
350
+ const type = FleetUtils.resourceType(r);
351
+ const state = r.state;
357
352
 
358
353
  const color = colorForState(state).replace('text-', 'bg-');
359
354
  const display = stateDisplay(state);
@@ -363,33 +358,38 @@ export default class GitRepo extends SteveModel {
363
358
  params: {
364
359
  product: NAME,
365
360
  cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
366
- resource: r.type,
361
+ resource: type,
367
362
  namespace: r.namespace,
368
363
  id: r.name,
369
364
  }
370
365
  };
371
366
 
367
+ const key = `${ c.id }-${ type }-${ r.namespace }-${ r.name }`;
368
+
372
369
  out.push({
373
- key: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }`,
374
- tableKey: `${ r.id }-${ c.id }-${ r.type }-${ r.namespace }-${ r.name }-${ randomStr(8) }`,
375
- kind: r.kind,
376
- apiVersion: r.apiVersion,
377
- type: r.type,
378
- id: r.id,
379
- namespace: r.namespace,
380
- name: r.name,
381
- clusterId: c.id,
382
- clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
383
- clusterName: c.nameDisplay,
384
- state: mapStateToEnum(state),
385
- stateBackground: color,
386
- stateDisplay: display,
387
- stateSort: stateSort(color, display),
388
- namespacedName,
370
+ key,
371
+ tableKey: key,
372
+
373
+ // Needed?
374
+ id,
375
+ type,
376
+ clusterId: c.id,
377
+
378
+ // columns, see FleetResources.vue
379
+ state: mapStateToEnum(state),
380
+ clusterName: c.nameDisplay,
381
+ apiVersion: r.apiVersion,
382
+ kind: r.kind,
383
+ name: r.name,
384
+ namespace: r.namespace,
385
+ creationTimestamp: r.createdAt,
386
+
387
+ // other properties
388
+ clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
389
+ stateBackground: color,
390
+ stateDisplay: display,
391
+ stateSort: stateSort(color, display),
389
392
  detailLocation,
390
- conditions: conditions[i],
391
- bundleDeploymentStatus: clusterBundleDeploymentResources?.[i],
392
- creationTimestamp: clusterBundleDeploymentResources?.[i]?.createdAt
393
393
  });
394
394
  });
395
395
  }
@@ -410,9 +410,7 @@ export default class GitRepo extends SteveModel {
410
410
 
411
411
  get clusterResourceStatus() {
412
412
  const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => {
413
- const { clusterId, clusterLabel } = curr;
414
-
415
- const state = curr.state;
413
+ const { clusterId, clusterLabel, state } = curr;
416
414
 
417
415
  if (!prev[clusterId]) {
418
416
  prev[clusterId] = {
@@ -0,0 +1,88 @@
1
+ import SteveModel from '@shell/plugins/steve/steve-class';
2
+ import { HCI } from '@shell/config/labels-annotations';
3
+
4
+ export default class NetworkAttachmentDef extends SteveModel {
5
+ applyDefaults() {
6
+ const spec = this.spec || {
7
+ config: JSON.stringify({
8
+ cniVersion: '0.3.1',
9
+ name: '',
10
+ type: 'bridge',
11
+ bridge: '',
12
+ promiscMode: true,
13
+ vlan: '',
14
+ ipam: {}
15
+ })
16
+ };
17
+
18
+ this['spec'] = spec;
19
+ }
20
+
21
+ get parseConfig() {
22
+ try {
23
+ return JSON.parse(this.spec.config) || {};
24
+ } catch (err) {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ get isIpamStatic() {
30
+ return this.parseConfig.ipam?.type === 'static';
31
+ }
32
+
33
+ get clusterNetwork() {
34
+ return this?.metadata?.labels?.[HCI.CLUSTER_NETWORK];
35
+ }
36
+
37
+ get vlanType() {
38
+ const labels = this.metadata?.labels || {};
39
+ const type = labels[HCI.NETWORK_TYPE];
40
+
41
+ return type;
42
+ }
43
+
44
+ get vlanId() {
45
+ return this.vlanType === 'UntaggedNetwork' ? 'N/A' : this.parseConfig.vlan;
46
+ }
47
+
48
+ get customValidationRules() {
49
+ const rules = [
50
+ {
51
+ nullable: false,
52
+ path: 'metadata.name',
53
+ required: true,
54
+ minLength: 1,
55
+ maxLength: 63,
56
+ translationKey: 'harvester.fields.name'
57
+ }
58
+ ];
59
+
60
+ return rules;
61
+ }
62
+
63
+ get connectivity() {
64
+ const annotations = this.metadata?.annotations || {};
65
+ const route = annotations[HCI.NETWORK_ROUTE];
66
+ let config = {};
67
+
68
+ if (this.vlanType === 'UntaggedNetwork') {
69
+ return 'N/A';
70
+ }
71
+
72
+ try {
73
+ config = JSON.parse(route || '{}');
74
+ } catch {
75
+ return 'invalid';
76
+ }
77
+
78
+ const connectivity = config.connectivity;
79
+
80
+ if (connectivity === 'false') {
81
+ return 'inactive';
82
+ } else if (connectivity === 'true') {
83
+ return 'active';
84
+ } else {
85
+ return connectivity;
86
+ }
87
+ }
88
+ }