@rancher/shell 3.0.3 → 3.0.5-rc.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.
Files changed (139) hide show
  1. package/assets/styles/base/_basic.scss +6 -0
  2. package/assets/styles/global/_button.scss +1 -0
  3. package/assets/translations/en-us.yaml +38 -3
  4. package/cloud-credential/aws.vue +2 -0
  5. package/components/AssignTo.vue +25 -11
  6. package/components/AsyncButton.vue +24 -7
  7. package/components/BannerGraphic.vue +1 -0
  8. package/components/CommunityLinks.vue +3 -3
  9. package/components/CopyToClipboardText.vue +2 -1
  10. package/components/DetailText.vue +5 -0
  11. package/components/DisableAuthProviderModal.vue +1 -0
  12. package/components/ExplorerMembers.vue +1 -1
  13. package/components/ExplorerProjectsNamespaces.vue +56 -14
  14. package/components/LandingPagePreference.vue +5 -3
  15. package/components/LocaleSelector.vue +38 -94
  16. package/components/ModalWithCard.vue +1 -0
  17. package/components/MoveModal.vue +1 -0
  18. package/components/PromptRemove.vue +2 -1
  19. package/components/PromptRestore.vue +1 -0
  20. package/components/ResourceCancelModal.vue +1 -0
  21. package/components/SortableTable/index.vue +35 -10
  22. package/components/StatusBadge.vue +10 -4
  23. package/components/__tests__/AsyncButton.test.ts +2 -2
  24. package/components/auth/Principal.vue +9 -3
  25. package/components/auth/__tests__/RoleDetailEdit.test.ts +3 -2
  26. package/components/form/ArrayList.vue +75 -54
  27. package/components/form/Command.vue +6 -15
  28. package/components/form/EnvVars.vue +15 -8
  29. package/components/form/HealthCheck.vue +3 -3
  30. package/components/form/HookOption.vue +11 -16
  31. package/components/form/KeyValue.vue +1 -1
  32. package/components/form/LabeledSelect.vue +2 -1
  33. package/components/form/LifecycleHooks.vue +3 -3
  34. package/components/form/MatchExpressions.vue +10 -7
  35. package/components/form/NameNsDescription.vue +123 -103
  36. package/components/form/Networking.vue +20 -12
  37. package/components/form/NodeAffinity.vue +31 -23
  38. package/components/form/NodeScheduling.vue +13 -3
  39. package/components/form/PodAffinity.vue +43 -43
  40. package/components/form/Probe.vue +67 -66
  41. package/components/form/ResourceQuota/Project.vue +5 -1
  42. package/components/form/ResourceSelector.vue +7 -9
  43. package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +6 -3
  44. package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +12 -1
  45. package/components/form/SSHKnownHosts/index.vue +16 -2
  46. package/components/form/Security.vue +54 -56
  47. package/components/form/Select.vue +31 -6
  48. package/components/form/ShellInput.vue +5 -1
  49. package/components/form/Tolerations.vue +5 -1
  50. package/components/form/ValueFromResource.vue +134 -121
  51. package/components/form/WorkloadPorts.vue +18 -18
  52. package/components/form/__tests__/ArrayList.test.ts +3 -0
  53. package/components/form/__tests__/MatchExpressions.test.ts +12 -12
  54. package/components/form/__tests__/NameNsDescription.test.ts +115 -14
  55. package/components/form/__tests__/Probe.test.ts +12 -8
  56. package/components/form/__tests__/SSHKnownHosts.test.ts +11 -0
  57. package/components/form/__tests__/Select.test.ts +37 -0
  58. package/components/formatter/InternalExternalIP.vue +2 -0
  59. package/components/formatter/SecretData.vue +20 -7
  60. package/components/nav/Group.vue +15 -1
  61. package/components/nav/Header.vue +1 -0
  62. package/components/nav/Type.vue +12 -1
  63. package/components/templates/blank.vue +4 -1
  64. package/components/templates/default.vue +2 -0
  65. package/components/templates/home.vue +4 -1
  66. package/components/templates/plain.vue +4 -1
  67. package/composables/useRuntimeFlag.ts +29 -0
  68. package/config/router/routes.js +20 -13
  69. package/core/types.ts +5 -0
  70. package/dialog/AddCustomBadgeDialog.vue +1 -0
  71. package/dialog/DeactivateDriverDialog.vue +5 -4
  72. package/dialog/ForceMachineRemoveDialog.vue +4 -1
  73. package/dialog/GitRepoForceUpdateDialog.vue +1 -1
  74. package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +16 -3
  75. package/edit/auth/__tests__/oidc.test.ts +152 -109
  76. package/edit/auth/azuread.vue +1 -0
  77. package/edit/auth/googleoauth.vue +4 -0
  78. package/edit/auth/oidc.vue +37 -4
  79. package/edit/cloudcredential.vue +1 -0
  80. package/edit/fleet.cattle.io.gitrepo.vue +1 -0
  81. package/edit/logging.banzaicloud.io.output/__tests__/logging.banzaicloud.io.output.test.ts +40 -9
  82. package/edit/networking.k8s.io.ingress/IngressClass.vue +7 -3
  83. package/edit/networking.k8s.io.ingress/__tests__/IngressClass.test.ts +58 -0
  84. package/edit/persistentvolume/__tests__/persistentvolume.test.ts +14 -2
  85. package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +1 -0
  86. package/edit/provisioning.cattle.io.cluster/rke2.vue +25 -34
  87. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +6 -1
  88. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +29 -1
  89. package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +2 -2
  90. package/edit/token.vue +2 -0
  91. package/edit/workload/index.vue +1 -0
  92. package/edit/workload/mixins/workload.js +0 -2
  93. package/list/management.cattle.io.feature.vue +1 -0
  94. package/list/provisioning.cattle.io.cluster.vue +20 -12
  95. package/machine-config/__tests__/vmwarevsphere.test.ts +48 -3
  96. package/machine-config/vmwarevsphere.vue +16 -0
  97. package/models/__tests__/namespace.test.ts +25 -1
  98. package/models/cloudcredential.js +5 -0
  99. package/models/kontainerdriver.js +6 -3
  100. package/models/management.cattle.io.node.js +3 -3
  101. package/models/namespace.js +4 -5
  102. package/models/nodedriver.js +6 -3
  103. package/models/workload.js +4 -1
  104. package/package.json +4 -4
  105. package/pages/about.vue +16 -8
  106. package/pages/account/index.vue +4 -1
  107. package/pages/auth/login.vue +11 -3
  108. package/pages/auth/logout.vue +4 -1
  109. package/pages/auth/setup.vue +1 -0
  110. package/pages/auth/verify.vue +4 -1
  111. package/pages/c/_cluster/apps/charts/chart.vue +1 -1
  112. package/pages/diagnostic.vue +47 -2
  113. package/pages/fail-whale.vue +6 -3
  114. package/pages/home.vue +24 -18
  115. package/pages/support/index.vue +4 -1
  116. package/promptRemove/management.cattle.io.fleetworkspace.vue +1 -1
  117. package/promptRemove/management.cattle.io.globalrole.vue +1 -1
  118. package/promptRemove/management.cattle.io.project.vue +2 -2
  119. package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
  120. package/promptRemove/pod.vue +1 -1
  121. package/rancher-components/Form/Radio/RadioGroup.vue +25 -23
  122. package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +3 -3
  123. package/rancher-components/RcDropdown/RcDropdown.vue +6 -5
  124. package/rancher-components/RcDropdown/RcDropdownMenu.vue +4 -2
  125. package/rancher-components/RcDropdown/RcDropdownTrigger.vue +12 -2
  126. package/rancher-components/RcDropdown/useDropdownCollection.ts +8 -0
  127. package/rancher-components/RcDropdown/useDropdownContext.ts +9 -3
  128. package/scripts/extension/publish +1 -0
  129. package/server/har-file.js +25 -3
  130. package/store/features.js +2 -1
  131. package/store/type-map.js +4 -0
  132. package/types/shell/index.d.ts +9 -2
  133. package/utils/__tests__/string.test.ts +2 -2
  134. package/utils/cluster.js +35 -0
  135. package/utils/string.js +1 -3
  136. package/utils/validators/machine-pool.ts +20 -0
  137. package/components/formatter/ExtensionCache.vue +0 -74
  138. package/components/formatter/Port.vue +0 -24
  139. package/components/formatter/SecretType.vue +0 -41
@@ -32,151 +32,194 @@ const mockModel = {
32
32
  };
33
33
 
34
34
  describe('oidc.vue', () => {
35
- let wrapper: VueWrapper<any, any>;
36
- const requiredSetup = () => ({
37
- data() {
38
- return {
39
- isEnabling: false,
40
- editConfig: false,
41
- model: { ...mockModel },
42
- serverSetting: null,
43
- errors: [],
44
- originalModel: null,
45
- principals: [],
46
- authConfigName: 'oidc',
47
- } as any; // any is necessary as in pre-existing tests we are including inherited mixins values
48
- },
49
- global: {
50
- mocks: {
51
- $fetchState: { pending: false },
52
- $store: {
53
- getters: {
54
- currentStore: () => 'current_store',
55
- 'current_store/schemaFor': jest.fn(),
56
- 'current_store/all': jest.fn(),
57
- 'i18n/t': (val: string) => val,
58
- 'i18n/exists': jest.fn(),
35
+ describe('given default valid values', () => {
36
+ let wrapper: VueWrapper<any, any>;
37
+ const requiredSetup = () => ({
38
+ data() {
39
+ return {
40
+ isEnabling: false,
41
+ editConfig: false,
42
+ model: { ...mockModel },
43
+ serverSetting: null,
44
+ errors: [],
45
+ originalModel: null,
46
+ principals: [],
47
+ authConfigName: 'oidc',
48
+ } as any; // any is necessary as in pre-existing tests we are including inherited mixins values
49
+ },
50
+ global: {
51
+ mocks: {
52
+ $fetchState: { pending: false },
53
+ $store: {
54
+ getters: {
55
+ currentStore: () => 'current_store',
56
+ 'current_store/schemaFor': jest.fn(),
57
+ 'current_store/all': jest.fn(),
58
+ 'i18n/t': (val: string) => val,
59
+ 'i18n/exists': jest.fn(),
60
+ },
61
+ dispatch: jest.fn()
59
62
  },
60
- dispatch: jest.fn()
63
+ $route: { query: { AS: '' }, params: { id: 'oicd' } },
64
+ $router: { applyQuery: jest.fn() },
61
65
  },
62
- $route: { query: { AS: '' }, params: { id: 'oicd' } },
63
- $router: { applyQuery: jest.fn() },
64
66
  },
65
- },
66
- props: {
67
- value: { applicationSecret: '' },
68
- mode: _EDIT,
69
- },
70
- });
67
+ props: {
68
+ value: { applicationSecret: '' },
69
+ mode: _EDIT,
70
+ },
71
+ });
71
72
 
72
- beforeEach(() => {
73
- wrapper = mount(oidc, { ...requiredSetup() });
74
- });
75
- afterEach(() => {
76
- wrapper.unmount();
77
- });
73
+ beforeEach(() => {
74
+ wrapper = mount(oidc, { ...requiredSetup() });
75
+ });
78
76
 
79
- it('have "Create" button enabled when provider is enabled and not editing config', async() => {
80
- wrapper.setData({ model: { enabled: true }, editConfig: false });
81
- await nextTick();
77
+ afterEach(() => {
78
+ wrapper.unmount();
79
+ });
82
80
 
83
- const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
81
+ describe('have "Create" button disabled', () => {
82
+ it('given missing Auth endpoint URL', () => {
83
+ wrapper.vm.model.authEndpoint = '';
84
+ wrapper.vm.model.scopes = 'openid profile email'; // set scope to be sure
85
+ wrapper.vm.oidcScope = ['openid', 'profile', 'email']; // TODO #13457: this is duplicated due wrong format of scopes
84
86
 
85
- expect(saveButton.disabled).toBe(false);
86
- });
87
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
87
88
 
88
- it('have "Create" button disabled when provider is disabled and editing config before fields are filled in', async() => {
89
- wrapper.setData({ model: {}, editConfig: true });
90
- await nextTick();
89
+ expect(saveButton.disabled).toBe(true);
90
+ });
91
91
 
92
- const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
92
+ it('given missing required basic scopes', () => {
93
+ wrapper.vm.model.authEndpoint = 'whatever'; // set auth endpoint to be sure
94
+ wrapper.vm.model.scopes = 'something else'; // set wrong scope
95
+ wrapper.vm.oidcScope = ['something', 'else']; // TODO #13457: this is duplicated due wrong format of scopes
93
96
 
94
- expect(saveButton.disabled).toBe(true);
95
- });
97
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
96
98
 
97
- it('have "Create" button disabled when provider is disabled and editing config after required fields and scope is missing openid', async() => {
98
- wrapper.setData({ oidcUrls: { url: validUrl, realm: validRealm } });
99
- await nextTick();
99
+ expect(saveButton.disabled).toBe(true);
100
+ });
100
101
 
101
- const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
102
+ it('when provider is disabled and editing config before fields are filled in', async() => {
103
+ wrapper.setData({ model: {}, editConfig: true });
104
+ await nextTick();
102
105
 
103
- expect(saveButton.disabled).toBe(true);
104
- });
106
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
105
107
 
106
- it('have "Create" button enabled when customEndpoint is disabled and required fields are filled in', async() => {
107
- wrapper.setData({ oidcUrls: { url: validUrl, realm: validRealm }, oidcScope: validScope.split(' ') });
108
- await nextTick();
108
+ expect(saveButton.disabled).toBe(true);
109
+ });
109
110
 
110
- const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
111
+ it('when provider is disabled and editing config after required fields and scope is missing openid', async() => {
112
+ wrapper.setData({ oidcUrls: { url: validUrl, realm: validRealm } });
113
+ await nextTick();
111
114
 
112
- expect(saveButton.disabled).toBe(false);
113
- });
115
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
114
116
 
115
- it('have "Create" button enabled when customEndpoint is enabled and required fields are filled in', async() => {
116
- wrapper.setData({ customEndpoint: { value: true }, oidcScope: validScope.split(' ') });
117
- await nextTick();
117
+ expect(saveButton.disabled).toBe(true);
118
+ });
119
+ });
118
120
 
119
- const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
121
+ describe('have "Create" button enabled', () => {
122
+ it('when customEndpoint is disabled and required fields are filled in', async() => {
123
+ wrapper.setData({ oidcUrls: { url: validUrl, realm: validRealm }, oidcScope: validScope.split(' ') });
124
+ await nextTick();
120
125
 
121
- expect(saveButton.disabled).toBe(false);
122
- });
126
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
123
127
 
124
- it('updates issuer endpoint when oidcUrls.url and oidcUrls.realm changes', async() => {
125
- wrapper.setData({ oidcUrls: { url: validUrl } });
126
- await nextTick();
128
+ expect(saveButton.disabled).toBe(false);
129
+ });
127
130
 
128
- expect(wrapper.vm.model.issuer).toBe(`${ validUrl }/realms/`);
131
+ it('when customEndpoint is enabled and required fields are filled in', async() => {
132
+ wrapper.setData({ customEndpoint: { value: true }, oidcScope: validScope.split(' ') });
133
+ await nextTick();
129
134
 
130
- wrapper.setData({ oidcUrls: { realm: validRealm } });
131
- await nextTick();
135
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
132
136
 
133
- expect(wrapper.vm.model.issuer).toBe(`${ validUrl }/realms/${ validRealm }`);
134
- });
137
+ expect(saveButton.disabled).toBe(false);
138
+ });
135
139
 
136
- it('`groupSearchEnabled` defaults to false', async() => {
137
- const groupSearchCheckbox = wrapper.getComponent('[data-testid="input-group-search"]');
140
+ it('when provider is enabled and not editing config', async() => {
141
+ wrapper.setData({ model: { enabled: true }, editConfig: false });
142
+ await nextTick();
138
143
 
139
- expect(groupSearchCheckbox.isVisible()).toBe(true);
140
- expect(wrapper.vm.model.groupSearchEnabled).toBe(false);
141
- });
144
+ const saveButton = wrapper.find('[data-testid="form-save"]').element as HTMLInputElement;
142
145
 
143
- it('`groupSearchEnabled` updates when checkbox is clicked', async() => {
144
- const groupSearchCheckbox = wrapper.getComponent('[data-testid="input-group-search"]');
146
+ expect(saveButton.disabled).toBe(false);
147
+ });
148
+ });
145
149
 
146
- await groupSearchCheckbox.find('[role="checkbox"]').trigger('click');
150
+ it('updates issuer endpoint when oidcUrls.url and oidcUrls.realm changes', async() => {
151
+ wrapper.setData({ oidcUrls: { url: validUrl } });
152
+ await nextTick();
147
153
 
148
- expect(groupSearchCheckbox.isVisible()).toBe(true);
149
- expect(wrapper.vm.model.groupSearchEnabled).toBe(true);
150
- });
154
+ expect(wrapper.vm.model.issuer).toBe(`${ validUrl }/realms/`);
151
155
 
152
- it('changing URL should update issuer and auth-endpoint if Keycloak', async() => {
153
- wrapper.vm.model.id = 'keycloakoidc';
154
- const newUrl = 'whatever';
156
+ wrapper.setData({ oidcUrls: { realm: validRealm } });
157
+ await nextTick();
155
158
 
156
- await wrapper.find(`[data-testid="oidc-url"]`).setValue(newUrl);
157
- await wrapper.vm.$nextTick();
159
+ expect(wrapper.vm.model.issuer).toBe(`${ validUrl }/realms/${ validRealm }`);
160
+ });
158
161
 
159
- const issuer = (wrapper.find('[data-testid="oidc-issuer"]').element as HTMLInputElement).value;
160
- const endpoint = (wrapper.find('[data-testid="oidc-auth-endpoint"]').element as HTMLInputElement).value;
162
+ it('`groupSearchEnabled` defaults to false', async() => {
163
+ const groupSearchCheckbox = wrapper.getComponent('[data-testid="input-group-search"]');
161
164
 
162
- expect(issuer).toBe(`${ newUrl }/realms/`);
163
- expect(endpoint).toBe(`${ newUrl }/realms//protocol/openid-connect/auth`);
164
- });
165
+ expect(groupSearchCheckbox.isVisible()).toBe(true);
166
+ expect(wrapper.vm.model.groupSearchEnabled).toBe(false);
167
+ });
168
+
169
+ it('`groupSearchEnabled` updates when checkbox is clicked', async() => {
170
+ const groupSearchCheckbox = wrapper.getComponent('[data-testid="input-group-search"]');
171
+
172
+ await groupSearchCheckbox.find('[role="checkbox"]').trigger('click');
173
+
174
+ expect(groupSearchCheckbox.isVisible()).toBe(true);
175
+ expect(wrapper.vm.model.groupSearchEnabled).toBe(true);
176
+ });
177
+
178
+ it('changing URL should update issuer and auth-endpoint if Keycloak', async() => {
179
+ wrapper.vm.model.id = 'keycloakoidc';
180
+ const newUrl = 'whatever';
181
+
182
+ await wrapper.find(`[data-testid="oidc-url"]`).setValue(newUrl);
183
+ await wrapper.vm.$nextTick();
184
+
185
+ const issuerValue = (wrapper.find('[data-testid="oidc-issuer"]').element as HTMLInputElement).value;
186
+ const endpointValue = (wrapper.find('[data-testid="oidc-auth-endpoint"]').element as HTMLInputElement).value;
187
+
188
+ expect(issuerValue).toBe(`${ newUrl }/realms/`);
189
+ expect(endpointValue).toBe(`${ newUrl }/realms//protocol/openid-connect/auth`);
190
+ });
191
+
192
+ it('changing realm should update issuer and auth-endpoint if Keycloak', async() => {
193
+ const newRealm = 'newRealm';
194
+ const oldUrl = 'oldUrl';
195
+
196
+ wrapper.vm.model.id = 'keycloakoidc';
197
+ wrapper.vm.oidcUrls.url = oldUrl;
198
+
199
+ await wrapper.find(`[data-testid="oidc-realm"]`).setValue(newRealm);
200
+ await wrapper.vm.$nextTick();
165
201
 
166
- it('changing realm should update issuer and auth-endpoint if Keycloak', async() => {
167
- const newRealm = 'newRealm';
168
- const oldUrl = 'oldUrl';
202
+ const issuerValue = (wrapper.find('[data-testid="oidc-issuer"]').element as HTMLInputElement).value;
203
+ const endpointValue = (wrapper.find('[data-testid="oidc-auth-endpoint"]').element as HTMLInputElement).value;
169
204
 
170
- wrapper.vm.model.id = 'keycloakoidc';
171
- wrapper.vm.oidcUrls.url = oldUrl;
205
+ expect(issuerValue).toBe(`${ oldUrl }/realms/${ newRealm }`);
206
+ expect(endpointValue).toBe(`${ oldUrl }/realms/${ newRealm }/protocol/openid-connect/auth`);
207
+ });
172
208
 
173
- await wrapper.find(`[data-testid="oidc-realm"]`).setValue(newRealm);
174
- await wrapper.vm.$nextTick();
209
+ it('clear URL should clear issuer and auth-endpoint if Keycloak', async() => {
210
+ wrapper.vm.model.id = 'keycloakoidc';
211
+ const newUrl = 'whatever';
212
+ const urlInput = wrapper.find(`[data-testid="oidc-url"]`);
175
213
 
176
- const issuer = (wrapper.find('[data-testid="oidc-issuer"]').element as HTMLInputElement).value;
177
- const endpoint = (wrapper.find('[data-testid="oidc-auth-endpoint"]').element as HTMLInputElement).value;
214
+ await urlInput.setValue(newUrl);
215
+ await wrapper.vm.$nextTick();
216
+ await urlInput.setValue('');
217
+ await wrapper.vm.$nextTick();
218
+ const issuer = (wrapper.find('[data-testid="oidc-issuer"]').element as HTMLInputElement).value;
219
+ const endpoint = (wrapper.find('[data-testid="oidc-auth-endpoint"]').element as HTMLInputElement).value;
178
220
 
179
- expect(issuer).toBe(`${ oldUrl }/realms/${ newRealm }`);
180
- expect(endpoint).toBe(`${ oldUrl }/realms/${ newRealm }/protocol/openid-connect/auth`);
221
+ expect(issuer).toBe('');
222
+ expect(endpoint).toBe('');
223
+ });
181
224
  });
182
225
  });
@@ -388,6 +388,7 @@ export default {
388
388
  <label class="reply-url">{{ t('authConfig.azuread.reply.label') }} </label>
389
389
  <CopyToClipboardText
390
390
  :plain="true"
391
+ :aria-label="t('authConfig.azuread.reply.ariaLabel')"
391
392
  :text="replyUrl"
392
393
  />
393
394
  </InfoBox>
@@ -143,12 +143,14 @@ export default {
143
143
  <b>{{ t('authConfig.googleoauth.steps.1.body.2', {}, true) }}</b> {{ t('authConfig.googleoauth.steps.1.topPrivateDomain', {}, true) }} <CopyToClipboardText
144
144
  :plain="true"
145
145
  :text="tArgs.hostname"
146
+ :aria-label="t('authConfig.googleoauth.steps.1.ariaLabel.hostname')"
146
147
  />
147
148
  </li>
148
149
  <li>
149
150
  <b>{{ t('authConfig.googleoauth.steps.1.body.3', {}, true) }}</b> <CopyToClipboardText
150
151
  :plain="true"
151
152
  :text="serverUrl"
153
+ :aria-label="t('authConfig.googleoauth.steps.1.ariaLabel.serverUrl')"
152
154
  />
153
155
  </li>
154
156
  <li>{{ t('authConfig.googleoauth.steps.1.body.4', {}, true) }} </li>
@@ -170,12 +172,14 @@ export default {
170
172
  <b>{{ t('authConfig.googleoauth.steps.2.body.2', {}, true) }}</b> <CopyToClipboardText
171
173
  :plain="true"
172
174
  :text="serverUrl"
175
+ :aria-label="t('authConfig.googleoauth.steps.1.ariaLabel.serverUrl')"
173
176
  />
174
177
  </li>
175
178
  <li>
176
179
  <b>{{ t('authConfig.googleoauth.steps.2.body.3', {}, true) }}</b> <CopyToClipboardText
177
180
  :plain="true"
178
181
  :text="serverUrl+'/verify-auth'"
182
+ :aria-label="t('authConfig.googleoauth.steps.2.ariaLabel.serverUrlVerify')"
179
183
  />
180
184
  </li>
181
185
  <li>{{ t('authConfig.googleoauth.steps.2.body.4', {}, true) }} </li>
@@ -12,6 +12,7 @@ import ArrayList from '@shell/components/form/ArrayList';
12
12
  import { LabeledInput } from '@components/Form/LabeledInput';
13
13
  import { RadioGroup } from '@components/Form/Radio';
14
14
  import { Checkbox } from '@components/Form/Checkbox';
15
+ import { BASE_SCOPES } from '@shell/store/auth';
15
16
 
16
17
  export default {
17
18
  components: {
@@ -50,6 +51,7 @@ export default {
50
51
  tokenEndpoint: null,
51
52
  userInfoEndpoint: null,
52
53
  },
54
+ // TODO #13457: this is duplicated due wrong format
53
55
  oidcScope: []
54
56
  };
55
57
  },
@@ -76,9 +78,10 @@ export default {
76
78
  }
77
79
 
78
80
  const { clientId, clientSecret } = this.model;
79
- const isValidScope = this.model.id === 'keycloakoidc' || this.oidcScope?.includes('openid');
81
+ const isMissingAuthEndpoint = (this.requiresAuthEndpoint && !this.model.authEndpoint);
82
+ const isMissingScopes = !this.requiredScopes.every((scope) => this.oidcScope.includes(scope));
80
83
 
81
- if ( !isValidScope ) {
84
+ if (isMissingAuthEndpoint || isMissingScopes) {
82
85
  return false;
83
86
  }
84
87
 
@@ -91,6 +94,19 @@ export default {
91
94
 
92
95
  return !!(clientId && clientSecret && rancherUrl && issuer);
93
96
  }
97
+ },
98
+
99
+ requiresAuthEndpoint() {
100
+ return ['genericoidc', 'keycloakoidc'].includes(this.model.id);
101
+ },
102
+
103
+ /**
104
+ * TODO #13457: Refactor scopes to be an array of terms
105
+ * Return valid scopes
106
+ * The scopes for given auth provider (model.id) have format of ['scope1 scope2 scope3']
107
+ */
108
+ requiredScopes() {
109
+ return this.model.id ? (BASE_SCOPES[this.model.id] || []) ? (BASE_SCOPES[this.model.id] || [])[0].split(' ') : [] : [];
94
110
  }
95
111
  },
96
112
 
@@ -104,6 +120,7 @@ export default {
104
120
  },
105
121
 
106
122
  'model.enabled'(neu) {
123
+ // TODO #13457: Refactor scopes to be an array of terms
107
124
  // Cover case where oidc gets disabled and we return to the edit screen with a reset model
108
125
  if (!neu) {
109
126
  this.oidcUrls = {
@@ -114,8 +131,10 @@ export default {
114
131
  userInfoEndpoint: null,
115
132
  };
116
133
  this.customEndpoint.value = false;
134
+ // TODO #13457: Refactor scopes to be an array of terms
117
135
  this.oidcScope = this.model?.scope?.split(' ');
118
136
  } else {
137
+ // TODO #13457: Refactor scopes to be an array of terms
119
138
  this.oidcScope = this.model?.scope?.split(' ');
120
139
  }
121
140
  },
@@ -130,10 +149,16 @@ export default {
130
149
 
131
150
  methods: {
132
151
  updateEndpoints() {
152
+ const isKeycloak = this.model.id === 'keycloakoidc';
153
+
133
154
  if (!this.oidcUrls.url) {
155
+ this.model.issuer = '';
156
+ if (isKeycloak) {
157
+ this.model.authEndpoint = '';
158
+ }
159
+
134
160
  return;
135
161
  }
136
- const isKeycloak = this.model.id === 'keycloakoidc';
137
162
 
138
163
  const url = this.oidcUrls.url.replaceAll(' ', '');
139
164
  const realmsPath = 'realms';
@@ -201,6 +226,7 @@ export default {
201
226
 
202
227
  <h3>{{ t(`authConfig.oidc.${NAME}`) }}</h3>
203
228
 
229
+ <!-- Auth credentials -->
204
230
  <div class="row mb-20">
205
231
  <div class="col span-6">
206
232
  <LabeledInput
@@ -222,6 +248,7 @@ export default {
222
248
  </div>
223
249
  </div>
224
250
 
251
+ <!-- Key/Certificate -->
225
252
  <div class="row mb-20">
226
253
  <div class="col span-6">
227
254
  <LabeledInput
@@ -255,6 +282,7 @@ export default {
255
282
  </div>
256
283
  </div>
257
284
 
285
+ <!-- Allow group search -->
258
286
  <div class="row mb-20">
259
287
  <div class="col span-6">
260
288
  <Checkbox
@@ -267,6 +295,7 @@ export default {
267
295
  </div>
268
296
  </div>
269
297
 
298
+ <!-- Scopes -->
270
299
  <div class="row mb-20">
271
300
  <div class="col span-6">
272
301
  <ArrayList
@@ -280,6 +309,7 @@ export default {
280
309
  </div>
281
310
  </div>
282
311
 
312
+ <!-- Generated vs Specific Endpoints -->
283
313
  <div class="row mb-20">
284
314
  <div class="col span-6">
285
315
  <RadioGroup
@@ -297,6 +327,7 @@ export default {
297
327
  </div>
298
328
  </div>
299
329
 
330
+ <!-- Generated endpoints -->
300
331
  <div class="row mb-20">
301
332
  <div class="col span-6">
302
333
  <LabeledInput
@@ -320,6 +351,7 @@ export default {
320
351
  </div>
321
352
  </div>
322
353
 
354
+ <!-- Specific Endpoints -->
323
355
  <div class="row mb-20">
324
356
  <div class="col span-6">
325
357
  <LabeledInput
@@ -350,12 +382,13 @@ export default {
350
382
  :label="t(`authConfig.oidc.authEndpoint`)"
351
383
  :mode="mode"
352
384
  :disabled="!customEndpoint.value"
353
- :required="model.id === 'keycloakoidc'"
385
+ :required="requiresAuthEndpoint"
354
386
  data-testid="oidc-auth-endpoint"
355
387
  />
356
388
  </div>
357
389
  </div>
358
390
 
391
+ <!-- Advanced section -->
359
392
  <AdvancedSection :mode="mode">
360
393
  <div class="row mb-20">
361
394
  <div class="col span-6">
@@ -276,6 +276,7 @@ export default {
276
276
  <Loading v-if="$fetchState.pending" />
277
277
  <CruResource
278
278
  v-else
279
+ :done-params="$attrs['done-params'] /* Without this, changes to the validationPassed prop end up propagating all the way to the root of the app and force a re-render when the input becomes valid. I haven't found a reasonable explanation for why this happens. */"
279
280
  :mode="mode"
280
281
  :validation-passed="validationPassed"
281
282
  :selected-subtype="value._type"
@@ -659,6 +659,7 @@ export default {
659
659
  :initial-empty-row="false"
660
660
  :value-placeholder="t('fleet.gitRepo.paths.placeholder')"
661
661
  :add-label="t('fleet.gitRepo.paths.addLabel')"
662
+ :a11y-label="t('fleet.gitRepo.paths.ariaLabel')"
662
663
  :add-icon="'icon-plus'"
663
664
  :protip="t('fleet.gitRepo.paths.empty')"
664
665
  />
@@ -1,5 +1,6 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import Banzai from '@shell/edit/logging.banzaicloud.io.output/index.vue';
3
+ import { createStore } from 'vuex';
3
4
 
4
5
  const outputSchema = {
5
6
  id: 'logging.banzaicloud.io.output',
@@ -109,16 +110,26 @@ describe('view: logging.banzaicloud.io.output', () => {
109
110
  ['http://localhost:3100', []],
110
111
  ['not a proper URL', ['logging.loki.urlInvalid']],
111
112
  ])('should validate Loki URL on save', (url, expectation) => {
113
+ const store = createStore({
114
+ getters: {
115
+ namespaces: () => () => ({}),
116
+ currentStore: () => () => 'cluster',
117
+ 'cluster/schemaFor': () => jest.fn()
118
+ }
119
+ });
112
120
  const wrapper = mount(Banzai, {
113
121
  data: () => ({ selectedProvider: 'loki' }),
114
122
  props: {
115
123
  value: {
116
- save: jest.fn(),
117
- spec: { loki: { url } }
124
+ save: jest.fn(),
125
+ setAnnotation: jest.fn(),
126
+ spec: { loki: { url } },
127
+ metadata: {},
118
128
  }
119
129
  },
120
130
  global: {
121
- mocks: {
131
+ provide: { store },
132
+ mocks: {
122
133
  $fetchState: { pending: false },
123
134
  $store: {
124
135
  dispatch: jest.fn(),
@@ -149,16 +160,26 @@ describe('view: logging.banzaicloud.io.output', () => {
149
160
  });
150
161
 
151
162
  it('should load the default YAML data for output buffer config (from schema) in a CREATE scenario', async() => {
163
+ const store = createStore({
164
+ getters: {
165
+ namespaces: () => () => ({}),
166
+ currentStore: () => () => 'cluster',
167
+ 'cluster/schemaFor': () => jest.fn()
168
+ }
169
+ });
152
170
  const wrapper = mount(Banzai, {
153
171
  data: () => ({ selectedProvider: 'awsElasticsearch' }),
154
172
  props: {
155
173
  value: {
156
- save: jest.fn(),
157
- spec: {}
174
+ save: jest.fn(),
175
+ setAnnotation: jest.fn(),
176
+ spec: {},
177
+ metadata: {},
158
178
  }
159
179
  },
160
180
  global: {
161
- mocks: {
181
+ provide: { store },
182
+ mocks: {
162
183
  $fetchState: { pending: false },
163
184
  $store: {
164
185
  dispatch(arg: any) {
@@ -230,16 +251,26 @@ describe('view: logging.banzaicloud.io.output', () => {
230
251
  });
231
252
 
232
253
  it('should load current output buffer config in an EDIT scenario', async() => {
254
+ const store = createStore({
255
+ getters: {
256
+ namespaces: () => () => ({}),
257
+ currentStore: () => () => 'cluster',
258
+ 'cluster/schemaFor': () => jest.fn()
259
+ }
260
+ });
233
261
  const wrapper = mount(Banzai, {
234
262
  data: () => ({ selectedProvider: 'awsElasticsearch' }),
235
263
  props: {
236
264
  value: {
237
- save: jest.fn(),
238
- spec: { awsElasticsearch: { buffer: '#chunk_limit_records: int' } }
265
+ save: jest.fn(),
266
+ setAnnotation: jest.fn(),
267
+ spec: { awsElasticsearch: { buffer: '#chunk_limit_records: int' } },
268
+ metadata: {}
239
269
  }
240
270
  },
241
271
  global: {
242
- mocks: {
272
+ provide: { store },
273
+ mocks: {
243
274
  $fetchState: { pending: false },
244
275
  $store: {
245
276
  dispatch(arg: any) {
@@ -5,6 +5,7 @@ import { get, set, remove } from '@shell/utils/object';
5
5
 
6
6
  export default {
7
7
  components: { LabeledSelect },
8
+ emits: ['update:value'],
8
9
  props: {
9
10
  value: {
10
11
  type: Object,
@@ -37,13 +38,16 @@ export default {
37
38
  },
38
39
  methods: {
39
40
  update(e) {
40
- if (!e || e.label === this.t('generic.none')) {
41
+ if (!e || e === '' || e.label === this.t('generic.none')) {
41
42
  remove(this.value, 'spec.ingressClassName');
42
43
  this.ingressClassName = '';
44
+ this.$emit('update:value', this.value);
43
45
  } else {
44
46
  // when a user manually types an ingress class name, the event emitted has a 'label' but no 'value'
45
- this.ingressClassName = e.value ? e.value : e.label;
47
+ this.ingressClassName = e.label || e;
46
48
  set(this.value, 'spec.ingressClassName', this.ingressClassName);
49
+
50
+ this.$emit('update:value', this.value);
47
51
  }
48
52
  }
49
53
  }
@@ -59,7 +63,7 @@ export default {
59
63
  :label="t('ingress.ingressClass.label')"
60
64
  :options="ingressClassOptions"
61
65
  option-label="label"
62
- @selecting="update"
66
+ @update:value="update"
63
67
  />
64
68
  </div>
65
69
  </template>