@rancher/shell 3.0.9 → 3.0.10
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/styles/base/_color.scss +4 -0
- package/assets/styles/themes/_light.scss +6 -6
- package/assets/styles/themes/_modern.scss +14 -6
- package/assets/translations/en-us.yaml +2 -5
- package/components/CopyToClipboard.vue +28 -0
- package/components/CopyToClipboardText.vue +4 -0
- package/components/CruResource.vue +1 -0
- package/components/GlobalRoleBindings.vue +1 -5
- package/components/ResourceDetail/index.vue +0 -21
- package/components/__tests__/CruResource.test.ts +35 -1
- package/composables/useIsNewDetailPageEnabled.test.ts +98 -0
- package/composables/useIsNewDetailPageEnabled.ts +12 -0
- package/config/product/explorer.js +11 -1
- package/config/table-headers.js +0 -9
- package/config/types.js +0 -1
- package/edit/auth/github-app-steps.vue +2 -0
- package/edit/auth/github-steps.vue +2 -0
- package/edit/management.cattle.io.user.vue +60 -35
- package/edit/token.vue +29 -68
- package/models/token.js +0 -4
- package/package.json +8 -8
- package/pages/account/index.vue +67 -96
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +66 -9
- package/pages/c/_cluster/explorer/index.vue +2 -19
- package/pkg/auto-import.js +41 -0
- package/plugins/dashboard-store/resource-class.js +2 -2
- package/plugins/steve/__tests__/steve-class.test.ts +1 -1
- package/plugins/steve/steve-class.js +3 -3
- package/plugins/steve/steve-pagination-utils.ts +2 -4
- package/rancher-components/Pill/RcCounterBadge/RcCounterBadge.vue +7 -7
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +5 -2
- package/rancher-components/RcIcon/types.ts +2 -2
- package/rancher-components/RcSection/RcSection.test.ts +323 -0
- package/rancher-components/RcSection/RcSection.vue +252 -0
- package/rancher-components/RcSection/RcSectionActions.test.ts +212 -0
- package/rancher-components/RcSection/RcSectionActions.vue +85 -0
- package/rancher-components/RcSection/RcSectionBadges.test.ts +149 -0
- package/rancher-components/RcSection/RcSectionBadges.vue +29 -0
- package/rancher-components/RcSection/index.ts +12 -0
- package/rancher-components/RcSection/types.ts +86 -0
- package/scripts/test-plugins-build.sh +5 -4
- package/types/shell/index.d.ts +92 -108
- package/utils/style.ts +17 -0
- package/utils/units.js +14 -5
- package/models/ext.cattle.io.token.js +0 -48
|
@@ -571,17 +571,17 @@
|
|
|
571
571
|
--rc-success: #{$green001};
|
|
572
572
|
--rc-success-secondary: #{$green002};
|
|
573
573
|
|
|
574
|
-
--rc-warning: #{$
|
|
575
|
-
--rc-warning-secondary: #{$
|
|
574
|
+
--rc-warning: #{$yellow002};
|
|
575
|
+
--rc-warning-secondary: #{$yellow001};
|
|
576
576
|
|
|
577
577
|
--rc-error: #{$red001};
|
|
578
578
|
--rc-error-secondary: #{$red002};
|
|
579
579
|
|
|
580
|
-
--rc-unknown: #{$
|
|
581
|
-
--rc-unknown-secondary: #{$
|
|
580
|
+
--rc-unknown: #{$gray004};
|
|
581
|
+
--rc-unknown-secondary: #{$gray001};
|
|
582
582
|
|
|
583
|
-
--rc-none: #{$
|
|
584
|
-
--rc-none-secondary: #{$
|
|
583
|
+
--rc-none: #{$gray004};
|
|
584
|
+
--rc-none-secondary: #{$gray002};
|
|
585
585
|
|
|
586
586
|
--rc-primary-hover: #{$blue003};
|
|
587
587
|
|
|
@@ -701,17 +701,17 @@ BODY, .theme-light {
|
|
|
701
701
|
--rc-success: #{$green001};
|
|
702
702
|
--rc-success-secondary: #{$green002};
|
|
703
703
|
|
|
704
|
-
--rc-warning: #{$
|
|
705
|
-
--rc-warning-secondary: #{$
|
|
704
|
+
--rc-warning: #{$yellow002};
|
|
705
|
+
--rc-warning-secondary: #{$yellow001};
|
|
706
706
|
|
|
707
707
|
--rc-error: #{$red001};
|
|
708
708
|
--rc-error-secondary: #{$red002};
|
|
709
709
|
|
|
710
|
-
--rc-unknown: #{$
|
|
711
|
-
--rc-unknown-secondary: #{$
|
|
710
|
+
--rc-unknown: #{$gray004};
|
|
711
|
+
--rc-unknown-secondary: #{$gray001};
|
|
712
712
|
|
|
713
|
-
--rc-none: #{$
|
|
714
|
-
--rc-none-secondary: #{$
|
|
713
|
+
--rc-none: #{$gray004};
|
|
714
|
+
--rc-none-secondary: #{$gray002};
|
|
715
715
|
|
|
716
716
|
--rc-primary-hover: #{$blue003};
|
|
717
717
|
|
|
@@ -724,6 +724,10 @@ BODY, .theme-light {
|
|
|
724
724
|
--rc-disabled-background: #{$gray001};
|
|
725
725
|
--rc-disabled-text-color: #{$gray004};
|
|
726
726
|
|
|
727
|
+
--rc-section-background-primary: #{$lightest};
|
|
728
|
+
--rc-section-background-secondary: #{$lighter};
|
|
729
|
+
--rc-section-action-color: #{$gray009};
|
|
730
|
+
|
|
727
731
|
--rc-image-bg: #{$lightest};
|
|
728
732
|
--rc-image-color: #{$darkest};
|
|
729
733
|
|
|
@@ -1068,6 +1072,10 @@ BODY, .theme-dark {
|
|
|
1068
1072
|
--rc-disabled-background: #{$gray005};
|
|
1069
1073
|
--rc-disabled-text-color: #{$gray004};
|
|
1070
1074
|
|
|
1075
|
+
--rc-section-background-primary: #{$gray007};
|
|
1076
|
+
--rc-section-background-secondary: #{$gray008};
|
|
1077
|
+
--rc-section-action-color: #{$gray010};
|
|
1078
|
+
|
|
1071
1079
|
--rc-image-bg: #{$lightest};
|
|
1072
1080
|
--rc-image-color: #{$darkest};
|
|
1073
1081
|
|
|
@@ -30,6 +30,7 @@ generic:
|
|
|
30
30
|
comma: ', '
|
|
31
31
|
copy: Copy
|
|
32
32
|
copyToClipboard: Copy text to Clipboard
|
|
33
|
+
copyValueToClipboard: 'Copy {value} to Clipboard'
|
|
33
34
|
copiedToClipboard: Text copied to Clipboard
|
|
34
35
|
create: Create
|
|
35
36
|
created: Created
|
|
@@ -438,7 +439,6 @@ accountAndKeys:
|
|
|
438
439
|
notAllowed: You do not have permission to manage API Keys
|
|
439
440
|
apiEndpoint: "API Endpoint:"
|
|
440
441
|
copyApiEnpoint: Copy API Endpoint to clipboard
|
|
441
|
-
normanTokenDeprecation: The API Keys feature is being migrated to a new API. Any existing API Keys from the legacy API will continue to work, but new API Keys will be created using the new API.
|
|
442
442
|
add:
|
|
443
443
|
description:
|
|
444
444
|
label: Description
|
|
@@ -464,9 +464,7 @@ accountAndKeys:
|
|
|
464
464
|
month: Months
|
|
465
465
|
year: Years
|
|
466
466
|
scope: Scope
|
|
467
|
-
userPrincipal: User Principal
|
|
468
467
|
noScope: No Scope
|
|
469
|
-
enabled: Token enabled
|
|
470
468
|
info:
|
|
471
469
|
accessKey: Access Key
|
|
472
470
|
secretKey: Secret Key
|
|
@@ -475,7 +473,7 @@ accountAndKeys:
|
|
|
475
473
|
keyCreated: A new API Key has been created
|
|
476
474
|
bearerTokenTip: "Access Key and Secret Key can be sent as the username and password for HTTP Basic auth to authorize requests. You can also combine them to use as a Bearer token:"
|
|
477
475
|
ttlLimitedWarning: The Expiry time for this API Key was reduced due to system configuration
|
|
478
|
-
|
|
476
|
+
|
|
479
477
|
addClusterMemberDialog:
|
|
480
478
|
title: Add Cluster Member
|
|
481
479
|
|
|
@@ -6857,7 +6855,6 @@ storageClass:
|
|
|
6857
6855
|
tooltip: By default the default storage class on the host Harvester cluster is used.
|
|
6858
6856
|
|
|
6859
6857
|
tableHeaders:
|
|
6860
|
-
isLegacy: Legacy
|
|
6861
6858
|
assuredConcurrencyShares: Assured Concurrency Shares
|
|
6862
6859
|
autoscaler: Autoscaler
|
|
6863
6860
|
accessKey: Access Key
|
|
@@ -38,7 +38,35 @@ export default {
|
|
|
38
38
|
success-label="Copied!"
|
|
39
39
|
error-label="Error Copying"
|
|
40
40
|
v-bind="$attrs"
|
|
41
|
+
:success-color="$attrs['action-color'] || 'role-primary'"
|
|
42
|
+
:waiting-color="$attrs['action-color'] || 'role-primary'"
|
|
41
43
|
:delay="2000"
|
|
42
44
|
@click="clicked"
|
|
43
45
|
/>
|
|
44
46
|
</template>
|
|
47
|
+
|
|
48
|
+
<style lang="scss" scoped>
|
|
49
|
+
.icon-btn {
|
|
50
|
+
min-height: 24px;
|
|
51
|
+
min-width: 24px;
|
|
52
|
+
justify-content: center;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.bg-transparent {
|
|
56
|
+
&:active {
|
|
57
|
+
background-color: var(--primary-keyboard-focus);
|
|
58
|
+
color: var(--primary-text);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
&:focus-visible {
|
|
62
|
+
@include focus-outline;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.role-primary {
|
|
67
|
+
&:active {
|
|
68
|
+
background-color: var(--primary-keyboard-focus);
|
|
69
|
+
color: var(--primary-text);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
</style>
|
|
@@ -49,10 +49,6 @@ export default {
|
|
|
49
49
|
userId: {
|
|
50
50
|
type: String,
|
|
51
51
|
default: ''
|
|
52
|
-
},
|
|
53
|
-
watchOverride: {
|
|
54
|
-
type: Boolean,
|
|
55
|
-
default: true,
|
|
56
52
|
}
|
|
57
53
|
},
|
|
58
54
|
async fetch() {
|
|
@@ -142,7 +138,7 @@ export default {
|
|
|
142
138
|
this.update();
|
|
143
139
|
},
|
|
144
140
|
userId(userId, oldUserId) {
|
|
145
|
-
if (userId === oldUserId
|
|
141
|
+
if (userId === oldUserId) {
|
|
146
142
|
return;
|
|
147
143
|
}
|
|
148
144
|
this.update();
|
|
@@ -403,25 +403,6 @@ export default {
|
|
|
403
403
|
// Remove id? How does subtype get in (cluster/node)
|
|
404
404
|
this.detailComponent = this.$store.getters['type-map/importDetail'](detailResource, id);
|
|
405
405
|
this.editComponent = this.$store.getters['type-map/importEdit'](editResource, id);
|
|
406
|
-
},
|
|
407
|
-
/**
|
|
408
|
-
* Sets the mode and initializes the resource components.
|
|
409
|
-
*
|
|
410
|
-
* This method sets the mode of the component and configures the resource
|
|
411
|
-
* components based on the provided user and resource.
|
|
412
|
-
*
|
|
413
|
-
* @param {Object} payload - An object containing the mode, user, and
|
|
414
|
-
* resource properties.
|
|
415
|
-
* @param {string} payload.mode - The mode to set.
|
|
416
|
-
* @param {Object} payload.user - The user object containing user-specific
|
|
417
|
-
* information.
|
|
418
|
-
* @param {string} payload.resource - The resource string to use for
|
|
419
|
-
* initialization.
|
|
420
|
-
*/
|
|
421
|
-
setMode({ mode, userId, resource }) {
|
|
422
|
-
this.mode = mode;
|
|
423
|
-
this.value.id = userId;
|
|
424
|
-
this.configureResource(userId, resource);
|
|
425
406
|
}
|
|
426
407
|
}
|
|
427
408
|
};
|
|
@@ -444,7 +425,6 @@ export default {
|
|
|
444
425
|
:class="{'flex-content': flexContent}"
|
|
445
426
|
:resource-errors="errors"
|
|
446
427
|
@update:value="$emit('input', $event)"
|
|
447
|
-
@update:mode="setMode"
|
|
448
428
|
@set-subtype="setSubtype"
|
|
449
429
|
/>
|
|
450
430
|
<div v-else>
|
|
@@ -514,7 +494,6 @@ export default {
|
|
|
514
494
|
:real-mode="realMode"
|
|
515
495
|
:class="{'flex-content': flexContent}"
|
|
516
496
|
@update:value="$emit('input', $event)"
|
|
517
|
-
@update:mode="setMode"
|
|
518
497
|
@set-subtype="setSubtype"
|
|
519
498
|
/>
|
|
520
499
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import CruResource from '@shell/components/CruResource.vue';
|
|
3
|
-
import { _EDIT, _YAML } from '@shell/config/query-params';
|
|
3
|
+
import { _CREATE, _EDIT, _VIEW, _YAML } from '@shell/config/query-params';
|
|
4
4
|
import TextAreaAutoGrow from '@components/Form/TextArea/TextAreaAutoGrow.vue';
|
|
5
5
|
|
|
6
6
|
describe('component: CruResource', () => {
|
|
@@ -171,6 +171,40 @@ describe('component: CruResource', () => {
|
|
|
171
171
|
expect(event.preventDefault).toHaveBeenCalledWith();
|
|
172
172
|
});
|
|
173
173
|
|
|
174
|
+
it.each([
|
|
175
|
+
[_EDIT, true],
|
|
176
|
+
[_CREATE, true],
|
|
177
|
+
[_VIEW, false],
|
|
178
|
+
])('should render CruResourceFooter when mode is %s: %s', (mode: string, shouldRender: boolean) => {
|
|
179
|
+
const wrapper = mount(CruResource, {
|
|
180
|
+
props: {
|
|
181
|
+
canYaml: false,
|
|
182
|
+
mode,
|
|
183
|
+
resource: {}
|
|
184
|
+
},
|
|
185
|
+
global: {
|
|
186
|
+
mocks: {
|
|
187
|
+
$store: {
|
|
188
|
+
getters: {
|
|
189
|
+
currentStore: () => 'current_store',
|
|
190
|
+
'current_store/schemaFor': jest.fn(),
|
|
191
|
+
'current_store/all': jest.fn(),
|
|
192
|
+
'i18n/t': jest.fn(),
|
|
193
|
+
'i18n/exists': jest.fn(),
|
|
194
|
+
},
|
|
195
|
+
dispatch: jest.fn(),
|
|
196
|
+
},
|
|
197
|
+
$route: { query: { AS: _YAML } },
|
|
198
|
+
$router: { applyQuery: jest.fn() },
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const footer = wrapper.find('.cru-resource-footer');
|
|
204
|
+
|
|
205
|
+
expect(footer.exists()).toBe(shouldRender);
|
|
206
|
+
});
|
|
207
|
+
|
|
174
208
|
it('should not prevent default events on keypress Enter', async() => {
|
|
175
209
|
const event = { preventDefault: jest.fn() };
|
|
176
210
|
const wrapper = mount(CruResource, {
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { useIsNewDetailPageEnabled } from '@shell/composables/useIsNewDetailPageEnabled';
|
|
2
|
+
|
|
3
|
+
const mockStore: any = { getters: {} };
|
|
4
|
+
const mockRoute: any = { query: {} };
|
|
5
|
+
|
|
6
|
+
jest.mock('vuex', () => ({ useStore: () => mockStore }));
|
|
7
|
+
jest.mock('vue-router', () => ({ useRoute: () => mockRoute }));
|
|
8
|
+
|
|
9
|
+
const mockGetVersionInfo = jest.fn(() => ({ fullVersion: '2.12.0' }));
|
|
10
|
+
|
|
11
|
+
jest.mock('@shell/utils/version', () => ({ getVersionInfo: (...args: any[]) => mockGetVersionInfo(...args) }));
|
|
12
|
+
|
|
13
|
+
describe('useIsNewDetailPageEnabled', () => {
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockRoute.query = {};
|
|
16
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: '2.12.0' });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('version gating', () => {
|
|
20
|
+
it('should return false when version is below 2.12.0', () => {
|
|
21
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: '2.11.9' });
|
|
22
|
+
const result = useIsNewDetailPageEnabled();
|
|
23
|
+
|
|
24
|
+
expect(result.value).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return false when version is 2.10.0', () => {
|
|
28
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: '2.10.0' });
|
|
29
|
+
const result = useIsNewDetailPageEnabled();
|
|
30
|
+
|
|
31
|
+
expect(result.value).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return true when version is exactly 2.12.0 and no legacy query', () => {
|
|
35
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: '2.12.0' });
|
|
36
|
+
const result = useIsNewDetailPageEnabled();
|
|
37
|
+
|
|
38
|
+
expect(result.value).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should return true when version is above 2.12.0', () => {
|
|
42
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: '2.13.0' });
|
|
43
|
+
const result = useIsNewDetailPageEnabled();
|
|
44
|
+
|
|
45
|
+
expect(result.value).toBe(true);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should return false when version is undefined', () => {
|
|
49
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: undefined });
|
|
50
|
+
const result = useIsNewDetailPageEnabled();
|
|
51
|
+
|
|
52
|
+
expect(result.value).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should return false when version is null', () => {
|
|
56
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: null });
|
|
57
|
+
const result = useIsNewDetailPageEnabled();
|
|
58
|
+
|
|
59
|
+
expect(result.value).toBe(false);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should handle pre-release version strings', () => {
|
|
63
|
+
mockGetVersionInfo.mockReturnValue({ fullVersion: 'v2.12.1-rc1' });
|
|
64
|
+
const result = useIsNewDetailPageEnabled();
|
|
65
|
+
|
|
66
|
+
expect(result.value).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('legacy query param (with version >= 2.12.0)', () => {
|
|
71
|
+
it('should return true when no legacy query param is present', () => {
|
|
72
|
+
const result = useIsNewDetailPageEnabled();
|
|
73
|
+
|
|
74
|
+
expect(result.value).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should return false when legacy query param is "true"', () => {
|
|
78
|
+
mockRoute.query = { legacy: 'true' };
|
|
79
|
+
const result = useIsNewDetailPageEnabled();
|
|
80
|
+
|
|
81
|
+
expect(result.value).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should return true when legacy query param is "false"', () => {
|
|
85
|
+
mockRoute.query = { legacy: 'false' };
|
|
86
|
+
const result = useIsNewDetailPageEnabled();
|
|
87
|
+
|
|
88
|
+
expect(result.value).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should return true when legacy query param has an unexpected value', () => {
|
|
92
|
+
mockRoute.query = { legacy: 'something' };
|
|
93
|
+
const result = useIsNewDetailPageEnabled();
|
|
94
|
+
|
|
95
|
+
expect(result.value).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { useRoute } from 'vue-router';
|
|
2
2
|
import { LEGACY } from '@shell/config/query-params';
|
|
3
3
|
import { computed } from 'vue';
|
|
4
|
+
import { getVersionInfo } from '@shell/utils/version';
|
|
5
|
+
import semver from 'semver';
|
|
6
|
+
import { useStore } from 'vuex';
|
|
4
7
|
|
|
5
8
|
const enabledByDefault = true;
|
|
6
9
|
|
|
@@ -8,6 +11,15 @@ export const useIsNewDetailPageEnabled = () => {
|
|
|
8
11
|
const route = useRoute();
|
|
9
12
|
|
|
10
13
|
return computed(() => {
|
|
14
|
+
const store = useStore();
|
|
15
|
+
const { fullVersion } = getVersionInfo(store);
|
|
16
|
+
|
|
17
|
+
const coerced = semver.coerce(fullVersion) || { version: '0.0.0' };
|
|
18
|
+
|
|
19
|
+
if (!semver.gte(coerced.version, '2.12.0')) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
|
|
11
23
|
if (enabledByDefault) {
|
|
12
24
|
return route?.query?.[LEGACY] !== 'true';
|
|
13
25
|
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
USER_ID, USERNAME, USER_DISPLAY_NAME, USER_PROVIDER, USER_LAST_LOGIN, USER_DISABLED_IN, USER_DELETED_IN, WORKLOAD_ENDPOINTS, STORAGE_CLASS_DEFAULT,
|
|
20
20
|
STORAGE_CLASS_PROVISIONER, PERSISTENT_VOLUME_SOURCE,
|
|
21
21
|
HPA_REFERENCE, MIN_REPLICA, MAX_REPLICA, CURRENT_REPLICA,
|
|
22
|
-
DESCRIPTION, SUB_TYPE, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE, POD_RESTARTS,
|
|
22
|
+
ACCESS_KEY, DESCRIPTION, EXPIRES, EXPIRY_STATE, LAST_USED, SUB_TYPE, AGE_NORMAN, SCOPE_NORMAN, PERSISTENT_VOLUME_CLAIM, RECLAIM_POLICY, PV_REASON, WORKLOAD_HEALTH_SCALE, POD_RESTARTS,
|
|
23
23
|
DURATION, MESSAGE, REASON, EVENT_TYPE, OBJECT, ROLE, ROLES, VERSION, INTERNAL_EXTERNAL_IP, KUBE_NODE_OS, CPU, RAM, SECRET_DATA,
|
|
24
24
|
EVENT_LAST_SEEN_TIME,
|
|
25
25
|
EVENT_FIRST_SEEN_TIME,
|
|
@@ -546,6 +546,16 @@ export function init(store) {
|
|
|
546
546
|
AGE
|
|
547
547
|
]);
|
|
548
548
|
|
|
549
|
+
headers(NORMAN.TOKEN, [
|
|
550
|
+
EXPIRY_STATE,
|
|
551
|
+
ACCESS_KEY,
|
|
552
|
+
DESCRIPTION,
|
|
553
|
+
SCOPE_NORMAN,
|
|
554
|
+
LAST_USED,
|
|
555
|
+
EXPIRES,
|
|
556
|
+
AGE_NORMAN
|
|
557
|
+
]);
|
|
558
|
+
|
|
549
559
|
virtualType({
|
|
550
560
|
label: store.getters['i18n/t']('clusterIndexPage.header'),
|
|
551
561
|
group: 'Root',
|
package/config/table-headers.js
CHANGED
|
@@ -1033,15 +1033,6 @@ export const SCOPE_NORMAN = {
|
|
|
1033
1033
|
sort: ['clusterId'],
|
|
1034
1034
|
};
|
|
1035
1035
|
|
|
1036
|
-
export const NORMAN_KEY_DEPRECATION = {
|
|
1037
|
-
name: 'isNormanKeyDeprecated',
|
|
1038
|
-
labelKey: 'tableHeaders.isLegacy',
|
|
1039
|
-
value: (row) => row.isDeprecated ? 'True' : undefined,
|
|
1040
|
-
sort: 'isDeprecated',
|
|
1041
|
-
align: 'left',
|
|
1042
|
-
dashIfEmpty: true,
|
|
1043
|
-
};
|
|
1044
|
-
|
|
1045
1036
|
export const EXPIRES = {
|
|
1046
1037
|
name: 'expires',
|
|
1047
1038
|
value: 'expiresAt',
|
package/config/types.js
CHANGED
|
@@ -269,7 +269,6 @@ export const EXT = {
|
|
|
269
269
|
GROUP_MEMBERSHIP_REFRESH_REQUESTS: 'ext.cattle.io.groupmembershiprefreshrequest',
|
|
270
270
|
PASSWORD_CHANGE_REQUESTS: 'ext.cattle.io.passwordchangerequest',
|
|
271
271
|
KUBECONFIG: 'ext.cattle.io.kubeconfig',
|
|
272
|
-
TOKEN: 'ext.cattle.io.token',
|
|
273
272
|
};
|
|
274
273
|
|
|
275
274
|
export const CAPI = {
|
|
@@ -49,6 +49,7 @@ defineProps<{
|
|
|
49
49
|
<CopyToClipboard
|
|
50
50
|
label-as="tooltip"
|
|
51
51
|
:text="tArgs.serverUrl"
|
|
52
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
52
53
|
class="icon-btn"
|
|
53
54
|
action-color="bg-transparent"
|
|
54
55
|
/>
|
|
@@ -67,6 +68,7 @@ defineProps<{
|
|
|
67
68
|
<CopyToClipboard
|
|
68
69
|
:text="t(`authConfig.${name}.form.callback.value`, tArgs, true)"
|
|
69
70
|
label-as="tooltip"
|
|
71
|
+
:aria-label="t('generic.copyValueToClipboard', { value: t(`authConfig.${name}.form.callback.value`, tArgs, true) })"
|
|
70
72
|
class="icon-btn"
|
|
71
73
|
action-color="bg-transparent"
|
|
72
74
|
/>
|
|
@@ -40,6 +40,7 @@ defineProps<{
|
|
|
40
40
|
<b>{{ t(`authConfig.${name}.form.homepage.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
|
|
41
41
|
label-as="tooltip"
|
|
42
42
|
:text="tArgs.serverUrl"
|
|
43
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
43
44
|
class="icon-btn"
|
|
44
45
|
action-color="bg-transparent"
|
|
45
46
|
/>
|
|
@@ -49,6 +50,7 @@ defineProps<{
|
|
|
49
50
|
<b>{{ t(`authConfig.${name}.form.callback.label`) }}</b>: {{ tArgs.serverUrl }} <CopyToClipboard
|
|
50
51
|
:text="tArgs.serverUrl"
|
|
51
52
|
label-as="tooltip"
|
|
53
|
+
:aria-label="t('generic.copyValueToClipboard', { value: tArgs.serverUrl })"
|
|
52
54
|
class="icon-btn"
|
|
53
55
|
action-color="bg-transparent"
|
|
54
56
|
/>
|
|
@@ -16,8 +16,6 @@ export default {
|
|
|
16
16
|
ChangePassword, GlobalRoleBindings, CruResource, LabeledInput, Loading
|
|
17
17
|
},
|
|
18
18
|
|
|
19
|
-
emits: ['update:mode'],
|
|
20
|
-
|
|
21
19
|
mixins: [
|
|
22
20
|
CreateEditView
|
|
23
21
|
],
|
|
@@ -42,8 +40,7 @@ export default {
|
|
|
42
40
|
password: false,
|
|
43
41
|
roles: !showGlobalRoles,
|
|
44
42
|
rolesChanged: false,
|
|
45
|
-
}
|
|
46
|
-
watchOverride: false,
|
|
43
|
+
}
|
|
47
44
|
};
|
|
48
45
|
},
|
|
49
46
|
|
|
@@ -110,10 +107,20 @@ export default {
|
|
|
110
107
|
if (this.isCreate) {
|
|
111
108
|
const user = await this.createUser();
|
|
112
109
|
|
|
113
|
-
await this.
|
|
110
|
+
await this.createSecret(user);
|
|
111
|
+
await this.updateRoles(user);
|
|
112
|
+
|
|
113
|
+
// Show success notification only after ALL operations complete
|
|
114
|
+
// this is a "clone" of steve-class "processSaveResponse" toast/growl
|
|
115
|
+
this.$store.dispatch('growl/success', {
|
|
116
|
+
title: this.t('generic.autogeneratedCreated.title', { resource: user.kind }),
|
|
117
|
+
message: this.t('generic.autogeneratedCreated.message', { id: user.username, resource: user.kind }),
|
|
118
|
+
timeout: 3000
|
|
119
|
+
}, { root: true });
|
|
114
120
|
} else {
|
|
115
|
-
await this.editUser();
|
|
116
|
-
|
|
121
|
+
const user = await this.editUser();
|
|
122
|
+
|
|
123
|
+
await this.updateRoles(user);
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
this.$router.replace({ name: this.doneRoute });
|
|
@@ -139,21 +146,10 @@ export default {
|
|
|
139
146
|
username: this.form.username
|
|
140
147
|
});
|
|
141
148
|
|
|
142
|
-
const userSaved = await user.save(
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const secret = await this.$store.dispatch('management/create', {
|
|
147
|
-
type: SECRET,
|
|
148
|
-
metadata: {
|
|
149
|
-
namespace: 'cattle-local-user-passwords',
|
|
150
|
-
name: userSaved.id
|
|
151
|
-
},
|
|
152
|
-
data: { password: base64Encode(this.form.password.password) }
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
await secret.save();
|
|
156
|
-
}
|
|
149
|
+
const userSaved = await user.save({
|
|
150
|
+
// Don't show a success toast until the secret and GRB are also created
|
|
151
|
+
suppressSuccessToast: true,
|
|
152
|
+
});
|
|
157
153
|
|
|
158
154
|
return userSaved;
|
|
159
155
|
},
|
|
@@ -178,27 +174,57 @@ export default {
|
|
|
178
174
|
await wait(5000);
|
|
179
175
|
}
|
|
180
176
|
|
|
181
|
-
this.value.save();
|
|
177
|
+
const user = this.value.save();
|
|
178
|
+
|
|
179
|
+
return user;
|
|
182
180
|
},
|
|
183
181
|
|
|
184
|
-
async
|
|
182
|
+
async createSecret(user) {
|
|
183
|
+
if (this.form.password.password) {
|
|
184
|
+
try {
|
|
185
|
+
// create secret to hold user password
|
|
186
|
+
const secret = await this.$store.dispatch('management/create', {
|
|
187
|
+
type: SECRET,
|
|
188
|
+
metadata: {
|
|
189
|
+
namespace: 'cattle-local-user-passwords',
|
|
190
|
+
name: user.id
|
|
191
|
+
},
|
|
192
|
+
data: { password: base64Encode(this.form.password.password) }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
await secret.save();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (this.isCreate) {
|
|
198
|
+
try {
|
|
199
|
+
// If secret creation fails, attempt to clean up the user to maintain consistency
|
|
200
|
+
await user.remove();
|
|
201
|
+
} catch (cleanupErr) {
|
|
202
|
+
// Log cleanup error but prioritize original error for user feedback
|
|
203
|
+
console.error('Failed to clean up user after secret creation failure:', cleanupErr); // eslint-disable-line no-console
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
throw err;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
async updateRoles(user) {
|
|
185
213
|
if (!this.$refs.grb) {
|
|
186
214
|
return;
|
|
187
215
|
}
|
|
188
216
|
|
|
189
217
|
try {
|
|
190
|
-
await this.$refs.grb.save(
|
|
218
|
+
await this.$refs.grb.save(user.id);
|
|
191
219
|
} catch (err) {
|
|
192
220
|
if (this.isCreate) {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
}
|
|
201
|
-
);
|
|
221
|
+
try {
|
|
222
|
+
// If GRB creation fails, clean up the user to maintain consistency
|
|
223
|
+
await user.remove();
|
|
224
|
+
} catch (cleanupErr) {
|
|
225
|
+
// Log cleanup error but prioritize original error for user feedback
|
|
226
|
+
console.error('Failed to clean up user after GRB creation failure:', cleanupErr); // eslint-disable-line no-console
|
|
227
|
+
}
|
|
202
228
|
}
|
|
203
229
|
throw err;
|
|
204
230
|
}
|
|
@@ -274,7 +300,6 @@ export default {
|
|
|
274
300
|
:user-id="value.id || liveValue.id"
|
|
275
301
|
:mode="mode"
|
|
276
302
|
:real-mode="realMode"
|
|
277
|
-
:watch-override="watchOverride"
|
|
278
303
|
type="user"
|
|
279
304
|
@hasChanges="validation.rolesChanged = $event"
|
|
280
305
|
@canLogIn="validation.roles = $event"
|