@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.
- package/assets/brand/harvester/favicon.png +0 -0
- package/assets/brand/harvester/metadata.json +3 -0
- package/assets/images/pl/harvester.svg +1 -0
- package/assets/translations/en-us.yaml +26 -8
- package/assets/translations/zh-hans.yaml +1 -1
- package/components/BrandImage.vue +5 -1
- package/components/CopyToClipboardText.vue +2 -0
- package/components/CruResourceFooter.vue +1 -0
- package/components/DetailTop.vue +1 -1
- package/components/ExplorerMembers.vue +5 -1
- package/components/ExplorerProjectsNamespaces.vue +39 -15
- package/components/HardwareResourceGauge.vue +12 -2
- package/components/InputOrDisplay.vue +6 -2
- package/components/LandingPagePreference.vue +2 -2
- package/components/MessageLink.vue +1 -1
- package/components/ResourceDetail/Masthead.vue +22 -1
- package/components/ResourceDetail/index.vue +2 -8
- package/components/ResourceTable.vue +14 -6
- package/components/ResourceYaml.vue +1 -1
- package/components/SideNav.vue +6 -4
- package/components/TableDataUserIcon.vue +1 -1
- package/components/fleet/FleetRepos.vue +0 -7
- package/components/form/ArrayList.vue +5 -1
- package/components/form/ArrayListSelect.vue +5 -1
- package/components/form/KeyValue.vue +1 -1
- package/components/form/LabeledSelect.vue +26 -6
- package/components/form/Password.vue +7 -1
- package/components/form/UnitInput.vue +10 -1
- package/components/form/__tests__/UnitInput.test.ts +1 -0
- package/components/formatter/ClusterProvider.vue +3 -3
- package/components/formatter/ImagePercentageBar.vue +1 -1
- package/components/formatter/Si.vue +5 -1
- package/components/formatter/Translate.vue +1 -1
- package/components/nav/Header.vue +29 -5
- package/components/nav/NamespaceFilter.vue +5 -8
- package/components/nav/TopLevelMenu.vue +16 -14
- package/config/labels-annotations.js +2 -0
- package/config/table-headers.js +15 -0
- package/config/types.js +3 -0
- package/detail/fleet.cattle.io.bundle.vue +5 -68
- package/detail/fleet.cattle.io.gitrepo.vue +2 -1
- package/directives/clean-tooltip.js +4 -4
- package/edit/constraints.gatekeeper.sh.constraint/index.vue +5 -2
- package/edit/logging-flow/Match.vue +75 -42
- package/edit/logging-flow/index.vue +89 -10
- package/edit/logging.banzaicloud.io.output/index.vue +2 -2
- package/edit/management.cattle.io.project.vue +2 -2
- package/edit/namespace.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +17 -7
- package/edit/provisioning.cattle.io.cluster/index.vue +2 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +20 -6
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +1 -1
- package/list/harvesterhci.io.management.cluster.vue +244 -0
- package/list/namespace.vue +16 -4
- package/models/__tests__/management.cattle.io.cluster.test.ts +45 -3
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +0 -86
- package/models/fleet.cattle.io.bundle.js +3 -1
- package/models/fleet.cattle.io.gitrepo.js +46 -48
- package/models/k8s.cni.cncf.io.networkattachmentdefinition.js +88 -0
- package/models/management.cattle.io.cluster.js +26 -5
- package/models/management.cattle.io.setting.js +25 -0
- package/models/provisioning.cattle.io.cluster.js +5 -14
- package/models/storage.k8s.io.storageclass.js +15 -4
- package/package.json +8 -6
- package/pages/auth/login.vue +8 -2
- package/pages/auth/setup.vue +1 -1
- package/pages/c/_cluster/fleet/index.vue +2 -4
- package/pages/c/_cluster/settings/brand.vue +4 -1
- package/pages/prefs.vue +22 -10
- package/plugins/dashboard-store/resource-class.js +11 -3
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +7 -2
- package/scripts/extension/parse-tag-name +19 -12
- package/scripts/publish-shell.sh +10 -2
- package/scripts/test-plugins-build.sh +8 -9
- package/scripts/typegen.sh +2 -0
- package/store/features.js +1 -0
- package/store/i18n.js +5 -1
- package/store/prefs.js +8 -0
- package/types/resources/fleet.d.ts +40 -0
- package/types/shell/index.d.ts +524 -396
- package/utils/auth.js +1 -1
- package/utils/create-yaml.js +1 -1
- package/utils/favicon.js +2 -0
- package/utils/fleet.ts +159 -0
- package/utils/gc/gc.ts +1 -1
- package/utils/v-sphere.ts +31 -0
- package/utils/validators/cron-schedule.js +1 -1
- package/utils/validators/formRules/index.ts +1 -1
- package/utils/version.js +1 -1
- 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>
|
package/list/namespace.vue
CHANGED
|
@@ -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' }, '
|
|
11
|
-
[{ provider: 'k3s', driver: 'K3S' }, '
|
|
12
|
-
[{ provider: 'aks', driver: '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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
329
|
-
const
|
|
330
|
-
|
|
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
|
|
335
|
-
const
|
|
336
|
-
|
|
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
|
-
|
|
340
|
-
|
|
342
|
+
if (!c) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
341
345
|
|
|
342
|
-
|
|
343
|
-
namespacedName = `${ r.namespace }:${ r.name }`;
|
|
344
|
-
}
|
|
346
|
+
const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
|
|
345
347
|
|
|
346
|
-
|
|
347
|
-
const
|
|
348
|
-
const
|
|
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:
|
|
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
|
|
374
|
-
tableKey:
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
clusterName:
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
+
}
|