@rancher/shell 3.0.12-rc.1 → 3.0.12-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/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/translations/en-us.yaml +19 -17
- package/assets/translations/zh-hans.yaml +4 -8
- package/chart/__tests__/S3.test.ts +10 -3
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/DetailText.vue +12 -3
- package/components/SelectIconGrid.vue +5 -0
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/fleet/FleetClusterTargets/index.vue +18 -1
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +82 -24
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/nav/Group.vue +7 -6
- package/components/nav/Header.vue +24 -3
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +12 -25
- package/components/templates/plain.vue +13 -26
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/query-params.js +1 -0
- package/config/router/routes.js +0 -8
- package/core/__tests__/plugin-products.test.ts +616 -25
- package/core/plugin-products-base.ts +31 -14
- package/core/plugin-products-helpers.ts +5 -4
- package/core/plugin-types.ts +18 -3
- package/core/types.ts +3 -1
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +1 -1
- package/edit/auth/oidc.vue +8 -0
- package/edit/kontainerDriver.vue +1 -2
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +70 -99
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/chart.js +40 -9
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/fleet-application.js +1 -1
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +0 -53
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/nodedriver.js +7 -0
- package/package.json +13 -12
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +189 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +217 -33
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +8 -3
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +39 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +15 -10
- package/pages/c/_cluster/uiplugins/index.vue +23 -25
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +205 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +82 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/scripts/test-plugins-build.sh +5 -2
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +161 -11
- package/store/auth.js +0 -3
- package/store/catalog.js +60 -8
- package/types/shell/index.d.ts +26 -22
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/object.js +33 -2
- package/utils/time.ts +5 -0
- package/vue.config.js +0 -9
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- package/utils/ember-page.js +0 -30
package/edit/auth/oidc.vue
CHANGED
|
@@ -452,6 +452,14 @@ export default {
|
|
|
452
452
|
:tooltip="t('authConfig.oidc.groupSearch.tooltip')"
|
|
453
453
|
:mode="mode"
|
|
454
454
|
/>
|
|
455
|
+
<Checkbox
|
|
456
|
+
v-if="isKeycloak"
|
|
457
|
+
v-model:value="model.clientAuthenticatedSearch"
|
|
458
|
+
data-testid="input-client-authenticated-group-search"
|
|
459
|
+
:label="t('authConfig.oidc.clientAuthenticatedSearch.label')"
|
|
460
|
+
:tooltip="t('authConfig.oidc.clientAuthenticatedSearch.tooltip')"
|
|
461
|
+
:mode="mode"
|
|
462
|
+
/>
|
|
455
463
|
<Checkbox
|
|
456
464
|
v-if="supportsCustomClaims"
|
|
457
465
|
v-model:value="addCustomClaims"
|
package/edit/kontainerDriver.vue
CHANGED
|
@@ -21,7 +21,6 @@ export default {
|
|
|
21
21
|
return {
|
|
22
22
|
fvFormRuleSets: [
|
|
23
23
|
{ path: 'url', rules: ['required', 'url'] },
|
|
24
|
-
{ path: 'uiUrl', rules: ['url'] },
|
|
25
24
|
{ path: 'checksum', rules: ['alphanumeric'] },
|
|
26
25
|
{ path: 'whitelistDomains', rules: ['wildcardHostname'] }
|
|
27
26
|
]
|
|
@@ -61,7 +60,7 @@ export default {
|
|
|
61
60
|
<CreateDriver
|
|
62
61
|
:mode="mode"
|
|
63
62
|
:value="value"
|
|
64
|
-
:rules="{url:fvGetAndReportPathRules('url'),
|
|
63
|
+
:rules="{url:fvGetAndReportPathRules('url'), checksum:fvGetAndReportPathRules('checksum'), whitelistDomains:fvGetAndReportPathRules('whitelistDomains')}"
|
|
65
64
|
/>
|
|
66
65
|
</CruResource>
|
|
67
66
|
</template>
|
package/edit/nodeDriver.vue
CHANGED
|
@@ -21,7 +21,6 @@ export default {
|
|
|
21
21
|
return {
|
|
22
22
|
fvFormRuleSets: [
|
|
23
23
|
{ path: 'url', rules: ['required', 'url'] },
|
|
24
|
-
{ path: 'uiUrl', rules: ['url'] },
|
|
25
24
|
{ path: 'checksum', rules: ['alphanumeric'] },
|
|
26
25
|
{ path: 'whitelistDomains', rules: ['wildcardHostname'] }
|
|
27
26
|
]
|
|
@@ -63,7 +62,6 @@ export default {
|
|
|
63
62
|
:value="value"
|
|
64
63
|
:rules="{
|
|
65
64
|
url: fvGetAndReportPathRules('url'),
|
|
66
|
-
uiUrl: fvGetAndReportPathRules('uiUrl'),
|
|
67
65
|
checksum: fvGetAndReportPathRules('checksum'),
|
|
68
66
|
whitelistDomains: fvGetAndReportPathRules('whitelistDomains')
|
|
69
67
|
}"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import AgentEnv from '@shell/edit/provisioning.cattle.io.cluster/AgentEnv.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: AgentEnv', () => {
|
|
5
|
+
it('should only accept text files (not binary) on the KeyValue file upload', () => {
|
|
6
|
+
const wrapper = mount(AgentEnv, {
|
|
7
|
+
props: {
|
|
8
|
+
mode: 'edit',
|
|
9
|
+
value: { spec: { agentEnvVars: [] } },
|
|
10
|
+
},
|
|
11
|
+
global: {
|
|
12
|
+
mocks: { t: (key: string) => key },
|
|
13
|
+
stubs: {
|
|
14
|
+
Tab: { template: '<div><slot /></div>' },
|
|
15
|
+
KeyValue: true,
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const keyValue = wrapper.findComponent({ name: 'KeyValue' });
|
|
21
|
+
|
|
22
|
+
expect(keyValue.exists()).toBe(true);
|
|
23
|
+
expect(keyValue.props('readAccept')).toBe('text/plain');
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -4,7 +4,6 @@ import Loading from '@shell/components/Loading';
|
|
|
4
4
|
import { Banner } from '@components/Banner';
|
|
5
5
|
import CruResource from '@shell/components/CruResource';
|
|
6
6
|
import SelectIconGrid from '@shell/components/SelectIconGrid';
|
|
7
|
-
import EmberPage from '@shell/components/EmberPage';
|
|
8
7
|
import {
|
|
9
8
|
CHART, FROM_CLUSTER, SUB_TYPE, RKE_TYPE, _EDIT, _IMPORT, _CONFIG, _VIEW
|
|
10
9
|
} from '@shell/config/query-params';
|
|
@@ -18,8 +17,8 @@ import { mapFeature, RKE2 as RKE2_FEATURE } from '@shell/store/features';
|
|
|
18
17
|
import { allHash } from '@shell/utils/promise';
|
|
19
18
|
import { BLANK_CLUSTER } from '@shell/store/store-types.js';
|
|
20
19
|
import { ELEMENTAL_PRODUCT_NAME, ELEMENTAL_CLUSTER_PROVIDER } from '../../config/elemental-types';
|
|
20
|
+
import { KONTAINER_TO_DRIVER } from '@shell/models/management.cattle.io.kontainerdriver';
|
|
21
21
|
import Rke2Config from './rke2';
|
|
22
|
-
import { DRIVER_TO_IMPORT } from '@shell/models/management.cattle.io.kontainerdriver';
|
|
23
22
|
import { requireAsset } from '@shell/utils/require-asset';
|
|
24
23
|
|
|
25
24
|
const SORT_GROUPS = {
|
|
@@ -44,7 +43,6 @@ export default {
|
|
|
44
43
|
|
|
45
44
|
components: {
|
|
46
45
|
CruResource,
|
|
47
|
-
EmberPage,
|
|
48
46
|
Loading,
|
|
49
47
|
Rke2Config,
|
|
50
48
|
SelectIconGrid,
|
|
@@ -103,19 +101,6 @@ export default {
|
|
|
103
101
|
hash.kontainerDrivers = this.$store.dispatch('management/findAll', { type: MANAGEMENT.KONTAINER_DRIVER });
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
// Not sure if needed for legacy hosted cluster?
|
|
107
|
-
if ( this.value.id && !this.value.isRke2 ) {
|
|
108
|
-
// These are needed to resolve references in the mgmt cluster -> node pool -> node template to figure out what provider the cluster is using
|
|
109
|
-
// so that the edit iframe for ember pages can go to the right place.
|
|
110
|
-
if (this.$store.getters[`management/canList`](MANAGEMENT.NODE_POOL)) {
|
|
111
|
-
hash.rke1NodePools = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_POOL });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (this.$store.getters[`management/canList`](MANAGEMENT.NODE_TEMPLATE)) {
|
|
115
|
-
hash.rke1NodeTemplates = this.$store.dispatch('management/findAll', { type: MANAGEMENT.NODE_TEMPLATE });
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
104
|
const res = await allHash(hash);
|
|
120
105
|
|
|
121
106
|
this.nodeDrivers = res.nodeDrivers || [];
|
|
@@ -172,11 +157,50 @@ export default {
|
|
|
172
157
|
if ( this.$route.query[SUB_TYPE]) {
|
|
173
158
|
subType = this.$route.query[SUB_TYPE];
|
|
174
159
|
} else if (this.value.isImported) {
|
|
160
|
+
// Default imported clusters to the generic imported subType.
|
|
161
|
+
// Imported hosted clusters (e.g. AKS, EKS, GKE) that have an extension-provided
|
|
162
|
+
// component will be overridden below to load the correct custom form.
|
|
175
163
|
subType = IMPORTED;
|
|
176
164
|
} else if (this.value.isLocal) {
|
|
177
165
|
subType = LOCAL;
|
|
178
166
|
}
|
|
179
167
|
|
|
168
|
+
// For imported hosted clusters, check if the provisioner has a matching extension
|
|
169
|
+
// component and override the subType so the correct custom form loads instead of
|
|
170
|
+
// the generic imported configuration page.
|
|
171
|
+
if (subType === IMPORTED && this.value?.id && this.value.provisioner) {
|
|
172
|
+
const provisionerLower = this.value.provisioner.toLowerCase();
|
|
173
|
+
const hasExtension = this.extensions.some((ext) => ext.id === provisionerLower);
|
|
174
|
+
|
|
175
|
+
if (hasExtension) {
|
|
176
|
+
subType = provisionerLower;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Auto-detect subType for existing clusters being edited
|
|
181
|
+
if ( !subType && this.value?.id ) {
|
|
182
|
+
// Check for extension annotation first
|
|
183
|
+
const fromAnnotation = this.value.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER];
|
|
184
|
+
|
|
185
|
+
if (fromAnnotation) {
|
|
186
|
+
subType = fromAnnotation;
|
|
187
|
+
} else if ( this.value.isRke2 ) {
|
|
188
|
+
// For custom RKE2 clusters
|
|
189
|
+
if ( this.value.isCustom && (this.realMode === _EDIT || (this.as === _CONFIG && this.realMode === _VIEW)) ) {
|
|
190
|
+
subType = 'custom';
|
|
191
|
+
} else if ( this.value.machineProvider ) {
|
|
192
|
+
// For RKE2/K3s clusters provisioned in Rancher, use the machine pool provisioner
|
|
193
|
+
subType = this.value.machineProvider;
|
|
194
|
+
}
|
|
195
|
+
} else if ( this.value.provisioner ) {
|
|
196
|
+
// For non-RKE2 clusters, try to match against extension-provided subtypes
|
|
197
|
+
const provisionerLower = this.value.provisioner.toLowerCase();
|
|
198
|
+
|
|
199
|
+
// This will be checked against available subtypes after they're computed
|
|
200
|
+
subType = provisionerLower;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
180
204
|
this.subType = subType;
|
|
181
205
|
},
|
|
182
206
|
|
|
@@ -217,74 +241,6 @@ export default {
|
|
|
217
241
|
},
|
|
218
242
|
_RKE2: () => _RKE2,
|
|
219
243
|
|
|
220
|
-
emberLink() {
|
|
221
|
-
if (this.value) {
|
|
222
|
-
// set subtype if editing EKS/GKE/AKS cluster -- this ensures that the component provided by extension is loaded instead of iframing old ember ui
|
|
223
|
-
if (this.value.provisioner) {
|
|
224
|
-
const matchingSubtype = this.subTypes.find((st) => {
|
|
225
|
-
const typeLower = st.id.toLowerCase();
|
|
226
|
-
const provisionerLower = this.value.provisioner.toLowerCase();
|
|
227
|
-
|
|
228
|
-
// This allows extensions to provide type for edit without breaking edit for Ember kontainer providers
|
|
229
|
-
return (!!st.component && (typeLower === provisionerLower)) || (DRIVER_TO_IMPORT[typeLower] === provisionerLower);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
if (matchingSubtype) {
|
|
233
|
-
this.selectType(matchingSubtype.id, false);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// subType set by the ui during cluster creation
|
|
238
|
-
// this is likely from a ui extension trying to load custom ui to edit the cluster
|
|
239
|
-
const fromAnnotation = this.value.annotations?.[CAPI_ANNOTATIONS.UI_CUSTOM_PROVIDER];
|
|
240
|
-
|
|
241
|
-
if (fromAnnotation) {
|
|
242
|
-
this.selectType(fromAnnotation, false);
|
|
243
|
-
|
|
244
|
-
return '';
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// For custom RKE2 clusters, don't load an Ember page.
|
|
248
|
-
// It should be the dashboard.
|
|
249
|
-
if ( this.value.isRke2 && ((this.value.isCustom && this.mode === _EDIT) || (this.value.isCustom && this.as === _CONFIG && this.mode === _VIEW) || (this.subType || '').toLowerCase() === 'custom')) {
|
|
250
|
-
// For admins, this.value.isCustom is used to check if it is a custom cluster.
|
|
251
|
-
// For cluster owners, this.subtype is used.
|
|
252
|
-
this.selectType('custom', false);
|
|
253
|
-
|
|
254
|
-
return '';
|
|
255
|
-
}
|
|
256
|
-
// For existing RKE2/K3s clusters provisioned in Rancher,
|
|
257
|
-
// set the subtype using the machine pool provisioner
|
|
258
|
-
// do not use an iFramed Ember page.
|
|
259
|
-
if ( this.value.isRke2 && this.value.machineProvider ) {
|
|
260
|
-
this.selectType(this.value.machineProvider, false);
|
|
261
|
-
|
|
262
|
-
return '';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if ( this.subType ) {
|
|
266
|
-
// if driver type has a custom form component, don't load an ember page
|
|
267
|
-
if (this.selectedSubType?.component) {
|
|
268
|
-
return '';
|
|
269
|
-
}
|
|
270
|
-
// For RKE1 and hosted Kubernetes Clusters, set the ember link
|
|
271
|
-
// so that we load the page rather than using RKE2 create
|
|
272
|
-
if (this.selectedSubType?.emberLink) {
|
|
273
|
-
return this.selectedSubType.emberLink;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return '';
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if ( this.value.mgmt?.emberEditPath ) {
|
|
280
|
-
// Iframe an old page
|
|
281
|
-
return this.value.mgmt.emberEditPath;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return '';
|
|
286
|
-
},
|
|
287
|
-
|
|
288
244
|
rke2Enabled: mapFeature(RKE2_FEATURE),
|
|
289
245
|
|
|
290
246
|
// todo nb is this info stored anywhere else..?
|
|
@@ -310,6 +266,24 @@ export default {
|
|
|
310
266
|
return this.value.isRke2;
|
|
311
267
|
},
|
|
312
268
|
|
|
269
|
+
isEmberKontainerDriver() {
|
|
270
|
+
// RKE2/K3s clusters are never legacy Ember kontainer drivers
|
|
271
|
+
if (!this.value?.id || !this.value?.provisioner || this.value.isRke2) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const provisioner = this.value.provisioner.toLowerCase();
|
|
276
|
+
// Resolve the provisioner to a driver name using the KONTAINER_TO_DRIVER map
|
|
277
|
+
const resolvedName = KONTAINER_TO_DRIVER[provisioner] || provisioner;
|
|
278
|
+
|
|
279
|
+
const driver = this.kontainerDrivers.find((d) => {
|
|
280
|
+
return d.driverName === resolvedName || d.driverName === provisioner || d.id === provisioner;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// If the driver exists and is not built-in, it's a legacy ember driver
|
|
284
|
+
return !!driver && !driver.spec?.builtIn;
|
|
285
|
+
},
|
|
286
|
+
|
|
313
287
|
templateOptions() {
|
|
314
288
|
if ( !this.rke2Enabled ) {
|
|
315
289
|
return [];
|
|
@@ -327,16 +301,14 @@ export default {
|
|
|
327
301
|
let out = [];
|
|
328
302
|
|
|
329
303
|
const templates = this.templateOptions;
|
|
330
|
-
const vueKontainerTypes = getters['plugins/clusterDrivers'];
|
|
331
304
|
const machineTypes = this.nodeDrivers.filter((x) => x.spec.active && x.state === 'active');
|
|
332
305
|
|
|
333
|
-
//
|
|
306
|
+
// Kontainer drivers that don't have an extension-provided component are legacy Ember-based
|
|
307
|
+
// and no longer functional. Show them as disabled with an informational tooltip.
|
|
308
|
+
const emberRemovalTooltip = getters['i18n/t']('drivers.kontainer.emberRemovalTooltip');
|
|
309
|
+
|
|
334
310
|
this.kontainerDrivers.filter((x) => (isImport ? x.showImport : x.showCreate)).forEach((obj) => {
|
|
335
|
-
|
|
336
|
-
addType(this.$extension, obj.driverName, 'hosted', false);
|
|
337
|
-
} else {
|
|
338
|
-
addType(this.$extension, obj.driverName, 'hosted', false, (isImport ? obj.emberImportPath : obj.emberCreatePath));
|
|
339
|
-
}
|
|
311
|
+
addType(this.$extension, obj.driverName, 'hosted', true, undefined, undefined, emberRemovalTooltip);
|
|
340
312
|
});
|
|
341
313
|
if (!isImport) {
|
|
342
314
|
templates.forEach((chart) => {
|
|
@@ -360,7 +332,7 @@ export default {
|
|
|
360
332
|
machineTypes.forEach((type) => {
|
|
361
333
|
const id = type.spec.displayName || type.id;
|
|
362
334
|
|
|
363
|
-
addType(this.$extension, id, _RKE2, false,
|
|
335
|
+
addType(this.$extension, id, _RKE2, false, undefined, type);
|
|
364
336
|
});
|
|
365
337
|
|
|
366
338
|
addType(this.$extension, 'custom', 'custom2', false);
|
|
@@ -409,7 +381,7 @@ export default {
|
|
|
409
381
|
out.push(subtype);
|
|
410
382
|
}
|
|
411
383
|
|
|
412
|
-
function addType(plugin, id, group, disabled = false,
|
|
384
|
+
function addType(plugin, id, group, disabled = false, iconClass = undefined, providerConfig = undefined, tooltip = undefined) {
|
|
413
385
|
const label = getters['i18n/withFallback'](`cluster.provider."${ id }"`, null, id);
|
|
414
386
|
const description = getters['i18n/withFallback'](`cluster.providerDescription."${ id }"`, null, '');
|
|
415
387
|
const tag = '';
|
|
@@ -439,8 +411,8 @@ export default {
|
|
|
439
411
|
iconClass,
|
|
440
412
|
group,
|
|
441
413
|
disabled,
|
|
442
|
-
emberLink,
|
|
443
414
|
tag,
|
|
415
|
+
tooltip,
|
|
444
416
|
providerConfig
|
|
445
417
|
};
|
|
446
418
|
|
|
@@ -594,12 +566,11 @@ export default {
|
|
|
594
566
|
/>
|
|
595
567
|
</div>
|
|
596
568
|
<div
|
|
597
|
-
v-else-if="
|
|
598
|
-
class="embed"
|
|
569
|
+
v-else-if="isEmberKontainerDriver"
|
|
599
570
|
>
|
|
600
|
-
<
|
|
601
|
-
|
|
602
|
-
|
|
571
|
+
<Banner
|
|
572
|
+
color="warning"
|
|
573
|
+
label-key="drivers.kontainer.emberRemovalMessage"
|
|
603
574
|
/>
|
|
604
575
|
</div>
|
|
605
576
|
<CruResource
|
package/initialize/App.vue
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import GlobalLoading from '@shell/components/nav/GlobalLoading.vue';
|
|
3
|
+
import WindowManager from '@shell/components/nav/WindowManager';
|
|
3
4
|
|
|
4
5
|
import '@shell/assets/styles/app.scss';
|
|
5
6
|
|
|
6
7
|
export default {
|
|
7
|
-
data
|
|
8
|
+
data() {
|
|
9
|
+
return {
|
|
10
|
+
isOnline: true,
|
|
11
|
+
currentLayout: null,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
8
14
|
|
|
9
15
|
created() {
|
|
10
16
|
// add to window so we can listen when ready
|
|
@@ -36,6 +42,14 @@ export default {
|
|
|
36
42
|
this.$loading = this.$refs.loading;
|
|
37
43
|
},
|
|
38
44
|
|
|
45
|
+
provide() {
|
|
46
|
+
return {
|
|
47
|
+
notifyWmContainerReady: (layout) => {
|
|
48
|
+
this.currentLayout = layout;
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
|
|
39
53
|
computed: {
|
|
40
54
|
isOffline() {
|
|
41
55
|
return !this.isOnline;
|
|
@@ -55,7 +69,10 @@ export default {
|
|
|
55
69
|
},
|
|
56
70
|
},
|
|
57
71
|
|
|
58
|
-
components: {
|
|
72
|
+
components: {
|
|
73
|
+
GlobalLoading,
|
|
74
|
+
WindowManager,
|
|
75
|
+
}
|
|
59
76
|
};
|
|
60
77
|
</script>
|
|
61
78
|
<template>
|
|
@@ -65,6 +82,16 @@ export default {
|
|
|
65
82
|
id="__layout"
|
|
66
83
|
>
|
|
67
84
|
<router-view />
|
|
85
|
+
<!--
|
|
86
|
+
WindowManager is teleported into each template's wm-container
|
|
87
|
+
This keeps a single instance that never re-mounts while appearing in each template
|
|
88
|
+
-->
|
|
89
|
+
<Teleport
|
|
90
|
+
v-if="currentLayout"
|
|
91
|
+
:to="`#wm-container-${currentLayout}`"
|
|
92
|
+
>
|
|
93
|
+
<WindowManager :layout="currentLayout" />
|
|
94
|
+
</Teleport>
|
|
68
95
|
</div>
|
|
69
96
|
</div>
|
|
70
97
|
</template>
|
|
@@ -21,7 +21,6 @@ import plugin from '@shell/plugins/plugin';
|
|
|
21
21
|
import pluginsLoader from '@shell/core/plugins-loader.js';
|
|
22
22
|
import replaceAll from '@shell/plugins/replaceall';
|
|
23
23
|
import steveCreateWorker from '@shell/plugins/steve-create-worker';
|
|
24
|
-
import emberCookie from '@shell/plugins/ember-cookie';
|
|
25
24
|
import ShortKey from '@shell/plugins/shortkey';
|
|
26
25
|
import { initUiApis } from '@shell/apis/impl/apis';
|
|
27
26
|
|
|
@@ -57,7 +56,6 @@ export async function installInjectedPlugins(app, vueApp) {
|
|
|
57
56
|
replaceAll,
|
|
58
57
|
plugin,
|
|
59
58
|
steveCreateWorker,
|
|
60
|
-
emberCookie,
|
|
61
59
|
dynamicContent,
|
|
62
60
|
];
|
|
63
61
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { shallowMount } from '@vue/test-utils';
|
|
2
|
+
|
|
3
|
+
jest.mock('@shell/mixins/resource-fetch', () => ({
|
|
4
|
+
__esModule: true,
|
|
5
|
+
default: {
|
|
6
|
+
data() {
|
|
7
|
+
return {
|
|
8
|
+
forceUpdateLiveAndDelayed: 0, loading: false, rows: []
|
|
9
|
+
};
|
|
10
|
+
},
|
|
11
|
+
async $fetchType() {}
|
|
12
|
+
}
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
// eslint-disable-next-line import/first
|
|
16
|
+
import ManagementFeature from '@shell/list/management.cattle.io.feature.vue';
|
|
17
|
+
|
|
18
|
+
const createMockStore = () => ({
|
|
19
|
+
getters: {
|
|
20
|
+
'i18n/t': (key: string) => key,
|
|
21
|
+
'management/schemaFor': () => ({ resourceMethods: ['PUT'] }),
|
|
22
|
+
},
|
|
23
|
+
dispatch: jest.fn(),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const createWrapper = (rows: any[]) => {
|
|
27
|
+
return shallowMount(ManagementFeature, {
|
|
28
|
+
props: {
|
|
29
|
+
resource: 'management.cattle.io.feature',
|
|
30
|
+
schema: { id: 'management.cattle.io.feature' } as any,
|
|
31
|
+
},
|
|
32
|
+
data: () => ({ rows }),
|
|
33
|
+
global: {
|
|
34
|
+
mocks: {
|
|
35
|
+
$store: createMockStore(),
|
|
36
|
+
$fetchState: { pending: false },
|
|
37
|
+
$fetchType: jest.fn(),
|
|
38
|
+
},
|
|
39
|
+
stubs: {
|
|
40
|
+
// Render the cell:name slot directly so we can assert on the lock icon
|
|
41
|
+
ResourceTable: {
|
|
42
|
+
props: ['rows'],
|
|
43
|
+
template: '<div><slot name="cell:name" :row="rows[0]" /></div>',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('list/management.cattle.io.feature', () => {
|
|
51
|
+
describe('locked icon rendering in cell:name slot', () => {
|
|
52
|
+
it('should render the lock icon when status.lockedValue is not null', () => {
|
|
53
|
+
const row = {
|
|
54
|
+
metadata: { name: 'feature-a' },
|
|
55
|
+
nameDisplay: 'feature-a',
|
|
56
|
+
status: { lockedValue: true },
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const wrapper = createWrapper([row]);
|
|
60
|
+
|
|
61
|
+
expect(wrapper.find('i.icon-lock').exists()).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should not render the lock icon when status.lockedValue is null', () => {
|
|
65
|
+
const row = {
|
|
66
|
+
metadata: { name: 'feature-a' },
|
|
67
|
+
nameDisplay: 'feature-a',
|
|
68
|
+
status: { lockedValue: null },
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const wrapper = createWrapper([row]);
|
|
72
|
+
|
|
73
|
+
expect(wrapper.find('i.icon-lock').exists()).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should not throw and should not render the lock icon when status is missing (malformed feature flag)', () => {
|
|
77
|
+
const row = {
|
|
78
|
+
metadata: { name: 'feature-a' },
|
|
79
|
+
nameDisplay: 'feature-a',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
expect(() => createWrapper([row])).not.toThrow();
|
|
83
|
+
|
|
84
|
+
const wrapper = createWrapper([row]);
|
|
85
|
+
|
|
86
|
+
expect(wrapper.find('i.icon-lock').exists()).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('filteredRows', () => {
|
|
91
|
+
it('should filter out hidden feature flags', () => {
|
|
92
|
+
const rows = [
|
|
93
|
+
{ metadata: { name: 'fleet' } },
|
|
94
|
+
{ metadata: { name: 'some-feature' } },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const wrapper = createWrapper(rows);
|
|
98
|
+
|
|
99
|
+
const filtered = (wrapper.vm as any).filteredRows;
|
|
100
|
+
|
|
101
|
+
expect(filtered).toHaveLength(1);
|
|
102
|
+
expect(filtered[0].metadata.name).toBe('some-feature');
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -40,14 +40,15 @@ export default {
|
|
|
40
40
|
data-testid="installed-app-catalog-list"
|
|
41
41
|
>
|
|
42
42
|
<template #cell:upgrade="{row}">
|
|
43
|
-
<
|
|
43
|
+
<div
|
|
44
44
|
v-if="row.upgradeAvailable === APP_UPGRADE_STATUS.SINGLE_UPGRADE"
|
|
45
|
-
|
|
45
|
+
v-clean-tooltip="row.upgradeAvailableVersion"
|
|
46
|
+
class="badge-state bg-warning hand app-upgrade-badge"
|
|
46
47
|
@click="row.goToUpgrade(row.upgradeAvailableVersion)"
|
|
47
48
|
>
|
|
48
|
-
{{ row.upgradeAvailableVersion }}
|
|
49
|
+
<div>{{ row.upgradeAvailableVersion }}</div>
|
|
49
50
|
<i class="icon icon-upload" />
|
|
50
|
-
</
|
|
51
|
+
</div>
|
|
51
52
|
<span
|
|
52
53
|
v-else-if="row.upgradeAvailable === APP_UPGRADE_STATUS.NOT_APPLICABLE"
|
|
53
54
|
v-t="'catalog.app.managed'"
|
|
@@ -69,8 +70,27 @@ export default {
|
|
|
69
70
|
</PaginatedResourceTable>
|
|
70
71
|
</template>
|
|
71
72
|
|
|
72
|
-
<style scoped>
|
|
73
|
+
<style scoped lang="scss">
|
|
73
74
|
.apps :deep() .state-description{
|
|
74
75
|
color: var(--error)
|
|
75
76
|
}
|
|
77
|
+
|
|
78
|
+
.badge-state.app-upgrade-badge {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
border-radius: var(--border-radius);
|
|
82
|
+
padding: 2px 4px;
|
|
83
|
+
|
|
84
|
+
> div {
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
text-overflow: ellipsis;
|
|
87
|
+
white-space: nowrap;
|
|
88
|
+
min-width: 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
> .icon {
|
|
92
|
+
flex-shrink: 0;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
</style>
|
|
@@ -21,6 +21,14 @@ export default {
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
|
|
24
|
+
created() {
|
|
25
|
+
this.$store.dispatch('showWorkspaceSwitcher', false);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
beforeUnmount() {
|
|
29
|
+
this.$store.dispatch('showWorkspaceSwitcher', true);
|
|
30
|
+
},
|
|
31
|
+
|
|
24
32
|
async fetch() {
|
|
25
33
|
try {
|
|
26
34
|
await this.$fetchType(this.resource);
|