@rancher/shell 2.0.1 → 2.0.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/translations/en-us.yaml +73 -34
- package/assets/translations/zh-hans.yaml +1 -0
- package/components/AssignTo.vue +2 -0
- package/components/PromptRemove.vue +8 -3
- package/components/Questions/index.vue +2 -2
- package/components/ResourceDetail/Masthead.vue +1 -0
- package/components/auth/RoleDetailEdit.vue +5 -4
- package/components/fleet/FleetClusters.vue +0 -3
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +11 -3
- package/components/form/labeled-select-utils/labeled-select.utils.ts +1 -1
- package/components/formatter/CloudCredExpired.vue +69 -0
- package/components/formatter/Date.vue +1 -1
- package/components/nav/Header.vue +9 -5
- package/components/nav/TopLevelMenu.vue +115 -51
- package/components/nav/__tests__/TopLevelMenu.test.ts +53 -27
- package/config/labels-annotations.js +2 -0
- package/config/pagination-table-headers.js +5 -4
- package/config/roles.ts +34 -19
- package/config/router/navigation-guards/attempt-first-login.js +1 -1
- package/config/router/navigation-guards/authentication.js +1 -1
- package/config/router/navigation-guards/i18n.js +1 -1
- package/config/router/navigation-guards/index.js +2 -1
- package/config/router/navigation-guards/load-initial-settings.js +1 -1
- package/config/router/navigation-guards/runtime-extension-route.js +31 -0
- package/config/router/routes.js +10 -1
- package/config/uiplugins.js +130 -61
- package/core/plugin.ts +5 -0
- package/core/plugins.js +7 -1
- package/detail/catalog.cattle.io.app.vue +17 -4
- package/detail/fleet.cattle.io.cluster.vue +11 -9
- package/detail/fleet.cattle.io.gitrepo.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +86 -13
- package/edit/provisioning.cattle.io.cluster/__tests__/DirectoryConfig.test.ts +3 -134
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +209 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/rke2.vue +128 -17
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnAdditionalManifest.vue +50 -0
- package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +29 -64
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +42 -3
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +22 -86
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +8 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +61 -0
- package/initialize/entry-helpers.js +4 -21
- package/list/provisioning.cattle.io.cluster.vue +56 -5
- package/mixins/__tests__/chart.test.ts +4 -1
- package/mixins/chart.js +36 -16
- package/models/__tests__/apps.deployment.test.ts +93 -0
- package/models/apps.deployment.js +18 -4
- package/models/catalog.cattle.io.app.js +108 -21
- package/models/cloudcredential.js +159 -2
- package/models/fleet.cattle.io.gitrepo.js +4 -13
- package/models/management.cattle.io.cluster.js +15 -4
- package/models/management.cattle.io.user.js +3 -3
- package/models/nodedriver.js +5 -0
- package/models/provisioning.cattle.io.cluster.js +41 -3
- package/package.json +1 -1
- package/pages/404.vue +15 -0
- package/pages/auth/login.vue +4 -1
- package/pages/auth/setup.vue +4 -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 +6 -2
- package/pages/c/_cluster/fleet/index.vue +11 -5
- package/pages/c/_cluster/manager/cloudCredential/index.vue +68 -4
- package/pages/c/_cluster/manager/jwt.authentication/index.vue +10 -4
- package/pages/c/_cluster/settings/performance.vue +2 -2
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +7 -10
- package/pages/c/_cluster/uiplugins/index.vue +28 -18
- package/pages/home.vue +2 -13
- package/plugins/dashboard-store/actions.js +1 -1
- package/plugins/dashboard-store/getters.js +1 -1
- package/plugins/steve/__tests__/getters.test.ts +5 -5
- package/plugins/steve/getters.js +6 -4
- package/plugins/steve/hybrid-class.js +1 -5
- package/scripts/extension/bundle +1 -1
- package/scripts/extension/helm/charts/ui-plugin-server/Chart.yaml +1 -1
- package/scripts/publish-shell.sh +56 -59
- package/scripts/test-plugins-build.sh +45 -39
- package/scripts/typegen.sh +26 -23
- package/store/type-map.js +4 -2
- package/types/shell/index.d.ts +10 -0
- package/types/store/pagination.types.ts +1 -1
- package/utils/cluster.js +9 -0
- package/utils/settings.ts +3 -1
- package/utils/string.js +9 -0
- package/utils/v-sphere.ts +251 -0
- package/creators/app/app.package.json +0 -14
- package/creators/app/files/.eslintignore +0 -16
- package/creators/app/files/.eslintrc.js +0 -173
- package/creators/app/files/.gitignore +0 -70
- package/creators/app/files/.gitlab-ci.yml +0 -14
- package/creators/app/files/.vscode/settings.json +0 -21
- package/creators/app/files/babel.config.js +0 -1
- package/creators/app/files/tsconfig.json +0 -42
- package/creators/app/files/vue.config.js +0 -6
- package/creators/app/init +0 -120
- package/creators/app/package.json +0 -25
- package/creators/pkg/files/.github/workflows/build-extension-catalog.yml +0 -24
- package/creators/pkg/files/.github/workflows/build-extension-charts.yml +0 -22
- package/creators/pkg/files/babel.config.js +0 -1
- package/creators/pkg/files/index.ts +0 -14
- package/creators/pkg/files/tsconfig.json +0 -53
- package/creators/pkg/files/vue.config.js +0 -1
- package/creators/pkg/init +0 -286
- package/creators/pkg/package.json +0 -19
- package/creators/pkg/pkg.package.json +0 -21
- package/creators/pkg/vue-shim.ts +0 -4
- package/creators/update/init +0 -56
- package/creators/update/package.json +0 -20
- package/creators/update/upgrade +0 -56
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
|
|
2
2
|
<script>
|
|
3
|
-
import { Checkbox } from '@components/Form/Checkbox';
|
|
4
3
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
5
4
|
import { _CREATE } from '@shell/config/query-params';
|
|
6
5
|
|
|
7
6
|
export default {
|
|
8
7
|
name: 'DirectoryConfig',
|
|
9
|
-
components: {
|
|
10
|
-
|
|
11
|
-
LabeledInput,
|
|
12
|
-
},
|
|
13
|
-
props: {
|
|
8
|
+
components: { LabeledInput },
|
|
9
|
+
props: {
|
|
14
10
|
mode: {
|
|
15
11
|
type: String,
|
|
16
12
|
required: true,
|
|
@@ -21,50 +17,11 @@ export default {
|
|
|
21
17
|
required: true,
|
|
22
18
|
},
|
|
23
19
|
},
|
|
24
|
-
data() {
|
|
25
|
-
let atLeastOneDirWithAnIdentifier = false;
|
|
26
|
-
let allDirsWithSameIdentifier = false;
|
|
27
|
-
|
|
28
|
-
if (this.value.systemAgent.length || this.value.provisioning.length || this.value.k8sDistro.length) {
|
|
29
|
-
atLeastOneDirWithAnIdentifier = true;
|
|
30
|
-
if (this.value.systemAgent === this.value.provisioning && this.value.provisioning === this.value.k8sDistro &&
|
|
31
|
-
this.value.systemAgent === this.value.k8sDistro) {
|
|
32
|
-
allDirsWithSameIdentifier = true;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
isSettingCommonConfig: !(atLeastOneDirWithAnIdentifier && !allDirsWithSameIdentifier),
|
|
38
|
-
commonConfig: allDirsWithSameIdentifier ? this.value.systemAgent : '',
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
watch: {
|
|
42
|
-
commonConfig(neu) {
|
|
43
|
-
if (neu && neu.length && this.isSettingCommonConfig) {
|
|
44
|
-
this.value.systemAgent = neu;
|
|
45
|
-
this.value.provisioning = neu;
|
|
46
|
-
this.value.k8sDistro = neu;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
},
|
|
50
20
|
computed: {
|
|
51
21
|
disableEditInput() {
|
|
52
22
|
return this.mode !== _CREATE;
|
|
53
23
|
}
|
|
54
|
-
}
|
|
55
|
-
methods: {
|
|
56
|
-
handleCommonConfig(val) {
|
|
57
|
-
this.isSettingCommonConfig = val;
|
|
58
|
-
|
|
59
|
-
if (val) {
|
|
60
|
-
this.value.systemAgent = '';
|
|
61
|
-
this.value.provisioning = '';
|
|
62
|
-
this.value.k8sDistro = '';
|
|
63
|
-
} else {
|
|
64
|
-
this.commonConfig = '';
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
},
|
|
24
|
+
}
|
|
68
25
|
};
|
|
69
26
|
</script>
|
|
70
27
|
|
|
@@ -74,54 +31,33 @@ export default {
|
|
|
74
31
|
<h3 class="mb-20">
|
|
75
32
|
{{ t('cluster.directoryConfig.title') }}
|
|
76
33
|
</h3>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
34
|
+
<LabeledInput
|
|
35
|
+
v-model="value.systemAgent"
|
|
36
|
+
class="mb-20"
|
|
37
|
+
:mode="mode"
|
|
38
|
+
:label="t('cluster.directoryConfig.systemAgent.label')"
|
|
39
|
+
:tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
|
|
40
|
+
:disabled="disableEditInput"
|
|
41
|
+
data-testid="rke2-directory-config-systemAgent-data-dir"
|
|
42
|
+
/>
|
|
43
|
+
<LabeledInput
|
|
44
|
+
v-model="value.provisioning"
|
|
45
|
+
class="mb-20"
|
|
80
46
|
:mode="mode"
|
|
81
|
-
:label="t('cluster.directoryConfig.
|
|
47
|
+
:label="t('cluster.directoryConfig.provisioning.label')"
|
|
48
|
+
:tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
|
|
82
49
|
:disabled="disableEditInput"
|
|
83
|
-
data-testid="rke2-directory-config-
|
|
84
|
-
@input="handleCommonConfig"
|
|
50
|
+
data-testid="rke2-directory-config-provisioning-data-dir"
|
|
85
51
|
/>
|
|
86
52
|
<LabeledInput
|
|
87
|
-
v-
|
|
88
|
-
v-model="commonConfig"
|
|
53
|
+
v-model="value.k8sDistro"
|
|
89
54
|
class="mb-20"
|
|
90
55
|
:mode="mode"
|
|
91
|
-
:label="t('cluster.directoryConfig.
|
|
92
|
-
:tooltip="t('cluster.directoryConfig.
|
|
56
|
+
:label="t('cluster.directoryConfig.k8sDistro.label')"
|
|
57
|
+
:tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
|
|
93
58
|
:disabled="disableEditInput"
|
|
94
|
-
data-testid="rke2-directory-config-
|
|
59
|
+
data-testid="rke2-directory-config-k8sDistro-data-dir"
|
|
95
60
|
/>
|
|
96
|
-
<div v-if="!isSettingCommonConfig">
|
|
97
|
-
<LabeledInput
|
|
98
|
-
v-model="value.systemAgent"
|
|
99
|
-
class="mb-20"
|
|
100
|
-
:mode="mode"
|
|
101
|
-
:label="t('cluster.directoryConfig.systemAgent.label')"
|
|
102
|
-
:tooltip="t('cluster.directoryConfig.systemAgent.tooltip')"
|
|
103
|
-
:disabled="disableEditInput"
|
|
104
|
-
data-testid="rke2-directory-config-systemAgent-data-dir"
|
|
105
|
-
/>
|
|
106
|
-
<LabeledInput
|
|
107
|
-
v-model="value.provisioning"
|
|
108
|
-
class="mb-20"
|
|
109
|
-
:mode="mode"
|
|
110
|
-
:label="t('cluster.directoryConfig.provisioning.label')"
|
|
111
|
-
:tooltip="t('cluster.directoryConfig.provisioning.tooltip')"
|
|
112
|
-
:disabled="disableEditInput"
|
|
113
|
-
data-testid="rke2-directory-config-provisioning-data-dir"
|
|
114
|
-
/>
|
|
115
|
-
<LabeledInput
|
|
116
|
-
v-model="value.k8sDistro"
|
|
117
|
-
class="mb-20"
|
|
118
|
-
:mode="mode"
|
|
119
|
-
:label="t('cluster.directoryConfig.k8sDistro.label')"
|
|
120
|
-
:tooltip="t('cluster.directoryConfig.k8sDistro.tooltip')"
|
|
121
|
-
:disabled="disableEditInput"
|
|
122
|
-
data-testid="rke2-directory-config-k8sDistro-data-dir"
|
|
123
|
-
/>
|
|
124
|
-
</div>
|
|
125
61
|
<div class="mb-40" />
|
|
126
62
|
</div>
|
|
127
63
|
</div>
|
|
@@ -7,6 +7,7 @@ import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthS
|
|
|
7
7
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
8
8
|
import SecretSelector from '@shell/components/form/SecretSelector';
|
|
9
9
|
import { SECRET_TYPES as TYPES } from '@shell/config/secret';
|
|
10
|
+
import { base64Decode, base64Encode } from '@shell/utils/crypto';
|
|
10
11
|
|
|
11
12
|
export default {
|
|
12
13
|
components: {
|
|
@@ -55,7 +56,7 @@ export default {
|
|
|
55
56
|
if (configMap[hostname]) {
|
|
56
57
|
configMap[hostname].insecureSkipVerify = configMap[hostname].insecureSkipVerify ?? defaultAddValue.insecureSkipVerify;
|
|
57
58
|
configMap[hostname].authConfigSecretName = configMap[hostname].authConfigSecretName ?? defaultAddValue.authConfigSecretName;
|
|
58
|
-
configMap[hostname].caBundle = configMap[hostname].caBundle ?? defaultAddValue.caBundle;
|
|
59
|
+
configMap[hostname].caBundle = base64Decode(configMap[hostname].caBundle ?? defaultAddValue.caBundle);
|
|
59
60
|
configMap[hostname].tlsSecretName = configMap[hostname].tlsSecretName ?? defaultAddValue.tlsSecretName;
|
|
60
61
|
}
|
|
61
62
|
entries.push({
|
|
@@ -94,7 +95,11 @@ export default {
|
|
|
94
95
|
continue;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
configs[h] = {
|
|
98
|
+
configs[h] = {
|
|
99
|
+
...entry,
|
|
100
|
+
caBundle: base64Encode(entry.caBundle)
|
|
101
|
+
};
|
|
102
|
+
|
|
98
103
|
delete configs[h].hostname;
|
|
99
104
|
}
|
|
100
105
|
|
|
@@ -177,6 +182,7 @@ export default {
|
|
|
177
182
|
|
|
178
183
|
<LabeledInput
|
|
179
184
|
v-model="row.value.caBundle"
|
|
185
|
+
:data-testid="`registry-caBundle-${i}`"
|
|
180
186
|
class="mt-20"
|
|
181
187
|
type="multiline"
|
|
182
188
|
label="CA Cert Bundle"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { mount, Wrapper } from '@vue/test-utils';
|
|
2
|
+
import { clone } from '@shell/utils/object';
|
|
3
|
+
import { _EDIT } from '@shell/config/query-params';
|
|
4
|
+
import { PROV_CLUSTER } from '@shell/edit/provisioning.cattle.io.cluster/__tests__/utils/cluster';
|
|
5
|
+
import RegistryConfigs from '@shell/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue';
|
|
6
|
+
|
|
7
|
+
describe('component: RegistryConfigs', () => {
|
|
8
|
+
let wrapper: Wrapper<InstanceType<typeof RegistryConfigs> & { [key: string]: any }>;
|
|
9
|
+
|
|
10
|
+
const mountOptions = {
|
|
11
|
+
propsData: {
|
|
12
|
+
value: {},
|
|
13
|
+
mode: _EDIT,
|
|
14
|
+
clusterRegisterBeforeHook: () => {}
|
|
15
|
+
},
|
|
16
|
+
stubs: {
|
|
17
|
+
SelectOrCreateAuthSecret: true,
|
|
18
|
+
SecretSelector: true,
|
|
19
|
+
},
|
|
20
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
describe('key CA Cert Bundle', () => {
|
|
24
|
+
it('should display default key', () => {
|
|
25
|
+
const value = clone(PROV_CLUSTER);
|
|
26
|
+
|
|
27
|
+
value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
|
|
28
|
+
|
|
29
|
+
mountOptions.propsData.value = value;
|
|
30
|
+
|
|
31
|
+
wrapper = mount(
|
|
32
|
+
RegistryConfigs,
|
|
33
|
+
mountOptions
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const registry = wrapper.find('[data-testid^="registry-caBundle"]').element as HTMLTextAreaElement;
|
|
37
|
+
|
|
38
|
+
expect(registry.value).toBe('foobar');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should update key in base64 format', async() => {
|
|
42
|
+
const value = clone(PROV_CLUSTER);
|
|
43
|
+
|
|
44
|
+
value.spec.rkeConfig.registries.configs = { foo: { caBundle: 'Zm9vYmFy' } };
|
|
45
|
+
|
|
46
|
+
mountOptions.propsData.value = value;
|
|
47
|
+
|
|
48
|
+
wrapper = mount(
|
|
49
|
+
RegistryConfigs,
|
|
50
|
+
mountOptions
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const registry = wrapper.find('[data-testid^="registry-caBundle"]');
|
|
54
|
+
|
|
55
|
+
await registry.setValue('ssh key');
|
|
56
|
+
wrapper.vm.update();
|
|
57
|
+
|
|
58
|
+
expect(wrapper.emitted('updateConfigs')![0][0]['foo']['caBundle']).toBe('c3NoIGtleQ==');
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
});
|
|
@@ -3,7 +3,6 @@ import { updatePageTitle } from '@shell/utils/title';
|
|
|
3
3
|
import { getVendor } from '@shell/config/private-label';
|
|
4
4
|
import middleware from '@shell/config/middleware.js';
|
|
5
5
|
import { withQuery } from 'ufo';
|
|
6
|
-
import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader';
|
|
7
6
|
|
|
8
7
|
// Global variable used on mount, updated on route change and used in the render function
|
|
9
8
|
let app;
|
|
@@ -191,30 +190,14 @@ async function render(to, from, next) {
|
|
|
191
190
|
next: _next.bind(this)
|
|
192
191
|
});
|
|
193
192
|
|
|
193
|
+
if (this.$loading.start && !this.$loading.manual) {
|
|
194
|
+
this.$loading.start();
|
|
195
|
+
}
|
|
196
|
+
|
|
194
197
|
// Get route's matched components
|
|
195
198
|
const matches = [];
|
|
196
199
|
const Components = getMatchedComponents(to, matches);
|
|
197
200
|
|
|
198
|
-
// If no Components matched, generate 404
|
|
199
|
-
if (!Components.length) {
|
|
200
|
-
// Handle the loading of dynamic plugins (Harvester) because we only want to attempt to load those plugins and routes if we first couldn't find a page.
|
|
201
|
-
// We should probably get rid of this concept entirely and just load plugins at the start.
|
|
202
|
-
await app.context.store.dispatch('loadManagement');
|
|
203
|
-
const newLocation = await dynamicPluginLoader.check({ route: { path: window.location.pathname }, store: app.context.store });
|
|
204
|
-
|
|
205
|
-
// If we have a new location, double check that it's actually valid
|
|
206
|
-
const resolvedRoute = newLocation?.path ? app.context.store.app.router.resolve({ path: newLocation.path.replace(/^\/{0,1}dashboard/, '') }) : null;
|
|
207
|
-
|
|
208
|
-
if (resolvedRoute?.route.matched.length) {
|
|
209
|
-
// Note - don't use `redirect` or `store.app.route` (breaks feature by failing to run middleware in default layout)
|
|
210
|
-
return next(resolvedRoute.resolved.path);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
errorRedirect(this, new Error('404: This page could not be found'));
|
|
214
|
-
|
|
215
|
-
return next();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
201
|
try {
|
|
219
202
|
// Call middleware
|
|
220
203
|
await callMiddleware.call(this, Components, app.context);
|
|
@@ -10,10 +10,11 @@ import { mapFeature, HARVESTER as HARVESTER_FEATURE } from '@shell/store/feature
|
|
|
10
10
|
import { NAME as EXPLORER } from '@shell/config/product/explorer';
|
|
11
11
|
import ResourceFetch from '@shell/mixins/resource-fetch';
|
|
12
12
|
import { BadgeState } from '@components/BadgeState';
|
|
13
|
+
import CloudCredExpired from '@shell/components/formatter/CloudCredExpired';
|
|
13
14
|
|
|
14
15
|
export default {
|
|
15
16
|
components: {
|
|
16
|
-
Banner, ResourceTable, Masthead, BadgeState
|
|
17
|
+
Banner, ResourceTable, Masthead, BadgeState, CloudCredExpired
|
|
17
18
|
},
|
|
18
19
|
mixins: [ResourceFetch],
|
|
19
20
|
props: {
|
|
@@ -41,6 +42,8 @@ export default {
|
|
|
41
42
|
mgmtClusters: this.$fetchType(MANAGEMENT.CLUSTER),
|
|
42
43
|
};
|
|
43
44
|
|
|
45
|
+
this.$store.dispatch('rancher/findAll', { type: NORMAN.CLOUD_CREDENTIAL });
|
|
46
|
+
|
|
44
47
|
if ( this.$store.getters['management/canList'](SNAPSHOT) ) {
|
|
45
48
|
hash.etcdSnapshots = this.$fetchType(SNAPSHOT);
|
|
46
49
|
}
|
|
@@ -141,6 +144,29 @@ export default {
|
|
|
141
144
|
// This will be used when there's clusters from extension based provisioners
|
|
142
145
|
// We should re-visit this for scaling reasons
|
|
143
146
|
return this.filteredRows.some((c) => c.metadata.namespace !== 'fleet-local' && c.metadata.namespace !== 'fleet-default');
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
tokenExpiredData() {
|
|
150
|
+
const counts = this.rows.reduce((res, provCluster) => {
|
|
151
|
+
const expireData = provCluster.cloudCredential?.expireData;
|
|
152
|
+
|
|
153
|
+
if (expireData?.expiring) {
|
|
154
|
+
res.expiring++;
|
|
155
|
+
}
|
|
156
|
+
if (expireData?.expired) {
|
|
157
|
+
res.expired++;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return res;
|
|
161
|
+
}, {
|
|
162
|
+
expiring: 0,
|
|
163
|
+
expired: 0
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
expiring: counts.expiring ? this.t('cluster.cloudCredentials.banners.expiring', { count: counts.expiring }) : '',
|
|
168
|
+
expired: counts.expired ? this.t('cluster.cloudCredentials.banners.expired', { count: counts.expired }) : '',
|
|
169
|
+
};
|
|
144
170
|
}
|
|
145
171
|
},
|
|
146
172
|
|
|
@@ -186,6 +212,17 @@ export default {
|
|
|
186
212
|
</template>
|
|
187
213
|
</Masthead>
|
|
188
214
|
|
|
215
|
+
<Banner
|
|
216
|
+
v-if="tokenExpiredData.expiring"
|
|
217
|
+
color="warning"
|
|
218
|
+
:label="tokenExpiredData.expiring"
|
|
219
|
+
/>
|
|
220
|
+
<Banner
|
|
221
|
+
v-if="tokenExpiredData.expired"
|
|
222
|
+
color="error"
|
|
223
|
+
:label="tokenExpiredData.expired"
|
|
224
|
+
/>
|
|
225
|
+
|
|
189
226
|
<ResourceTable
|
|
190
227
|
:schema="schema"
|
|
191
228
|
:rows="filteredRows"
|
|
@@ -194,6 +231,7 @@ export default {
|
|
|
194
231
|
:use-query-params-for-simple-filtering="useQueryParamsForSimpleFiltering"
|
|
195
232
|
:data-testid="'cluster-list'"
|
|
196
233
|
:force-update-live-and-delayed="forceUpdateLiveAndDelayed"
|
|
234
|
+
:sub-rows="true"
|
|
197
235
|
>
|
|
198
236
|
<!-- Why are state column and subrow overwritten here? -->
|
|
199
237
|
<!-- for rke1 clusters, where they try to use the mgmt cluster stateObj instead of prov cluster stateObj, -->
|
|
@@ -207,19 +245,32 @@ export default {
|
|
|
207
245
|
</template>
|
|
208
246
|
<template #sub-row="{fullColspan, row, keyField, componentTestid, i, onRowMouseEnter, onRowMouseLeave}">
|
|
209
247
|
<tr
|
|
210
|
-
v-if="row.stateDescription"
|
|
211
248
|
:key="row[keyField] + '-description'"
|
|
212
249
|
:data-testid="componentTestid + '-' + i + '-row-description'"
|
|
213
250
|
class="state-description sub-row"
|
|
214
251
|
@mouseenter="onRowMouseEnter"
|
|
215
252
|
@mouseleave="onRowMouseLeave"
|
|
216
253
|
>
|
|
217
|
-
<td
|
|
254
|
+
<td v-if="row.cloudCredentialWarning || row.stateDescription">
|
|
255
|
+
|
|
256
|
+
</td>
|
|
218
257
|
<td
|
|
258
|
+
v-if="row.cloudCredentialWarning || row.stateDescription"
|
|
219
259
|
:colspan="fullColspan - 1"
|
|
220
|
-
:class="{ 'text-error' : row.stateObj.error }"
|
|
221
260
|
>
|
|
222
|
-
|
|
261
|
+
<CloudCredExpired
|
|
262
|
+
v-if="row.cloudCredentialWarning"
|
|
263
|
+
:value="row.cloudCredential.expires"
|
|
264
|
+
:row="row.cloudCredential"
|
|
265
|
+
:verbose="true"
|
|
266
|
+
:class="{'mb-10': row.stateDescription}"
|
|
267
|
+
/>
|
|
268
|
+
<div
|
|
269
|
+
v-if="row.stateDescription"
|
|
270
|
+
:class="{ 'text-error' : row.stateObj.error }"
|
|
271
|
+
>
|
|
272
|
+
{{ row.stateDescription }}
|
|
273
|
+
</div>
|
|
223
274
|
</td>
|
|
224
275
|
</tr>
|
|
225
276
|
</template>
|
|
@@ -24,8 +24,9 @@ describe('chartMixin', () => {
|
|
|
24
24
|
localVue.mixin(ChartMixin);
|
|
25
25
|
|
|
26
26
|
it.each(testCases.opa)(
|
|
27
|
-
'should add OPA deprecation warning properly', (chartId, expected) => {
|
|
27
|
+
'should add OPA deprecation warning properly', async(chartId, expected) => {
|
|
28
28
|
const store = new Vuex.Store({
|
|
29
|
+
actions: { 'catalog/load': () => {} },
|
|
29
30
|
getters: {
|
|
30
31
|
currentCluster: () => {},
|
|
31
32
|
isRancher: () => true,
|
|
@@ -44,6 +45,8 @@ describe('chartMixin', () => {
|
|
|
44
45
|
|
|
45
46
|
instance.$route = { query: { chart: 'chart_name' } };
|
|
46
47
|
|
|
48
|
+
await instance.fetchChart();
|
|
49
|
+
|
|
47
50
|
const warnings = instance.warnings;
|
|
48
51
|
|
|
49
52
|
expect(warnings).toHaveLength(expected);
|
package/mixins/chart.js
CHANGED
|
@@ -27,6 +27,8 @@ export default {
|
|
|
27
27
|
existing: null,
|
|
28
28
|
|
|
29
29
|
ignoreWarning: false,
|
|
30
|
+
|
|
31
|
+
chart: null,
|
|
30
32
|
};
|
|
31
33
|
},
|
|
32
34
|
|
|
@@ -35,20 +37,6 @@ export default {
|
|
|
35
37
|
|
|
36
38
|
showPreRelease: mapPref(SHOW_PRE_RELEASE),
|
|
37
39
|
|
|
38
|
-
chart() {
|
|
39
|
-
if ( this.repo && this.query.chartName ) {
|
|
40
|
-
return this.$store.getters['catalog/chart']({
|
|
41
|
-
repoType: this.query.repoType,
|
|
42
|
-
repoName: this.query.repoName,
|
|
43
|
-
chartName: this.query.chartName,
|
|
44
|
-
includeHidden: true,
|
|
45
|
-
showDeprecated: this.showDeprecated
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return null;
|
|
50
|
-
},
|
|
51
|
-
|
|
52
40
|
repo() {
|
|
53
41
|
return this.$store.getters['catalog/repo']({
|
|
54
42
|
repoType: this.query.repoType,
|
|
@@ -258,11 +246,39 @@ export default {
|
|
|
258
246
|
},
|
|
259
247
|
|
|
260
248
|
methods: {
|
|
249
|
+
/**
|
|
250
|
+
* Populate `this.chart`
|
|
251
|
+
*
|
|
252
|
+
* `chart` used to be a computed property pointing at getter catalog/chart
|
|
253
|
+
*
|
|
254
|
+
* this however stopped recalculating given changes to the store
|
|
255
|
+
*
|
|
256
|
+
* (the store would populate a charts collection, which the getter uses to find the chart,
|
|
257
|
+
* however this did not kick off the computed property, so this.charts was not populated)
|
|
258
|
+
*
|
|
259
|
+
* Now we find and cache the chart
|
|
260
|
+
*/
|
|
261
|
+
fetchStoreChart() {
|
|
262
|
+
if (!this.chart && this.repo && this.query.chartName) {
|
|
263
|
+
this.chart = this.$store.getters['catalog/chart']({
|
|
264
|
+
repoType: this.query.repoType,
|
|
265
|
+
repoName: this.query.repoName,
|
|
266
|
+
chartName: this.query.chartName,
|
|
267
|
+
includeHidden: true,
|
|
268
|
+
showDeprecated: this.showDeprecated
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return this.chart;
|
|
273
|
+
},
|
|
274
|
+
|
|
261
275
|
async fetchChart() {
|
|
262
276
|
this.versionInfoError = null;
|
|
263
277
|
|
|
264
278
|
await this.$store.dispatch('catalog/load'); // not the problem
|
|
265
279
|
|
|
280
|
+
this.fetchStoreChart();
|
|
281
|
+
|
|
266
282
|
if ( this.query.appNamespace && this.query.appName ) {
|
|
267
283
|
// First check the URL query for an app name and namespace.
|
|
268
284
|
// Use those values to check for a catalog app resource.
|
|
@@ -275,6 +291,8 @@ export default {
|
|
|
275
291
|
id: `${ this.query.appNamespace }/${ this.query.appName }`,
|
|
276
292
|
});
|
|
277
293
|
|
|
294
|
+
await this.existing?.fetchValues(true);
|
|
295
|
+
|
|
278
296
|
this.mode = _EDIT;
|
|
279
297
|
} catch (e) {
|
|
280
298
|
this.mode = _CREATE;
|
|
@@ -434,10 +452,12 @@ export default {
|
|
|
434
452
|
}
|
|
435
453
|
}
|
|
436
454
|
if (existingCRDApp) {
|
|
455
|
+
await existingCRDApp.fetchValues(true);
|
|
456
|
+
|
|
437
457
|
// spec.values are any non-default values the user configured
|
|
438
458
|
// the installation form should show these, as well as any default values from the chart
|
|
439
|
-
const existingValues = clone(existingCRDApp.
|
|
440
|
-
const defaultValues = clone(existingCRDApp.
|
|
459
|
+
const existingValues = clone(existingCRDApp.values || {});
|
|
460
|
+
const defaultValues = clone(existingCRDApp.chartValues || {});
|
|
441
461
|
|
|
442
462
|
crdVersionInfo.existingValues = existingValues;
|
|
443
463
|
crdVersionInfo.allValues = merge(defaultValues, existingValues);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import Deployment from '@shell/models/apps.deployment';
|
|
2
|
+
import { WORKLOAD_TYPES } from '@shell/config/types';
|
|
3
|
+
|
|
4
|
+
describe('class Deployment', () => {
|
|
5
|
+
describe('replicaSetId', () => {
|
|
6
|
+
it.each([{
|
|
7
|
+
relationships: [],
|
|
8
|
+
expected: undefined,
|
|
9
|
+
}, {
|
|
10
|
+
relationships: [{
|
|
11
|
+
rel: 'owner',
|
|
12
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
13
|
+
toId: 'rel-id'
|
|
14
|
+
}],
|
|
15
|
+
expected: 'rel-id',
|
|
16
|
+
}, {
|
|
17
|
+
relationships: [{
|
|
18
|
+
rel: 'owner',
|
|
19
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
20
|
+
toId: 'rel-id-1',
|
|
21
|
+
message: 'ReplicaSet is available. Replicas: 1'
|
|
22
|
+
}],
|
|
23
|
+
expected: 'rel-id-1',
|
|
24
|
+
}, {
|
|
25
|
+
relationships: [{
|
|
26
|
+
rel: 'owner',
|
|
27
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
28
|
+
toId: 'rel-id-1',
|
|
29
|
+
message: 'ReplicaSet is available. Replicas: 0'
|
|
30
|
+
}, {
|
|
31
|
+
rel: 'owner',
|
|
32
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
33
|
+
toId: 'rel-id-2',
|
|
34
|
+
message: 'ReplicaSet is available. Replicas: 1'
|
|
35
|
+
}],
|
|
36
|
+
expected: 'rel-id-2',
|
|
37
|
+
}, {
|
|
38
|
+
relationships: [{
|
|
39
|
+
rel: 'owner',
|
|
40
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
41
|
+
toId: 'rel-id-1',
|
|
42
|
+
message: 'Message without replicas count'
|
|
43
|
+
}, {
|
|
44
|
+
rel: 'owner',
|
|
45
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
46
|
+
toId: 'rel-id-2',
|
|
47
|
+
message: 'Another message without replicas count'
|
|
48
|
+
}],
|
|
49
|
+
expected: 'rel-id-1',
|
|
50
|
+
}, {
|
|
51
|
+
relationships: [{
|
|
52
|
+
rel: 'owner',
|
|
53
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
54
|
+
toId: 'rel-id-1',
|
|
55
|
+
message: 'ReplicaSet is available. Replicas: 0'
|
|
56
|
+
}, {
|
|
57
|
+
rel: 'owner',
|
|
58
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
59
|
+
toId: 'rel-id-2',
|
|
60
|
+
message: 'ReplicaSet is available. Replicas: 0'
|
|
61
|
+
}],
|
|
62
|
+
expected: 'rel-id-1',
|
|
63
|
+
}, {
|
|
64
|
+
relationships: [{
|
|
65
|
+
rel: 'owner',
|
|
66
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
67
|
+
toId: 'rel-id-1',
|
|
68
|
+
message: 'Message without replicas count'
|
|
69
|
+
}, {
|
|
70
|
+
rel: 'owner',
|
|
71
|
+
toType: WORKLOAD_TYPES.REPLICA_SET,
|
|
72
|
+
toId: 'rel-id-2',
|
|
73
|
+
message: 'ReplicaSet is available. Replicas: 0'
|
|
74
|
+
}],
|
|
75
|
+
expected: 'rel-id-1',
|
|
76
|
+
}])('replicaSetId', ({ relationships, expected }) => {
|
|
77
|
+
const deploymentData = {
|
|
78
|
+
id: 'any-id',
|
|
79
|
+
type: WORKLOAD_TYPES.DEPLOYMENT,
|
|
80
|
+
metadata: {
|
|
81
|
+
name: 'any-name',
|
|
82
|
+
namespace: 'any-namespace',
|
|
83
|
+
uid: 'any-uid',
|
|
84
|
+
relationships,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const deployment = new Deployment(deploymentData);
|
|
89
|
+
|
|
90
|
+
expect(deployment.replicaSetId).toStrictEqual(expected);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -10,14 +10,28 @@ const IGNORED_ANNOTATIONS = [
|
|
|
10
10
|
'deprecated.deployment.rollback.to',
|
|
11
11
|
];
|
|
12
12
|
|
|
13
|
+
const replicasRegEx = /Replicas: (\d+)/;
|
|
14
|
+
|
|
13
15
|
export default class Deployment extends Workload {
|
|
14
16
|
get replicaSetId() {
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const relationships = this.metadata?.relationships || [];
|
|
18
|
+
|
|
19
|
+
// Find all relevant ReplicaSet relationships
|
|
20
|
+
const replicaSetRelationships = relationships.filter((relationship) => relationship.rel === 'owner' && relationship.toType === WORKLOAD_TYPES.REPLICA_SET
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Filter the ReplicaSets based on replicas > 0
|
|
24
|
+
const activeReplicaSet = replicaSetRelationships.find((relationship) => {
|
|
25
|
+
const replicasMatch = relationship.message?.match(replicasRegEx);
|
|
26
|
+
const replicas = replicasMatch ? parseInt(replicasMatch[1], 10) : 0;
|
|
27
|
+
|
|
28
|
+
return replicas > 0;
|
|
18
29
|
});
|
|
19
30
|
|
|
20
|
-
|
|
31
|
+
// If no active ReplicaSet is found, fall back to the first one from the list
|
|
32
|
+
const selectedReplicaSet = activeReplicaSet || replicaSetRelationships[0];
|
|
33
|
+
|
|
34
|
+
return selectedReplicaSet?.toId?.replace(`${ this.namespace }/`, '');
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
async rollBack(cluster, deployment, revision) {
|