@rancher/shell 2.0.2-rc.1 → 2.0.3
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/translations/en-us.yaml +53 -31
- package/components/PromptRemove.vue +8 -3
- package/components/ResourceDetail/Masthead.vue +1 -0
- package/components/ResourceDetail/index.vue +2 -1
- package/components/SideNav.vue +1 -1
- package/components/TableDataUserIcon.vue +1 -1
- package/components/fleet/FleetClusters.vue +0 -3
- package/components/fleet/FleetRepos.vue +0 -7
- package/components/formatter/CloudCredExpired.vue +69 -0
- package/components/formatter/ClusterProvider.vue +3 -3
- package/components/formatter/Date.vue +1 -1
- package/components/nav/Header.vue +9 -5
- package/components/nav/TopLevelMenu.vue +127 -63
- package/components/nav/__tests__/TopLevelMenu.test.ts +53 -27
- package/config/labels-annotations.js +3 -0
- package/core/types-provisioning.ts +5 -0
- package/core/types.ts +26 -1
- package/detail/catalog.cattle.io.app.vue +17 -4
- package/detail/fleet.cattle.io.bundle.vue +5 -68
- package/detail/fleet.cattle.io.cluster.vue +11 -9
- package/detail/fleet.cattle.io.gitrepo.vue +3 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +109 -24
- package/edit/provisioning.cattle.io.cluster/index.vue +10 -4
- package/edit/provisioning.cattle.io.cluster/rke2.vue +13 -2
- package/edit/provisioning.cattle.io.cluster/tabs/Advanced.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +177 -26
- package/list/provisioning.cattle.io.cluster.vue +56 -5
- package/mixins/chart.js +6 -2
- package/models/__tests__/management.cattle.io.cluster.test.ts +3 -3
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +0 -86
- package/models/catalog.cattle.io.app.js +108 -21
- package/models/cloudcredential.js +159 -2
- package/models/fleet.cattle.io.bundle.js +3 -1
- package/models/fleet.cattle.io.gitrepo.js +50 -61
- package/models/management.cattle.io.cluster.js +15 -7
- package/models/provisioning.cattle.io.cluster.js +62 -15
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/install.vue +2 -1
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +1 -1
- package/pages/c/_cluster/explorer/index.vue +1 -2
- package/pages/c/_cluster/fleet/index.vue +12 -5
- package/pages/c/_cluster/manager/cloudCredential/index.vue +68 -4
- package/pages/c/_cluster/uiplugins/index.vue +4 -2
- package/pages/home.vue +1 -0
- package/scripts/extension/bundle +1 -1
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +0 -2
- package/scripts/extension/parse-tag-name +21 -12
- package/scripts/publish-shell.sh +10 -4
- package/scripts/typegen.sh +27 -22
- package/store/features.js +1 -0
- package/types/resources/fleet.d.ts +40 -0
- package/types/shell/index.d.ts +4692 -0
- package/utils/auth.js +1 -1
- package/utils/cluster.js +1 -1
- package/utils/fleet.ts +159 -0
- package/utils/string.js +9 -0
- package/utils/v-sphere.ts +282 -0
- package/vue.config.js +3 -3
- package/shell/types/shell/index.d.ts +0 -2
|
@@ -1,11 +1,65 @@
|
|
|
1
|
-
import { CAPI } from '@shell/config/labels-annotations';
|
|
1
|
+
import { CAPI, CLOUD_CREDENTIALS } 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 { SECRET } from '@shell/config/types';
|
|
4
|
+
import { MANAGEMENT, 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
|
+
};
|
|
7
45
|
|
|
8
46
|
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
|
+
|
|
9
63
|
get hasSensitiveData() {
|
|
10
64
|
return true;
|
|
11
65
|
}
|
|
@@ -177,4 +231,107 @@ export default class CloudCredential extends NormanModel {
|
|
|
177
231
|
get doneRoute() {
|
|
178
232
|
return 'c-cluster-manager-secret';
|
|
179
233
|
}
|
|
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`?
|
|
245
|
+
// Ensures the correct sort order:
|
|
246
|
+
// 'expired' --> 'expiring soon' --> 'never expires'
|
|
247
|
+
return this.expires !== undefined ? this.expires : Number.MAX_SAFE_INTEGER;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
get expires() {
|
|
251
|
+
const expires = this.annotations[CLOUD_CREDENTIALS.EXPIRATION];
|
|
252
|
+
|
|
253
|
+
if (typeof expires === 'string') {
|
|
254
|
+
return parseInt(expires);
|
|
255
|
+
} else if (typeof expires === 'number') {
|
|
256
|
+
return expires;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return undefined; // Weird things happen if this isn't a number
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
get expireData() {
|
|
263
|
+
if (typeof this.expiresIn !== 'number') {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const sevenDays = 1000 * 60 * 60 * 24 * 7;
|
|
268
|
+
|
|
269
|
+
if (this.expiresIn === 0) {
|
|
270
|
+
return {
|
|
271
|
+
expired: true,
|
|
272
|
+
expiring: false,
|
|
273
|
+
};
|
|
274
|
+
} else if (this.expiresIn < sevenDays) {
|
|
275
|
+
return {
|
|
276
|
+
expired: false,
|
|
277
|
+
expiring: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
get expiresString() {
|
|
285
|
+
if (this.expires === undefined) {
|
|
286
|
+
return '';
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (this.expireData.expired) {
|
|
290
|
+
return this.t('manager.cloudCredentials.expired');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const dateFormat = escapeHtml( this.$rootGetters['prefs/get'](DATE_FORMAT));
|
|
294
|
+
const timeFormat = escapeHtml( this.$rootGetters['prefs/get'](TIME_FORMAT));
|
|
295
|
+
|
|
296
|
+
return day(this.expires).format(`${ dateFormat } ${ timeFormat }`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
get expiresIn() {
|
|
300
|
+
if (this.expires === undefined) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const timeThen = this.expires;
|
|
305
|
+
const timeNow = Date.now();
|
|
306
|
+
|
|
307
|
+
const expiresIn = timeThen - timeNow;
|
|
308
|
+
|
|
309
|
+
return expiresIn < 0 ? 0 : expiresIn;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
renew() {
|
|
313
|
+
const renewFn = renew[this.provider]?.renew;
|
|
314
|
+
|
|
315
|
+
if (!renewFn) {
|
|
316
|
+
console.error('No fn renew function for ', this.provider); // eslint-disable-line no-console
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
return renewFn({
|
|
320
|
+
cloudCredential: this,
|
|
321
|
+
$ctx: this.$ctx
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async renewBulk(cloudCredentials = []) {
|
|
326
|
+
const renewBulkFn = renew[this.provider]?.renewBulk;
|
|
327
|
+
|
|
328
|
+
if (!renewBulkFn) {
|
|
329
|
+
console.error('No fn renew bulk function for ', this.provider); // eslint-disable-line no-console
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return renewBulkFn({
|
|
333
|
+
cloudCredentials,
|
|
334
|
+
$ctx: this.$ctx
|
|
335
|
+
});
|
|
336
|
+
}
|
|
180
337
|
}
|
|
@@ -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,16 +1,17 @@
|
|
|
1
1
|
import Vue from 'vue';
|
|
2
2
|
import { convert, matching, convertSelectorObj } from '@shell/utils/selector';
|
|
3
3
|
import jsyaml from 'js-yaml';
|
|
4
|
-
import { escapeHtml
|
|
4
|
+
import { escapeHtml } from '@shell/utils/string';
|
|
5
5
|
import { FLEET } from '@shell/config/types';
|
|
6
6
|
import { FLEET as FLEET_ANNOTATIONS } from '@shell/config/labels-annotations';
|
|
7
7
|
import { addObject, addObjects, findBy, insertAt } from '@shell/utils/array';
|
|
8
8
|
import { set } from '@shell/utils/object';
|
|
9
9
|
import SteveModel from '@shell/plugins/steve/steve-class';
|
|
10
10
|
import {
|
|
11
|
-
|
|
11
|
+
colorForState, mapStateToEnum, primaryDisplayStatusFromCount, stateDisplay, stateSort
|
|
12
12
|
} from '@shell/plugins/dashboard-store/resource-class';
|
|
13
13
|
import { NAME } from '@shell/config/product/explorer';
|
|
14
|
+
import FleetUtils from '@shell/utils/fleet';
|
|
14
15
|
|
|
15
16
|
function quacksLikeAHash(str) {
|
|
16
17
|
if (str.match(/^[a-f0-9]{40,}$/i)) {
|
|
@@ -308,20 +309,11 @@ export default class GitRepo extends SteveModel {
|
|
|
308
309
|
bundle.namespacedName.startsWith(`${ this.namespace }:${ this.name }`));
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
/**
|
|
313
|
+
* Bundles with state of active
|
|
314
|
+
*/
|
|
311
315
|
get bundlesReady() {
|
|
312
|
-
|
|
313
|
-
return this.bundles.filter((bundle) => bundle.state === 'active');
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return 0;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
get targetClustersReady() {
|
|
320
|
-
if (this.targetClusters && this.targetClusters.length) {
|
|
321
|
-
return this.targetClusters.filter((cluster) => cluster.state === 'active');
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return 0;
|
|
316
|
+
return this.bundles?.filter((bundle) => bundle.state === 'active');
|
|
325
317
|
}
|
|
326
318
|
|
|
327
319
|
get bundleDeployments() {
|
|
@@ -331,35 +323,29 @@ export default class GitRepo extends SteveModel {
|
|
|
331
323
|
}
|
|
332
324
|
|
|
333
325
|
get resourcesStatuses() {
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
|
|
326
|
+
const bundleDeployments = this.bundleDeployments || [];
|
|
327
|
+
const clusters = (this.targetClusters || []).reduce((res, c) => {
|
|
328
|
+
res[c.id] = c;
|
|
337
329
|
|
|
338
|
-
|
|
330
|
+
return res;
|
|
331
|
+
}, {});
|
|
339
332
|
|
|
340
|
-
|
|
341
|
-
const clusterBundleDeploymentResources = this.bundleDeployments
|
|
342
|
-
.find((bd) => bd.metadata?.labels?.[FLEET_ANNOTATIONS.CLUSTER] === c.metadata.name)
|
|
343
|
-
?.status?.resources || [];
|
|
333
|
+
const out = [];
|
|
344
334
|
|
|
345
|
-
|
|
346
|
-
|
|
335
|
+
for (const bd of bundleDeployments) {
|
|
336
|
+
const clusterId = FleetUtils.clusterIdFromBundleDeploymentLabels(bd.metadata?.labels);
|
|
337
|
+
const c = clusters[clusterId];
|
|
347
338
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
339
|
+
if (!c) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
351
342
|
|
|
352
|
-
|
|
353
|
-
const perEntry = r.perClusterState?.find((x) => x.clusterId === c.id);
|
|
354
|
-
const tooMany = r.perClusterState?.length >= 10 || false;
|
|
343
|
+
const resources = FleetUtils.resourcesFromBundleDeploymentStatus(bd.status);
|
|
355
344
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
} else {
|
|
361
|
-
state = STATES_ENUM.READY;
|
|
362
|
-
}
|
|
345
|
+
resources.forEach((r) => {
|
|
346
|
+
const id = FleetUtils.resourceId(r);
|
|
347
|
+
const type = FleetUtils.resourceType(r);
|
|
348
|
+
const state = r.state;
|
|
363
349
|
|
|
364
350
|
const color = colorForState(state).replace('text-', 'bg-');
|
|
365
351
|
const display = stateDisplay(state);
|
|
@@ -369,33 +355,38 @@ export default class GitRepo extends SteveModel {
|
|
|
369
355
|
params: {
|
|
370
356
|
product: NAME,
|
|
371
357
|
cluster: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
|
|
372
|
-
resource:
|
|
358
|
+
resource: type,
|
|
373
359
|
namespace: r.namespace,
|
|
374
360
|
id: r.name,
|
|
375
361
|
}
|
|
376
362
|
};
|
|
377
363
|
|
|
364
|
+
const key = `${ c.id }-${ type }-${ r.namespace }-${ r.name }`;
|
|
365
|
+
|
|
378
366
|
out.push({
|
|
379
|
-
key
|
|
380
|
-
tableKey:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
clusterName:
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
367
|
+
key,
|
|
368
|
+
tableKey: key,
|
|
369
|
+
|
|
370
|
+
// Needed?
|
|
371
|
+
id,
|
|
372
|
+
type,
|
|
373
|
+
clusterId: c.id,
|
|
374
|
+
|
|
375
|
+
// columns, see FleetResources.vue
|
|
376
|
+
state: mapStateToEnum(state),
|
|
377
|
+
clusterName: c.nameDisplay,
|
|
378
|
+
apiVersion: r.apiVersion,
|
|
379
|
+
kind: r.kind,
|
|
380
|
+
name: r.name,
|
|
381
|
+
namespace: r.namespace,
|
|
382
|
+
creationTimestamp: r.createdAt,
|
|
383
|
+
|
|
384
|
+
// other properties
|
|
385
|
+
clusterLabel: c.metadata.labels[FLEET_ANNOTATIONS.CLUSTER_NAME],
|
|
386
|
+
stateBackground: color,
|
|
387
|
+
stateDisplay: display,
|
|
388
|
+
stateSort: stateSort(color, display),
|
|
395
389
|
detailLocation,
|
|
396
|
-
conditions: conditions[i],
|
|
397
|
-
bundleDeploymentStatus: clusterBundleDeploymentResources?.[i],
|
|
398
|
-
creationTimestamp: clusterBundleDeploymentResources?.[i]?.createdAt
|
|
399
390
|
});
|
|
400
391
|
});
|
|
401
392
|
}
|
|
@@ -416,9 +407,7 @@ export default class GitRepo extends SteveModel {
|
|
|
416
407
|
|
|
417
408
|
get clusterResourceStatus() {
|
|
418
409
|
const clusterStatuses = this.resourcesStatuses.reduce((prev, curr) => {
|
|
419
|
-
const { clusterId, clusterLabel } = curr;
|
|
420
|
-
|
|
421
|
-
const state = curr.state;
|
|
410
|
+
const { clusterId, clusterLabel, state } = curr;
|
|
422
411
|
|
|
423
412
|
if (!prev[clusterId]) {
|
|
424
413
|
prev[clusterId] = {
|
|
@@ -16,6 +16,8 @@ import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
|
16
16
|
import { PINNED_CLUSTERS } from '@shell/store/prefs';
|
|
17
17
|
import { copyTextToClipboard } from '@shell/utils/clipboard';
|
|
18
18
|
|
|
19
|
+
const DEFAULT_BADGE_COLOR = '#707070';
|
|
20
|
+
|
|
19
21
|
// See translation file cluster.providers for list of providers
|
|
20
22
|
// If the logo is not named with the provider name, add an override here
|
|
21
23
|
const PROVIDER_LOGO_OVERRIDE = {};
|
|
@@ -88,10 +90,6 @@ export default class MgmtCluster extends SteveModel {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
get provisioner() {
|
|
91
|
-
if (this.status?.provider ) {
|
|
92
|
-
return this.status.provider;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
93
|
// For imported K3s clusters, this.status.driver is 'k3s.'
|
|
96
94
|
return this.status?.driver ? this.status.driver : 'imported';
|
|
97
95
|
}
|
|
@@ -115,7 +113,8 @@ export default class MgmtCluster extends SteveModel {
|
|
|
115
113
|
get providerForEmberParam() {
|
|
116
114
|
// Ember wants one word called provider to tell what component to show, but has much indirect mapping to figure out what it is.
|
|
117
115
|
let provider;
|
|
118
|
-
|
|
116
|
+
|
|
117
|
+
// provisioner is status.driver
|
|
119
118
|
const provisioner = KONTAINER_TO_DRIVER[(this.provisioner || '').toLowerCase()] || this.provisioner;
|
|
120
119
|
|
|
121
120
|
if ( provisioner === 'rancherKubernetesEngine' ) {
|
|
@@ -306,13 +305,22 @@ export default class MgmtCluster extends SteveModel {
|
|
|
306
305
|
return undefined;
|
|
307
306
|
}
|
|
308
307
|
|
|
309
|
-
|
|
308
|
+
let color = this.metadata?.annotations[CLUSTER_BADGE.COLOR] || DEFAULT_BADGE_COLOR;
|
|
310
309
|
const iconText = this.metadata?.annotations[CLUSTER_BADGE.ICON_TEXT] || '';
|
|
310
|
+
let foregroundColor;
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
foregroundColor = textColor(parseColor(color.trim())); // Remove any whitespace
|
|
314
|
+
} catch (_e) {
|
|
315
|
+
// If we could not parse the badge color, use the defaults
|
|
316
|
+
color = DEFAULT_BADGE_COLOR;
|
|
317
|
+
foregroundColor = textColor(parseColor(color));
|
|
318
|
+
}
|
|
311
319
|
|
|
312
320
|
return {
|
|
313
321
|
text: comment || undefined,
|
|
314
322
|
color,
|
|
315
|
-
textColor:
|
|
323
|
+
textColor: foregroundColor,
|
|
316
324
|
iconText: iconText.substr(0, 3)
|
|
317
325
|
};
|
|
318
326
|
}
|
|
@@ -10,7 +10,6 @@ import { compare } from '@shell/utils/version';
|
|
|
10
10
|
import { AS, MODE, _VIEW, _YAML } from '@shell/config/query-params';
|
|
11
11
|
import { HARVESTER_NAME as HARVESTER } from '@shell/config/features';
|
|
12
12
|
import { CAPI as CAPI_ANNOTATIONS, NODE_ARCHITECTURE } from '@shell/config/labels-annotations';
|
|
13
|
-
import capitalize from 'lodash/capitalize';
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
15
|
* Class representing Cluster resource.
|
|
@@ -166,6 +165,19 @@ export default class ProvCluster extends SteveModel {
|
|
|
166
165
|
enabled: canSaveRKETemplate,
|
|
167
166
|
}, { divider: true }];
|
|
168
167
|
|
|
168
|
+
// Harvester Cluster 1:1 Harvester Cloud Cred
|
|
169
|
+
if (this.cloudCredential?.canRenew || this.cloudCredential?.canBulkRenew) {
|
|
170
|
+
out.splice(0, 0, { divider: true });
|
|
171
|
+
out.splice(0, 0, {
|
|
172
|
+
action: 'renew',
|
|
173
|
+
enabled: this.cloudCredential?.canRenew,
|
|
174
|
+
bulkable: this.cloudCredential?.canBulkRenew,
|
|
175
|
+
bulkAction: 'renewBulk',
|
|
176
|
+
icon: 'icon icon-fw icon-refresh',
|
|
177
|
+
label: this.$rootGetters['i18n/t']('cluster.cloudCredentials.renew'),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
169
181
|
return actions.concat(out);
|
|
170
182
|
}
|
|
171
183
|
|
|
@@ -270,19 +282,29 @@ export default class ProvCluster extends SteveModel {
|
|
|
270
282
|
}
|
|
271
283
|
|
|
272
284
|
get isImported() {
|
|
273
|
-
|
|
274
|
-
|
|
285
|
+
if (this.isLocal) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
275
288
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
// imported rke2 and k3s have status.driver === rke2 and k3s respectively
|
|
290
|
+
// Provisioned rke2 and k3s have status.driver === imported
|
|
291
|
+
if (this.mgmt?.status?.provider === 'k3s' || this.mgmt?.status?.provider === 'rke2') {
|
|
292
|
+
return this.mgmt?.status?.driver === this.mgmt?.status?.provider;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// imported KEv2
|
|
296
|
+
// we can't rely on this.provisioner to determine imported-ness for these clusters, as it will return 'aks' 'eks' 'gke' for both provisioned and imported clusters
|
|
297
|
+
const kontainerConfigs = ['aksConfig', 'eksConfig', 'gkeConfig'];
|
|
298
|
+
|
|
299
|
+
const isImportedKontainer = kontainerConfigs.filter((key) => {
|
|
300
|
+
return this.mgmt?.spec?.[key]?.imported === true;
|
|
301
|
+
}).length;
|
|
302
|
+
|
|
303
|
+
if (isImportedKontainer) {
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
284
306
|
|
|
285
|
-
return
|
|
307
|
+
return this.provisioner === 'imported';
|
|
286
308
|
}
|
|
287
309
|
|
|
288
310
|
get isCustom() {
|
|
@@ -318,7 +340,8 @@ export default class ProvCluster extends SteveModel {
|
|
|
318
340
|
}
|
|
319
341
|
|
|
320
342
|
get isRke1() {
|
|
321
|
-
|
|
343
|
+
// rancherKubernetesEngineConfig is not defined on imported RKE1 clusters
|
|
344
|
+
return !!this.mgmt?.spec?.rancherKubernetesEngineConfig || this.mgmt?.labels['provider.cattle.io'] === 'rke';
|
|
322
345
|
}
|
|
323
346
|
|
|
324
347
|
get isHarvester() {
|
|
@@ -395,6 +418,8 @@ export default class ProvCluster extends SteveModel {
|
|
|
395
418
|
provisioner = 'k3s';
|
|
396
419
|
} else if ( this.isImportedRke2 ) {
|
|
397
420
|
provisioner = 'rke2';
|
|
421
|
+
} else if ((this.isImported || this.isLocal) && this.isRke1) {
|
|
422
|
+
provisioner = 'rke';
|
|
398
423
|
}
|
|
399
424
|
|
|
400
425
|
return this.$rootGetters['i18n/withFallback'](`cluster.provider."${ provisioner }"`, null, ucFirst(provisioner));
|
|
@@ -411,7 +436,7 @@ export default class ProvCluster extends SteveModel {
|
|
|
411
436
|
if (!node.metadata?.state?.transitioning) {
|
|
412
437
|
const architecture = node.status?.nodeLabels?.[NODE_ARCHITECTURE];
|
|
413
438
|
|
|
414
|
-
const key = architecture
|
|
439
|
+
const key = architecture || this.t('cluster.architecture.label.unknown');
|
|
415
440
|
|
|
416
441
|
obj[key] = (obj[key] || 0) + 1;
|
|
417
442
|
}
|
|
@@ -885,7 +910,8 @@ export default class ProvCluster extends SteveModel {
|
|
|
885
910
|
get agentConfig() {
|
|
886
911
|
// The one we want is the first one with no selector.
|
|
887
912
|
// If there are multiple with no selector, that will fall under the unsupported message below.
|
|
888
|
-
return this.spec.rkeConfig
|
|
913
|
+
return this.spec.rkeConfig?.machineSelectorConfig
|
|
914
|
+
?.find((x) => !x.machineLabelSelector)?.config || { };
|
|
889
915
|
}
|
|
890
916
|
|
|
891
917
|
get cloudProvider() {
|
|
@@ -990,4 +1016,25 @@ export default class ProvCluster extends SteveModel {
|
|
|
990
1016
|
get description() {
|
|
991
1017
|
return super.description || this.mgmt?.description;
|
|
992
1018
|
}
|
|
1019
|
+
|
|
1020
|
+
renew() {
|
|
1021
|
+
return this.cloudCredential?.renew();
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
renewBulk(clusters = []) {
|
|
1025
|
+
// In theory we don't need to filter by cloudCred, but do so for safety
|
|
1026
|
+
const cloudCredentials = clusters.filter((c) => c.cloudCredential).map((c) => c.cloudCredential);
|
|
1027
|
+
|
|
1028
|
+
return this.cloudCredential?.renewBulk(cloudCredentials);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
get cloudCredential() {
|
|
1032
|
+
return this.$rootGetters['rancher/all'](NORMAN.CLOUD_CREDENTIAL).find((cc) => cc.id === this.spec.cloudCredentialSecretName);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
get cloudCredentialWarning() {
|
|
1036
|
+
const expireData = this.cloudCredential?.expireData;
|
|
1037
|
+
|
|
1038
|
+
return expireData?.expired || expireData?.expiring;
|
|
1039
|
+
}
|
|
993
1040
|
}
|
package/package.json
CHANGED
|
@@ -302,8 +302,9 @@ export default {
|
|
|
302
302
|
*/
|
|
303
303
|
userValues = diff(this.loadedVersionValues, this.chartValues);
|
|
304
304
|
} else if ( this.existing ) {
|
|
305
|
+
await this.existing.fetchValues(); // In theory this has already been called, but do again to be safe
|
|
305
306
|
/* For an already installed app, use the values from the previous install. */
|
|
306
|
-
userValues = clone(this.existing.
|
|
307
|
+
userValues = clone(this.existing.values || {});
|
|
307
308
|
} else {
|
|
308
309
|
/* For an new app, start empty. */
|
|
309
310
|
userValues = {};
|
|
@@ -163,7 +163,7 @@ describe('page: cluster dashboard', () => {
|
|
|
163
163
|
['created', 'glance.created', []],
|
|
164
164
|
['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { [NODE_ARCHITECTURE]: 'intel' } }]],
|
|
165
165
|
['architecture', 'mixed', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }, { labels: { } }]],
|
|
166
|
-
['architecture', '
|
|
166
|
+
['architecture', 'amd64', [{ labels: { [NODE_ARCHITECTURE]: 'amd64' } }]],
|
|
167
167
|
['architecture', 'unknown', [{ labels: { } }]],
|
|
168
168
|
['architecture', '—', [{ metadata: { state: { transitioning: true } } }]],
|
|
169
169
|
])('should show %p label %p', (label, text, nodes) => {
|
|
@@ -47,7 +47,6 @@ import Certificates from '@shell/components/Certificates';
|
|
|
47
47
|
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
48
48
|
import TabTitle from '@shell/components/TabTitle';
|
|
49
49
|
import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
|
|
50
|
-
import capitalize from 'lodash/capitalize';
|
|
51
50
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
52
51
|
|
|
53
52
|
export const RESOURCES = [NAMESPACE, INGRESS, PV, WORKLOAD_TYPES.DEPLOYMENT, WORKLOAD_TYPES.STATEFUL_SET, WORKLOAD_TYPES.JOB, WORKLOAD_TYPES.DAEMON_SET, SERVICE];
|
|
@@ -216,7 +215,7 @@ export default {
|
|
|
216
215
|
if (!node.metadata?.state?.transitioning) {
|
|
217
216
|
const architecture = node.labels?.[NODE_ARCHITECTURE];
|
|
218
217
|
|
|
219
|
-
const key = architecture
|
|
218
|
+
const key = architecture || this.t('cluster.architecture.label.unknown');
|
|
220
219
|
|
|
221
220
|
obj[key] = (obj[key] || 0) + 1;
|
|
222
221
|
}
|
|
@@ -12,6 +12,7 @@ import { WORKSPACE_ANNOTATION } from '@shell/config/labels-annotations';
|
|
|
12
12
|
import { filterBy } from '@shell/utils/array';
|
|
13
13
|
import FleetNoWorkspaces from '@shell/components/fleet/FleetNoWorkspaces.vue';
|
|
14
14
|
import { NAME } from '@shell/config/product/fleet';
|
|
15
|
+
import { xOfy } from '@shell/utils/string';
|
|
15
16
|
|
|
16
17
|
export default {
|
|
17
18
|
name: 'FleetDashboard',
|
|
@@ -39,6 +40,7 @@ export default {
|
|
|
39
40
|
allBundles: {
|
|
40
41
|
inStoreType: 'management',
|
|
41
42
|
type: FLEET.BUNDLE,
|
|
43
|
+
opt: { excludeFields: ['metadata.managedFields', 'spec.resources'] },
|
|
42
44
|
},
|
|
43
45
|
gitRepos: {
|
|
44
46
|
inStoreType: 'management',
|
|
@@ -175,7 +177,12 @@ export default {
|
|
|
175
177
|
}
|
|
176
178
|
|
|
177
179
|
if (area === 'clusters') {
|
|
178
|
-
|
|
180
|
+
if (row.clusterInfo?.ready === row.clusterInfo?.total && row.clusterInfo?.ready) {
|
|
181
|
+
return {
|
|
182
|
+
badgeClass: STATES[STATES_ENUM.ACTIVE].color,
|
|
183
|
+
icon: STATES[STATES_ENUM.ACTIVE].compoundIcon
|
|
184
|
+
};
|
|
185
|
+
}
|
|
179
186
|
} else if (area === 'bundles') {
|
|
180
187
|
group = row.bundles;
|
|
181
188
|
} else if (area === 'resources') {
|
|
@@ -223,7 +230,7 @@ export default {
|
|
|
223
230
|
}
|
|
224
231
|
|
|
225
232
|
if (area === 'clusters') {
|
|
226
|
-
group =
|
|
233
|
+
group = '';
|
|
227
234
|
} else if (area === 'bundles') {
|
|
228
235
|
group = row.bundles;
|
|
229
236
|
} else if (area === 'resources') {
|
|
@@ -262,11 +269,11 @@ export default {
|
|
|
262
269
|
}
|
|
263
270
|
|
|
264
271
|
if (area === 'clusters') {
|
|
265
|
-
|
|
272
|
+
return `${ row.clusterInfo.ready }/${ row.clusterInfo.total }`;
|
|
266
273
|
} else if (area === 'bundles') {
|
|
267
|
-
value =
|
|
274
|
+
value = xOfy(row.bundlesReady?.length, row.bundles?.length);
|
|
268
275
|
} else if (area === 'resources') {
|
|
269
|
-
value =
|
|
276
|
+
value = xOfy(row.status?.resourceCounts?.ready, row.status?.resourceCounts?.desiredReady);
|
|
270
277
|
}
|
|
271
278
|
|
|
272
279
|
return value;
|