@rancher/shell 2.0.0 → 2.0.1
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 +18 -3
- package/components/AlertTable.vue +17 -7
- package/components/GrafanaDashboard.vue +6 -4
- package/components/PromptRemove.vue +1 -0
- package/components/form/KeyValue.vue +1 -0
- package/components/form/Taints.vue +13 -7
- package/components/form/__tests__/Taints.test.ts +70 -0
- package/components/nav/Header.vue +1 -1
- package/components/nav/TopLevelMenu.vue +1 -4
- package/config/product/auth.js +1 -1
- package/config/router/navigation-guards/i18n.js +13 -0
- package/config/router/navigation-guards/index.js +2 -1
- package/creators/app/app.package.json +2 -1
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +42 -0
- package/detail/provisioning.cattle.io.cluster.vue +4 -4
- package/dialog/DeactivateDriverDialog.vue +30 -11
- package/edit/auth/__tests__/oidc.test.ts +2 -2
- package/edit/token.vue +2 -1
- package/initialize/entry-helpers.js +10 -13
- package/list/management.cattle.io.feature.vue +4 -2
- package/middleware/authenticated.js +0 -19
- package/mixins/auth-config.js +1 -1
- package/models/driver.js +3 -2
- package/models/kontainerdriver.js +30 -13
- package/models/management.cattle.io.authconfig.js +2 -2
- package/models/nodedriver.js +30 -13
- package/package.json +3 -2
- package/pages/c/_cluster/apps/charts/install.vue +3 -2
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +0 -3
- package/pages/c/_cluster/manager/drivers/nodeDriver/index.vue +1 -4
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +2 -1
- package/promptRemove/pod.vue +15 -7
- package/scripts/publish-shell.sh +1 -0
- package/store/auth.js +1 -1
- package/store/index.js +1 -1
- package/utils/__tests__/kontainer.test.ts +89 -1
- package/utils/auth.js +1 -1
- package/utils/kontainer.ts +5 -1
- package/utils/version.js +2 -1
- package/rancher-components/components/Accordion/Accordion.test.ts +0 -45
- package/rancher-components/components/Accordion/Accordion.vue +0 -86
- package/rancher-components/components/Accordion/index.ts +0 -1
- package/rancher-components/components/BadgeState/BadgeState.test.ts +0 -12
- package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
- package/rancher-components/components/BadgeState/index.ts +0 -1
- package/rancher-components/components/Banner/Banner.test.ts +0 -59
- package/rancher-components/components/Banner/Banner.vue +0 -244
- package/rancher-components/components/Banner/index.ts +0 -1
- package/rancher-components/components/Card/Card.test.ts +0 -37
- package/rancher-components/components/Card/Card.vue +0 -167
- package/rancher-components/components/Card/index.ts +0 -1
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -421
- package/rancher-components/components/Form/Checkbox/index.ts +0 -1
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -40
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -402
- package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
- package/rancher-components/components/Form/Radio/RadioButton.test.ts +0 -33
- package/rancher-components/components/Form/Radio/RadioButton.vue +0 -293
- package/rancher-components/components/Form/Radio/RadioGroup.test.ts +0 -30
- package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -259
- package/rancher-components/components/Form/Radio/index.ts +0 -2
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -172
- package/rancher-components/components/Form/TextArea/index.ts +0 -1
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -152
- package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
- package/rancher-components/components/Form/index.ts +0 -5
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -156
- package/rancher-components/components/LabeledTooltip/index.ts +0 -1
- package/rancher-components/components/StringList/StringList.test.ts +0 -754
- package/rancher-components/components/StringList/StringList.vue +0 -650
- package/rancher-components/components/StringList/index.ts +0 -1
|
@@ -572,7 +572,7 @@ authConfig:
|
|
|
572
572
|
title: Are you sure? This update is irreversible.
|
|
573
573
|
body: '<p><b>You may need to make some additional changes</b>. Please ensure the Azure AD app has the Directory.Read.All <b>Application</b> permission added to Microsoft Graph.<br> If any endpoints were customized while configuring Azure AD authentication in Rancher, they will not be automatically updated. </p>'
|
|
574
574
|
oidc:
|
|
575
|
-
|
|
575
|
+
genericoidc: Configure an OIDC account
|
|
576
576
|
keycloakoidc: Configure a Keycloak OIDC account
|
|
577
577
|
rancherUrl: Rancher URL
|
|
578
578
|
clientId: Client ID
|
|
@@ -2225,7 +2225,22 @@ drivers:
|
|
|
2225
2225
|
refresh: Refresh Kubernetes Metadata
|
|
2226
2226
|
deactivate:
|
|
2227
2227
|
title: Are you sure?
|
|
2228
|
-
|
|
2228
|
+
andOthers: |-
|
|
2229
|
+
{count, plural,
|
|
2230
|
+
=0 {}
|
|
2231
|
+
=1 { and <b>one other </b>}
|
|
2232
|
+
other { and <b>{count} other </b>}
|
|
2233
|
+
}
|
|
2234
|
+
warningDrivers: |-
|
|
2235
|
+
{count, plural,
|
|
2236
|
+
=1 { You will no longer be able to edit the configuration of clusters using {names} driver.}
|
|
2237
|
+
other { You will no longer be able to edit the configuration of clusters using {names} drivers.}
|
|
2238
|
+
}
|
|
2239
|
+
warning: |-
|
|
2240
|
+
{count, plural,
|
|
2241
|
+
=1 { {warningDrivers} Resources in the corresponding provider will not be automatically removed.}
|
|
2242
|
+
other { {warningDrivers} Resources in the corresponding providers will not be automatically removed.}
|
|
2243
|
+
}
|
|
2229
2244
|
|
|
2230
2245
|
detailText:
|
|
2231
2246
|
collapse: Hide
|
|
@@ -6582,7 +6597,7 @@ model:
|
|
|
6582
6597
|
okta: Okta
|
|
6583
6598
|
freeipa: FreeIPA
|
|
6584
6599
|
googleoauth: Google
|
|
6585
|
-
|
|
6600
|
+
genericoidc: Generic OIDC
|
|
6586
6601
|
keycloakoidc: Keycloak
|
|
6587
6602
|
|
|
6588
6603
|
cluster:
|
|
@@ -49,6 +49,7 @@ export default {
|
|
|
49
49
|
];
|
|
50
50
|
|
|
51
51
|
return {
|
|
52
|
+
inStore: this.$store.getters['currentProduct'].inStore,
|
|
52
53
|
alertManagerPoller: new Poller(
|
|
53
54
|
this.loadAlertManagerEvents,
|
|
54
55
|
ALERTMANAGER_POLL_RATE_MS,
|
|
@@ -69,15 +70,24 @@ export default {
|
|
|
69
70
|
},
|
|
70
71
|
|
|
71
72
|
methods: {
|
|
72
|
-
async
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
{ url: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/${ this.monitoringNamespace }/services/http:${ this.alertServiceEndpoint }:9093/proxy/api/v1/alerts` }
|
|
73
|
+
async fetchAlertManagerEvents(version) {
|
|
74
|
+
return await this.$store.dispatch(
|
|
75
|
+
`${ this.inStore }/request`,
|
|
76
|
+
{ url: `/k8s/clusters/${ this.currentCluster.id }/api/v1/namespaces/${ this.monitoringNamespace }/services/http:${ this.alertServiceEndpoint }:9093/proxy/api/${ version }/alerts` }
|
|
77
77
|
);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
async loadAlertManagerEvents() {
|
|
81
|
+
let alertEvents;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
alertEvents = await this.fetchAlertManagerEvents('v2');
|
|
85
|
+
} catch (err) {
|
|
86
|
+
alertEvents = await this.fetchAlertManagerEvents('v1').then((res) => res?.data);
|
|
87
|
+
}
|
|
78
88
|
|
|
79
|
-
if (
|
|
80
|
-
this.allAlerts =
|
|
89
|
+
if (alertEvents) {
|
|
90
|
+
this.allAlerts = alertEvents;
|
|
81
91
|
}
|
|
82
92
|
},
|
|
83
93
|
|
|
@@ -114,10 +114,12 @@ export default {
|
|
|
114
114
|
this.interval = setInterval(() => {
|
|
115
115
|
try {
|
|
116
116
|
const graphWindow = this.$refs.frame?.contentWindow;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
const
|
|
117
|
+
|
|
118
|
+
// Note. getElementsByClassName won't work, following a grafana bump class names are now unique - for example css-2qng6u-panel-container
|
|
119
|
+
const errorElements = graphWindow.document.querySelectorAll('[class$="alert-error');
|
|
120
|
+
const errorCornerElements = graphWindow.document.querySelectorAll('[class$="panel-info-corner--error');
|
|
121
|
+
const panelInFullScreenElements = graphWindow.document.querySelectorAll('[class$="panel-in-fullscreen');
|
|
122
|
+
const panelContainerElements = graphWindow.document.querySelectorAll('[class$="panel-container');
|
|
121
123
|
const error = errorElements.length > 0 || errorCornerElements.length > 0;
|
|
122
124
|
const loaded = panelInFullScreenElements.length > 0 || panelContainerElements.length > 0;
|
|
123
125
|
const errorMessageElms = graphWindow.document.getElementsByTagName('pre');
|
|
@@ -3,10 +3,10 @@ import KeyValue from '@shell/components/form/KeyValue';
|
|
|
3
3
|
import { _VIEW } from '@shell/config/query-params';
|
|
4
4
|
import Select from '@shell/components/form/Select';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const DEFAULT_EFFECT_VALUES = {
|
|
7
|
+
NoSchedule: 'NoSchedule',
|
|
8
|
+
PreferNoSchedule: 'PreferNoSchedule',
|
|
9
|
+
NoExecute: 'NoExecute',
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export default {
|
|
@@ -24,11 +24,15 @@ export default {
|
|
|
24
24
|
disabled: {
|
|
25
25
|
default: false,
|
|
26
26
|
type: Boolean
|
|
27
|
+
},
|
|
28
|
+
effectValues: {
|
|
29
|
+
type: Object,
|
|
30
|
+
default: () => DEFAULT_EFFECT_VALUES
|
|
27
31
|
}
|
|
28
32
|
},
|
|
29
33
|
|
|
30
34
|
data() {
|
|
31
|
-
return { effectOptions: Object.
|
|
35
|
+
return { effectOptions: Object.keys(this.effectValues).map((k) => ({ label: this.effectValues[k], value: k })) };
|
|
32
36
|
},
|
|
33
37
|
|
|
34
38
|
computed: {
|
|
@@ -43,7 +47,7 @@ export default {
|
|
|
43
47
|
},
|
|
44
48
|
|
|
45
49
|
defaultAddData() {
|
|
46
|
-
return { effect:
|
|
50
|
+
return { effect: this.effectOptions[0].value };
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
};
|
|
@@ -53,6 +57,7 @@ export default {
|
|
|
53
57
|
<div class="taints">
|
|
54
58
|
<KeyValue
|
|
55
59
|
v-model="localValue"
|
|
60
|
+
data-testid="taints-keyvalue"
|
|
56
61
|
:title="t('tableHeaders.taints')"
|
|
57
62
|
:mode="mode"
|
|
58
63
|
:as-map="false"
|
|
@@ -69,9 +74,10 @@ export default {
|
|
|
69
74
|
{{ t('tableHeaders.effect') }}
|
|
70
75
|
</template>
|
|
71
76
|
|
|
72
|
-
<template #col:effect="{row, queueUpdate}">
|
|
77
|
+
<template #col:effect="{row, queueUpdate, i}">
|
|
73
78
|
<Select
|
|
74
79
|
v-model="row.effect"
|
|
80
|
+
:data-testid="`taints-effect-row-${i}`"
|
|
75
81
|
:options="effectOptions"
|
|
76
82
|
:disabled="disabled"
|
|
77
83
|
class="compact-select"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import Taints from '@shell/components/form/Taints.vue';
|
|
3
|
+
|
|
4
|
+
describe('component: Taints', () => {
|
|
5
|
+
it('should accept custom effect values', async() => {
|
|
6
|
+
const customEffects = { FOO_EFFECT: 'foo', BAR_EFFECT: 'bar' };
|
|
7
|
+
|
|
8
|
+
const wrapper = mount(Taints, {
|
|
9
|
+
propsData: {
|
|
10
|
+
value: [{ effect: 'FOO_EFFECT', value: 'abc' }],
|
|
11
|
+
effectValues: customEffects
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const firstEffectInput = wrapper.find('[data-testid="taints-effect-row-0"]');
|
|
16
|
+
|
|
17
|
+
expect(firstEffectInput.exists()).toBe(true);
|
|
18
|
+
|
|
19
|
+
expect(firstEffectInput.props().value).toBe('FOO_EFFECT');
|
|
20
|
+
expect(firstEffectInput.props().options).toStrictEqual([{ value: 'FOO_EFFECT', label: 'foo' }, { value: 'BAR_EFFECT', label: 'bar' }]);
|
|
21
|
+
|
|
22
|
+
const taintKV = wrapper.find('[data-testid="taints-keyvalue"]');
|
|
23
|
+
|
|
24
|
+
taintKV.vm.add();
|
|
25
|
+
await wrapper.vm.$nextTick();
|
|
26
|
+
|
|
27
|
+
const secondEffectInput = wrapper.find('[data-testid="taints-effect-row-1"]');
|
|
28
|
+
|
|
29
|
+
expect(secondEffectInput.exists()).toBe(true);
|
|
30
|
+
|
|
31
|
+
expect(secondEffectInput.props().value).toStrictEqual('FOO_EFFECT');
|
|
32
|
+
expect(wrapper.vm.defaultAddData).toStrictEqual({ effect: 'FOO_EFFECT' });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should use default effect values of NoSchedule, PreferNoSchedule, and PreferNoExecute', async() => {
|
|
36
|
+
const expectedEffectOptions = [
|
|
37
|
+
{ label: 'NoSchedule', value: 'NoSchedule' },
|
|
38
|
+
{ label: 'PreferNoSchedule', value: 'PreferNoSchedule' },
|
|
39
|
+
|
|
40
|
+
{ label: 'NoExecute', value: 'NoExecute' },
|
|
41
|
+
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const wrapper = mount(Taints, { propsData: { value: [{ effect: '', value: 'abc' }] } });
|
|
45
|
+
|
|
46
|
+
const firstEffectInput = wrapper.find('[data-testid="taints-effect-row-0"]');
|
|
47
|
+
|
|
48
|
+
expect(firstEffectInput.exists()).toBe(true);
|
|
49
|
+
|
|
50
|
+
expect(firstEffectInput.props().value).toBe('');
|
|
51
|
+
expect(firstEffectInput.props().options).toStrictEqual(expectedEffectOptions);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should set the effect value to NoSchedule by default', async() => {
|
|
55
|
+
const wrapper = mount(Taints, { propsData: { value: [] } });
|
|
56
|
+
|
|
57
|
+
const taintKV = wrapper.find('[data-testid="taints-keyvalue"]');
|
|
58
|
+
|
|
59
|
+
taintKV.vm.add();
|
|
60
|
+
await wrapper.vm.$nextTick();
|
|
61
|
+
|
|
62
|
+
const effectInput = wrapper.find('[data-testid="taints-effect-row-0"]');
|
|
63
|
+
|
|
64
|
+
expect(effectInput.exists()).toBe(true);
|
|
65
|
+
|
|
66
|
+
expect(effectInput.props().value).toStrictEqual('NoSchedule');
|
|
67
|
+
|
|
68
|
+
expect(wrapper.vm.defaultAddData).toStrictEqual({ effect: 'NoSchedule' });
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -133,7 +133,7 @@ export default {
|
|
|
133
133
|
// Don't show if the header is in 'simple' mode
|
|
134
134
|
const notSimple = !this.simple;
|
|
135
135
|
// One of these must be enabled, otherwise t here's no component to show
|
|
136
|
-
const validFilterSettings = this.currentProduct
|
|
136
|
+
const validFilterSettings = this.currentProduct?.showNamespaceFilter || this.currentProduct?.showWorkspaceSwitcher;
|
|
137
137
|
|
|
138
138
|
return validClusterOrProduct && notSimple && validFilterSettings;
|
|
139
139
|
},
|
|
@@ -1090,7 +1090,7 @@ export default {
|
|
|
1090
1090
|
}
|
|
1091
1091
|
}
|
|
1092
1092
|
|
|
1093
|
-
> i {
|
|
1093
|
+
> i, > img {
|
|
1094
1094
|
display: block;
|
|
1095
1095
|
width: 42px;
|
|
1096
1096
|
font-size: $icon-size;
|
|
@@ -1102,9 +1102,6 @@ export default {
|
|
|
1102
1102
|
margin-right: 16px;
|
|
1103
1103
|
fill: var(--link);
|
|
1104
1104
|
}
|
|
1105
|
-
img {
|
|
1106
|
-
margin-right: 16px;
|
|
1107
|
-
}
|
|
1108
1105
|
|
|
1109
1106
|
&.router-link-active, &.active-menu-link {
|
|
1110
1107
|
background: var(--primary-hover-bg);
|
package/config/product/auth.js
CHANGED
|
@@ -174,7 +174,7 @@ export function init(store) {
|
|
|
174
174
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/googleoauth`, 'auth/googleoauth');
|
|
175
175
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/azuread`, 'auth/azuread');
|
|
176
176
|
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/keycloakoidc`, 'auth/oidc');
|
|
177
|
-
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/
|
|
177
|
+
componentForType(`${ MANAGEMENT.AUTH_CONFIG }/genericoidc`, 'auth/oidc');
|
|
178
178
|
|
|
179
179
|
basicType([
|
|
180
180
|
'config',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function install(router, context) {
|
|
2
|
+
router.beforeEach((to, from, next) => loadI18n(to, from, next, context));
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function loadI18n(to, from, next, { store }) {
|
|
6
|
+
try {
|
|
7
|
+
await store.dispatch('i18n/init');
|
|
8
|
+
} catch (e) {
|
|
9
|
+
console.error('Failed to initialize i18n', e); // eslint-disable-line no-console
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
next();
|
|
13
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { install as installLoadInitialSettings } from '@shell/config/router/navigation-guards/load-initial-settings';
|
|
2
2
|
import { install as installAttemptFirstLogin } from '@shell/config/router/navigation-guards/attempt-first-login';
|
|
3
3
|
import { install as installAuthentication } from '@shell/config/router/navigation-guards/authentication';
|
|
4
|
+
import { install as installI18N } from '@shell/config/router/navigation-guards/i18n';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Install our router navigation guards. i.e. router.beforeEach(), router.afterEach()
|
|
@@ -9,7 +10,7 @@ export function installNavigationGuards(router, context) {
|
|
|
9
10
|
// NOTE: the order of the installation matters.
|
|
10
11
|
// Be intentional when adding, removing or modifying the guards that are installed.
|
|
11
12
|
|
|
12
|
-
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication];
|
|
13
|
+
const navigationGuardInstallers = [installLoadInitialSettings, installAttemptFirstLogin, installAuthentication, installI18N];
|
|
13
14
|
|
|
14
15
|
navigationGuardInstallers.forEach((installer) => installer(router, context));
|
|
15
16
|
}
|
|
@@ -73,5 +73,47 @@ describe('view: provisioning.cattle.io.cluster', () => {
|
|
|
73
73
|
|
|
74
74
|
expect(wrapper.vm.showRegistration).toStrictEqual(false);
|
|
75
75
|
});
|
|
76
|
+
|
|
77
|
+
it('should SHOW if custom/imported cluster and the cluster is active', async() => {
|
|
78
|
+
const value = {
|
|
79
|
+
isCustom: true,
|
|
80
|
+
isImported: true,
|
|
81
|
+
mgmt: {
|
|
82
|
+
hasLink: () => jest.fn(),
|
|
83
|
+
linkFor: () => '',
|
|
84
|
+
isReady: true
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const wrapper = shallowMount(ProvisioningCattleIoCluster, {
|
|
89
|
+
mocks,
|
|
90
|
+
propsData: { value },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await wrapper.setData({ clusterToken: {} });
|
|
94
|
+
|
|
95
|
+
expect(wrapper.vm.showRegistration).toStrictEqual(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should NOT show if imported cluster and the cluster is active', async() => {
|
|
99
|
+
const value = {
|
|
100
|
+
isCustom: false,
|
|
101
|
+
isImported: true,
|
|
102
|
+
mgmt: {
|
|
103
|
+
hasLink: () => jest.fn(),
|
|
104
|
+
linkFor: () => '',
|
|
105
|
+
isReady: true
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const wrapper = shallowMount(ProvisioningCattleIoCluster, {
|
|
110
|
+
mocks,
|
|
111
|
+
propsData: { value },
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await wrapper.setData({ clusterToken: {} });
|
|
115
|
+
|
|
116
|
+
expect(wrapper.vm.showRegistration).toStrictEqual(false);
|
|
117
|
+
});
|
|
76
118
|
});
|
|
77
119
|
});
|
|
@@ -512,14 +512,14 @@ export default {
|
|
|
512
512
|
return false;
|
|
513
513
|
}
|
|
514
514
|
|
|
515
|
-
if ( this.value.isImported ) {
|
|
516
|
-
return !this.value.mgmt?.isReady && this.extDetailTabs.registration;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
515
|
if ( this.value.isCustom ) {
|
|
520
516
|
return this.extDetailTabs.registration;
|
|
521
517
|
}
|
|
522
518
|
|
|
519
|
+
if ( this.value.isImported ) {
|
|
520
|
+
return !this.value.mgmt?.isReady && this.extDetailTabs.registration;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
523
|
// Hosted kubernetes providers with private endpoints need the registration tab
|
|
524
524
|
// https://github.com/rancher/dashboard/issues/6036
|
|
525
525
|
// https://github.com/rancher/dashboard/issues/4545
|
|
@@ -3,6 +3,8 @@ import AsyncButton from '@shell/components/AsyncButton';
|
|
|
3
3
|
import { Card } from '@components/Card';
|
|
4
4
|
import { Banner } from '@components/Banner';
|
|
5
5
|
import { exceptionToErrorsArray } from '@shell/utils/error';
|
|
6
|
+
import { resourceNames } from '@shell/utils/string';
|
|
7
|
+
import { mapGetters } from 'vuex';
|
|
6
8
|
|
|
7
9
|
export default {
|
|
8
10
|
components: {
|
|
@@ -12,20 +14,35 @@ export default {
|
|
|
12
14
|
},
|
|
13
15
|
|
|
14
16
|
props: {
|
|
15
|
-
|
|
16
|
-
type:
|
|
17
|
-
|
|
17
|
+
drivers: {
|
|
18
|
+
type: Array,
|
|
19
|
+
required: true
|
|
18
20
|
},
|
|
19
|
-
|
|
20
|
-
type:
|
|
21
|
-
|
|
21
|
+
driverType: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true
|
|
22
24
|
}
|
|
23
25
|
},
|
|
24
26
|
|
|
25
27
|
data() {
|
|
26
28
|
return { errors: [] };
|
|
27
29
|
},
|
|
30
|
+
computed: {
|
|
31
|
+
formattedText() {
|
|
32
|
+
const namesSliced = this.drivers.map((obj) => obj.nameDisplay).slice(0, 5);
|
|
33
|
+
const remaining = this.drivers.length - namesSliced.length;
|
|
34
|
+
|
|
35
|
+
const plusMore = this.t('drivers.deactivate.andOthers', { count: remaining });
|
|
36
|
+
const names = resourceNames(namesSliced, plusMore, this.t);
|
|
37
|
+
const count = remaining || namesSliced.length;
|
|
38
|
+
const warningDrivers = this.t('drivers.deactivate.warningDrivers', { names, count });
|
|
39
|
+
|
|
40
|
+
return this.t('drivers.deactivate.warning', { warningDrivers, count: namesSliced.length });
|
|
41
|
+
},
|
|
42
|
+
...mapGetters({ t: 'i18n/t' }),
|
|
43
|
+
},
|
|
28
44
|
methods: {
|
|
45
|
+
resourceNames,
|
|
29
46
|
close(buttonDone) {
|
|
30
47
|
if (buttonDone && typeof buttonDone === 'function') {
|
|
31
48
|
buttonDone(true);
|
|
@@ -34,10 +51,12 @@ export default {
|
|
|
34
51
|
},
|
|
35
52
|
async apply(buttonDone) {
|
|
36
53
|
try {
|
|
37
|
-
await this
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
54
|
+
await Promise.all(this.drivers.map(
|
|
55
|
+
(driver) => this.$store.dispatch('rancher/request', {
|
|
56
|
+
url: `v3/${ this.driverType }/${ escape(driver.id) }?action=deactivate`,
|
|
57
|
+
method: 'POST'
|
|
58
|
+
})
|
|
59
|
+
));
|
|
41
60
|
|
|
42
61
|
this.close(buttonDone);
|
|
43
62
|
} catch (err) {
|
|
@@ -64,7 +83,7 @@ export default {
|
|
|
64
83
|
<template #body>
|
|
65
84
|
<div class="pl-10 pr-10">
|
|
66
85
|
<div class="text info mb-10 mt-20">
|
|
67
|
-
<span v-clean-html="
|
|
86
|
+
<span v-clean-html="formattedText" />
|
|
68
87
|
</div>
|
|
69
88
|
<Banner
|
|
70
89
|
v-for="(err, i) in errors"
|
|
@@ -19,14 +19,14 @@ const validScope = 'openid profile email';
|
|
|
19
19
|
|
|
20
20
|
const mockModel = {
|
|
21
21
|
enabled: false,
|
|
22
|
-
id: '
|
|
22
|
+
id: 'genericoidc',
|
|
23
23
|
rancherUrl: validRancherUrl,
|
|
24
24
|
issuer: validIssuer,
|
|
25
25
|
authEndpoint: validAuthEndpoint,
|
|
26
26
|
scope: validScope,
|
|
27
27
|
clientId: validClientId,
|
|
28
28
|
clientSecret: validClientSecret,
|
|
29
|
-
type: '
|
|
29
|
+
type: 'genericOIDCConfig',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
const mockedAuthConfigMixin = {
|
package/edit/token.vue
CHANGED
|
@@ -13,6 +13,7 @@ import Select from '@shell/components/form/Select';
|
|
|
13
13
|
import CreateEditView from '@shell/mixins/create-edit-view';
|
|
14
14
|
import { diffFrom } from '@shell/utils/time';
|
|
15
15
|
import { filterHiddenLocalCluster, filterOnlyKubernetesClusters } from '@shell/utils/cluster';
|
|
16
|
+
import { SETTING } from '@shell/config/settings';
|
|
16
17
|
|
|
17
18
|
export default {
|
|
18
19
|
components: {
|
|
@@ -29,7 +30,7 @@ export default {
|
|
|
29
30
|
|
|
30
31
|
data() {
|
|
31
32
|
// Get the setting that defines the max token TTL allowed (in minutes)
|
|
32
|
-
const maxTTLSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING,
|
|
33
|
+
const maxTTLSetting = this.$store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.AUTH_TOKEN_MAX_TTL_MINUTES);
|
|
33
34
|
let maxTTL = 0;
|
|
34
35
|
|
|
35
36
|
try {
|
|
@@ -3,6 +3,7 @@ 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';
|
|
6
7
|
|
|
7
8
|
// Global variable used on mount, updated on route change and used in the render function
|
|
8
9
|
let app;
|
|
@@ -196,23 +197,19 @@ async function render(to, from, next) {
|
|
|
196
197
|
|
|
197
198
|
// If no Components matched, generate 404
|
|
198
199
|
if (!Components.length) {
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
// 4. This would allow harvester cluster pages to load on page reload
|
|
204
|
-
// We should really make authenticated middleware do less...
|
|
205
|
-
await callMiddleware.call(this, [{ options: { middleware: ['authenticated'] } }], app.context);
|
|
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 });
|
|
206
204
|
|
|
207
|
-
//
|
|
208
|
-
|
|
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;
|
|
209
207
|
|
|
210
|
-
if (
|
|
211
|
-
|
|
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);
|
|
212
211
|
}
|
|
213
212
|
|
|
214
|
-
// Show error page
|
|
215
|
-
// this.error({ statusCode: 404, message: 'This page could not be found' });
|
|
216
213
|
errorRedirect(this, new Error('404: This page could not be found'));
|
|
217
214
|
|
|
218
215
|
return next();
|
|
@@ -163,14 +163,16 @@ export default {
|
|
|
163
163
|
const response = await this.$axios.get(url, { timeout: 5000 });
|
|
164
164
|
|
|
165
165
|
if (response?.status === 200) {
|
|
166
|
-
|
|
166
|
+
await this.$store.dispatch('management/findAll', { type: this.resource, opt: { force: true } });
|
|
167
167
|
btnCB(true);
|
|
168
168
|
this.close();
|
|
169
169
|
this.waiting = false;
|
|
170
170
|
}
|
|
171
171
|
} catch (e) {}
|
|
172
172
|
|
|
173
|
-
this.
|
|
173
|
+
if (this.waiting) {
|
|
174
|
+
this.waitForBackend(btnCB, id);
|
|
175
|
+
}
|
|
174
176
|
}, 5000);
|
|
175
177
|
},
|
|
176
178
|
|
|
@@ -2,7 +2,6 @@ import { DEFAULT_WORKSPACE } from '@shell/config/types';
|
|
|
2
2
|
import { applyProducts } from '@shell/store/type-map';
|
|
3
3
|
import { ClusterNotFoundError, RedirectToError } from '@shell/utils/error';
|
|
4
4
|
import { get } from '@shell/utils/object';
|
|
5
|
-
import dynamicPluginLoader from '@shell/pkg/dynamic-plugin-loader';
|
|
6
5
|
import { AFTER_LOGIN_ROUTE, WORKSPACE } from '@shell/store/prefs';
|
|
7
6
|
import { BACK_TO } from '@shell/config/local-storage';
|
|
8
7
|
import { NAME as FLEET_NAME } from '@shell/config/product/fleet.js';
|
|
@@ -107,24 +106,6 @@ export default async function({
|
|
|
107
106
|
});
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
if (!route.matched?.length) {
|
|
111
|
-
// If there are no matching routes we could be trying to nav to a page belonging to a dynamic plugin which needs loading
|
|
112
|
-
await Promise.all([
|
|
113
|
-
...always,
|
|
114
|
-
]);
|
|
115
|
-
|
|
116
|
-
// If a plugin claims the route and is loaded correctly we'll get a route back
|
|
117
|
-
const newLocation = await dynamicPluginLoader.check({ route, store });
|
|
118
|
-
|
|
119
|
-
// If we have a new location, double check that it's actually valid
|
|
120
|
-
const resolvedRoute = newLocation ? store.app.router.resolve(newLocation) : null;
|
|
121
|
-
|
|
122
|
-
if (resolvedRoute?.route.matched.length) {
|
|
123
|
-
// Note - don't use `redirect` or `store.app.route` (breaks feature by failing to run middleware in default layout)
|
|
124
|
-
return next(newLocation);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
109
|
// Ensure that the activeNamespaceCache is updated given the change of context either from or to a place where it uses workspaces
|
|
129
110
|
// When fleet moves to it's own package this should be moved to pkg onEnter/onLeave
|
|
130
111
|
if ((oldProduct === FLEET_NAME || product === FLEET_NAME) && oldProduct !== product) {
|
package/mixins/auth-config.js
CHANGED
|
@@ -273,7 +273,7 @@ export default {
|
|
|
273
273
|
|
|
274
274
|
// KeyCloakOIDCConfig --> OIDCConfig
|
|
275
275
|
set(this.model, 'rancherUrl', `${ serverUrl }/verify-auth`);
|
|
276
|
-
set(this.model, 'scope', BASE_SCOPES.
|
|
276
|
+
set(this.model, 'scope', this.model.id === 'keycloakoidc' ? BASE_SCOPES.keycloakoidc[0] : BASE_SCOPES.genericoidc[0]);
|
|
277
277
|
break;
|
|
278
278
|
}
|
|
279
279
|
|
package/models/driver.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DESCRIPTION } from '@shell/config/labels-annotations';
|
|
2
2
|
import NormanModel from '@shell/plugins/steve/norman-class';
|
|
3
3
|
import { KONTAINER_TO_DRIVER } from './management.cattle.io.kontainerdriver';
|
|
4
|
+
import capitalize from 'lodash/capitalize';
|
|
4
5
|
|
|
5
6
|
export default class Driver extends NormanModel {
|
|
6
7
|
get canViewYaml() {
|
|
@@ -20,12 +21,12 @@ export default class Driver extends NormanModel {
|
|
|
20
21
|
}
|
|
21
22
|
}
|
|
22
23
|
|
|
23
|
-
return KONTAINER_TO_DRIVER[this.id] || this.id;
|
|
24
|
+
return KONTAINER_TO_DRIVER[this.id] || this.name || this.id;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
get nameDisplay() {
|
|
27
28
|
const path = `cluster.provider.${ this.driverName }`;
|
|
28
|
-
const label = this.driverName
|
|
29
|
+
const label = capitalize(this.driverName);
|
|
29
30
|
|
|
30
31
|
return this.$rootGetters['i18n/withFallback'](path, label);
|
|
31
32
|
}
|