@rancher/shell 3.0.9-rc.5 → 3.0.9-rc.6

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 (142) hide show
  1. package/assets/images/providers/oci-open-containers.svg +22 -0
  2. package/assets/images/providers/traefik.png +0 -0
  3. package/assets/styles/themes/_dark.scss +2 -0
  4. package/assets/styles/themes/_light.scss +2 -0
  5. package/assets/styles/themes/_modern.scss +6 -0
  6. package/assets/translations/en-us.yaml +129 -25
  7. package/components/CruResource.vue +3 -1
  8. package/components/ExplorerProjectsNamespaces.vue +12 -12
  9. package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +109 -0
  10. package/components/Resource/Detail/Card/StatusCard/index.vue +21 -4
  11. package/components/Resource/Detail/Metadata/IdentifyingInformation/__tests__/identifying-fields.test.ts +19 -2
  12. package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +19 -11
  13. package/components/Resource/Detail/ResourcePopover/__tests__/index.test.ts +12 -0
  14. package/components/Resource/Detail/ResourcePopover/index.vue +2 -0
  15. package/components/Resource/Detail/ResourceRow.vue +2 -2
  16. package/components/ResourceList/index.vue +7 -4
  17. package/components/Window/ContainerLogs.vue +48 -37
  18. package/components/fleet/FleetClusterTargets/TargetsList.vue +2 -2
  19. package/components/fleet/FleetClusterTargets/index.vue +6 -1
  20. package/components/fleet/GitRepoAdvancedTab.vue +333 -0
  21. package/components/fleet/GitRepoMetadataTab.vue +43 -0
  22. package/components/fleet/GitRepoRepositoryTab.vue +101 -0
  23. package/components/fleet/GitRepoTargetTab.vue +77 -0
  24. package/components/fleet/HelmOpAdvancedTab.vue +247 -0
  25. package/components/fleet/HelmOpChartTab.vue +158 -0
  26. package/components/fleet/HelmOpMetadataTab.vue +46 -0
  27. package/components/fleet/HelmOpTargetTab.vue +84 -0
  28. package/components/fleet/HelmOpValuesTab.vue +147 -0
  29. package/components/fleet/__tests__/FleetClusterTargets.test.ts +119 -70
  30. package/components/form/NodeScheduling.vue +81 -7
  31. package/components/form/PodAffinity.vue +1 -36
  32. package/components/form/ResourceLabeledSelect.vue +8 -4
  33. package/components/form/ResourceQuota/Namespace.vue +30 -9
  34. package/components/form/ResourceQuota/NamespaceRow.vue +25 -7
  35. package/components/form/ResourceQuota/Project.vue +140 -82
  36. package/components/form/ResourceQuota/ResourceQuotaEntry.vue +145 -0
  37. package/components/form/ResourceQuota/__tests__/Namespace.test.ts +307 -0
  38. package/components/form/ResourceQuota/__tests__/NamespaceRow.test.ts +281 -0
  39. package/components/form/ResourceQuota/__tests__/Project.test.ts +274 -27
  40. package/components/form/ResourceQuota/__tests__/ResourceQuotaEntry.test.ts +215 -0
  41. package/components/form/SchedulingCustomization.vue +14 -6
  42. package/components/form/SelectOrCreateAuthSecret.vue +107 -18
  43. package/components/form/__tests__/NodeScheduling.test.ts +12 -9
  44. package/components/form/__tests__/PodAffinity.test.ts +21 -2
  45. package/components/form/__tests__/SchedulingCustomization.test.ts +240 -0
  46. package/components/formatter/ClusterLink.vue +8 -0
  47. package/components/formatter/SecretOrigin.vue +79 -0
  48. package/config/labels-annotations.js +7 -6
  49. package/config/pagination-table-headers.js +6 -4
  50. package/config/product/explorer.js +1 -11
  51. package/config/query-params.js +3 -0
  52. package/config/settings.ts +15 -2
  53. package/config/table-headers.js +21 -17
  54. package/config/types.js +23 -8
  55. package/detail/workload/index.vue +11 -16
  56. package/dialog/DeactivateDriverDialog.vue +1 -1
  57. package/dialog/Ipv6NetworkingDialog.vue +156 -0
  58. package/dialog/ScalePoolDownDialog.vue +2 -2
  59. package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +1 -1
  60. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +1 -0
  61. package/edit/__tests__/management.cattle.io.project.test.js +56 -128
  62. package/edit/auth/oidc.vue +1 -1
  63. package/edit/catalog.cattle.io.clusterrepo.vue +155 -25
  64. package/edit/fleet.cattle.io.gitrepo.vue +153 -283
  65. package/edit/fleet.cattle.io.helmop.vue +190 -332
  66. package/edit/management.cattle.io.project.vue +5 -42
  67. package/edit/management.cattle.io.setting.vue +6 -0
  68. package/edit/provisioning.cattle.io.cluster/__tests__/Basics.test.ts +55 -24
  69. package/edit/provisioning.cattle.io.cluster/__tests__/Networking.test.ts +1 -103
  70. package/edit/provisioning.cattle.io.cluster/__tests__/index.test.ts +13 -1
  71. package/edit/provisioning.cattle.io.cluster/__tests__/rke2-fleet-cluster-agent.test.ts +283 -0
  72. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +65 -49
  73. package/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue +112 -0
  74. package/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue +158 -0
  75. package/edit/provisioning.cattle.io.cluster/rke2.vue +171 -72
  76. package/edit/provisioning.cattle.io.cluster/shared.ts +36 -1
  77. package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +2 -1
  78. package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +55 -7
  79. package/edit/provisioning.cattle.io.cluster/tabs/Ingress.vue +319 -0
  80. package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +2 -1
  81. package/edit/provisioning.cattle.io.cluster/tabs/etcd/__tests__/S3Config.test.ts +13 -1
  82. package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +10 -44
  83. package/edit/secret/index.vue +1 -1
  84. package/edit/token.vue +68 -29
  85. package/edit/workload/__tests__/index.test.ts +2 -37
  86. package/edit/workload/index.vue +6 -2
  87. package/edit/workload/mixins/workload.js +0 -32
  88. package/list/__tests__/management.cattle.io.setting.test.ts +198 -0
  89. package/list/management.cattle.io.setting.vue +13 -0
  90. package/list/provisioning.cattle.io.cluster.vue +50 -1
  91. package/list/secret.vue +4 -9
  92. package/list/service.vue +6 -8
  93. package/machine-config/amazonec2.vue +11 -4
  94. package/machine-config/components/EC2Networking.vue +46 -30
  95. package/machine-config/components/__tests__/EC2Networking.test.ts +7 -7
  96. package/machine-config/components/__tests__/utils/vpcSubnetMockData.js +0 -9
  97. package/machine-config/digitalocean.vue +3 -3
  98. package/models/__tests__/namespace.test.ts +11 -0
  99. package/models/__tests__/provisioning.cattle.io.cluster.test.ts +96 -0
  100. package/models/__tests__/workload.test.ts +42 -1
  101. package/models/catalog.cattle.io.clusterrepo.js +30 -4
  102. package/models/ext.cattle.io.token.js +48 -0
  103. package/models/kontainerdriver.js +2 -2
  104. package/models/namespace.js +7 -1
  105. package/models/nodedriver.js +2 -2
  106. package/models/provisioning.cattle.io.cluster.js +28 -7
  107. package/models/secret.js +0 -17
  108. package/models/service.js +44 -1
  109. package/models/token.js +4 -0
  110. package/models/workload.js +12 -6
  111. package/package.json +1 -1
  112. package/pages/account/index.vue +96 -67
  113. package/pages/auth/setup.vue +5 -14
  114. package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +4 -1
  115. package/pages/c/_cluster/apps/charts/index.vue +93 -4
  116. package/pages/c/_cluster/apps/charts/install.vue +317 -42
  117. package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -4
  118. package/pages/c/_cluster/settings/index.vue +3 -1
  119. package/plugins/dashboard-store/__tests__/getters.test.ts +108 -0
  120. package/plugins/dashboard-store/__tests__/resource-class.test.ts +27 -0
  121. package/plugins/dashboard-store/actions.js +3 -8
  122. package/plugins/dashboard-store/getters.js +7 -5
  123. package/plugins/dashboard-store/mutations.js +4 -1
  124. package/plugins/dashboard-store/resource-class.js +3 -3
  125. package/plugins/steve/__tests__/steve-class.test.ts +102 -141
  126. package/plugins/steve/steve-class.js +12 -3
  127. package/plugins/steve/steve-pagination-utils.ts +6 -2
  128. package/rancher-components/RcIcon/types.ts +2 -0
  129. package/rancher-components/RcItemCard/RcItemCard.vue +64 -19
  130. package/store/prefs.js +3 -0
  131. package/types/aws-sdk.d.ts +121 -0
  132. package/types/resources/node.ts +15 -0
  133. package/types/shell/index.d.ts +536 -506
  134. package/types/store/pagination.types.ts +5 -5
  135. package/utils/__tests__/array.test.ts +1 -29
  136. package/utils/__tests__/cluster-agent-configuration.test.ts +203 -0
  137. package/utils/array.ts +0 -11
  138. package/utils/aws.ts +21 -0
  139. package/utils/cluster.js +22 -2
  140. package/utils/selector-typed.ts +1 -1
  141. package/components/__tests__/ProjectRow.test.ts +0 -206
  142. package/components/form/ResourceQuota/ProjectRow.vue +0 -277
@@ -0,0 +1,215 @@
1
+ import { shallowMount } from '@vue/test-utils';
2
+ import { createStore } from 'vuex';
3
+ import ResourceQuotaEntry from '@shell/components/form/ResourceQuota/ResourceQuotaEntry.vue';
4
+ import { LabeledInput } from '@components/Form/LabeledInput';
5
+ import { RcButton } from '@components/RcButton';
6
+ import { TYPES } from '@shell/components/form/ResourceQuota/shared';
7
+
8
+ describe('component: ResourceQuotaEntry', () => {
9
+ const store = createStore({});
10
+
11
+ const mockType = {
12
+ value: 'configMaps',
13
+ inputExponent: 0,
14
+ baseUnit: '',
15
+ placeholder: 'Enter count',
16
+ increment: 1,
17
+ };
18
+
19
+ const podsType = {
20
+ value: 'pods',
21
+ inputExponent: 0,
22
+ baseUnit: '',
23
+ placeholder: 'Enter count',
24
+ increment: 1,
25
+ };
26
+
27
+ const limitsCpuType = {
28
+ value: 'limitsCpu',
29
+ inputExponent: -1,
30
+ baseUnit: '',
31
+ placeholder: 'Enter CPU',
32
+ increment: 1,
33
+ };
34
+
35
+ const extendedType = {
36
+ value: TYPES.EXTENDED,
37
+ inputExponent: 0,
38
+ baseUnit: '',
39
+ placeholder: 'Enter value',
40
+ increment: 1,
41
+ };
42
+
43
+ const allTypes = [mockType, podsType, limitsCpuType, extendedType];
44
+
45
+ const defaultProps = {
46
+ id: '1',
47
+ mode: 'edit',
48
+ types: allTypes,
49
+ resourceType: 'configMaps',
50
+ resourceIdentifier: 'configMaps',
51
+ projectLimit: '20',
52
+ namespaceDefaultLimit: '10',
53
+ };
54
+
55
+ const createWrapper = (propsOverrides: Record<string, unknown> = {}) => {
56
+ return shallowMount(ResourceQuotaEntry, {
57
+ props: { ...defaultProps, ...propsOverrides },
58
+ global: { provide: { store } },
59
+ });
60
+ };
61
+
62
+ describe('rendering', () => {
63
+ it('should render all input fields with their data-testid attributes', () => {
64
+ const wrapper = createWrapper();
65
+
66
+ expect(wrapper.find('[data-testid="projectrow-type-input"]').exists()).toBe(true);
67
+ expect(wrapper.find('[data-testid="projectrow-custom-type-input"]').exists()).toBe(true);
68
+ expect(wrapper.find('[data-testid="projectrow-project-quota-input"]').exists()).toBe(true);
69
+ expect(wrapper.find('[data-testid="projectrow-namespace-quota-input"]').exists()).toBe(true);
70
+ });
71
+ });
72
+
73
+ describe('computed: typeOption', () => {
74
+ it('should return the matching type option for a given resourceType', () => {
75
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
76
+
77
+ expect((wrapper.vm as any).typeOption).toStrictEqual(mockType);
78
+ });
79
+
80
+ it('should return the extended type option when resourceType is TYPES.EXTENDED', () => {
81
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
82
+
83
+ expect((wrapper.vm as any).typeOption).toStrictEqual(extendedType);
84
+ });
85
+ });
86
+
87
+ describe('computed: isCustom', () => {
88
+ it('should return true when resourceType equals TYPES.EXTENDED', () => {
89
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
90
+
91
+ expect((wrapper.vm as any).isCustom).toBe(true);
92
+ });
93
+
94
+ it.each([
95
+ ['configMaps'],
96
+ ['pods'],
97
+ ['limitsCpu'],
98
+ ])('should return false when resourceType is "%s"', (resourceType) => {
99
+ const wrapper = createWrapper({ resourceType });
100
+
101
+ expect((wrapper.vm as any).isCustom).toBe(false);
102
+ });
103
+ });
104
+
105
+ describe('computed: customTypeRules', () => {
106
+ it('should return an empty array when resourceType is a standard type', () => {
107
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
108
+
109
+ expect((wrapper.vm as any).customTypeRules).toStrictEqual([]);
110
+ });
111
+
112
+ it('should return one validation rule when resourceType is TYPES.EXTENDED', () => {
113
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
114
+
115
+ expect((wrapper.vm as any).customTypeRules).toHaveLength(1);
116
+ });
117
+
118
+ describe('validation rule behavior for custom type', () => {
119
+ it.each([
120
+ ['', 'resourceQuota.errors.customTypeRequired'],
121
+ [null, 'resourceQuota.errors.customTypeRequired'],
122
+ [undefined, 'resourceQuota.errors.customTypeRequired'],
123
+ ])('should return an error message for falsy value "%s"', (value, expectedError) => {
124
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
125
+ const [rule] = (wrapper.vm as any).customTypeRules;
126
+
127
+ expect(rule(value)).toBe(expectedError);
128
+ });
129
+
130
+ it.each([
131
+ ['my-resource'],
132
+ ['cpu'],
133
+ ['custom.resource/name'],
134
+ ])('should return undefined for non-empty value "%s"', (value) => {
135
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
136
+ const [rule] = (wrapper.vm as any).customTypeRules;
137
+
138
+ expect(rule(value)).toBeUndefined();
139
+ });
140
+ });
141
+ });
142
+
143
+ describe('method: remove', () => {
144
+ it('should emit remove event with the resource quota id', () => {
145
+ const id = 'test-remove-id';
146
+ const wrapper = createWrapper({ id });
147
+
148
+ (wrapper.vm as any).remove(id);
149
+
150
+ expect(wrapper.emitted('remove')).toBeTruthy();
151
+ expect(wrapper.emitted('remove')![0]).toStrictEqual([id]);
152
+ });
153
+
154
+ it('should emit remove when the Remove button is clicked', async() => {
155
+ const id = 'click-test-id';
156
+ const wrapper = createWrapper({ id });
157
+
158
+ await wrapper.findComponent(RcButton as any).trigger('click');
159
+
160
+ expect(wrapper.emitted('remove')).toBeTruthy();
161
+ expect(wrapper.emitted('remove')![0]).toStrictEqual([id]);
162
+ });
163
+ });
164
+
165
+ describe('method: updateResourceIdentifier', () => {
166
+ it('should emit update:resourceIdentifier with the new type for standard types', () => {
167
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
168
+
169
+ (wrapper.vm as any).updateResourceIdentifier('pods');
170
+
171
+ expect(wrapper.emitted('update:resourceIdentifier')).toBeTruthy();
172
+ expect(wrapper.emitted('update:resourceIdentifier')![0]).toStrictEqual(['pods']);
173
+ });
174
+
175
+ it('should not emit update:resourceIdentifier when called with TYPES.EXTENDED', () => {
176
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
177
+
178
+ (wrapper.vm as any).updateResourceIdentifier(TYPES.EXTENDED);
179
+
180
+ expect(wrapper.emitted('update:resourceIdentifier')).toBeFalsy();
181
+ });
182
+ });
183
+
184
+ describe('template: LabeledInput disabled state', () => {
185
+ it('should have the identifier input disabled when resourceType is a standard type', () => {
186
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
187
+ const input = wrapper.findComponent(LabeledInput as any);
188
+
189
+ expect(input.props('disabled')).toBe(true);
190
+ });
191
+
192
+ it('should not have the identifier input disabled when resourceType is TYPES.EXTENDED', () => {
193
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
194
+ const input = wrapper.findComponent(LabeledInput as any);
195
+
196
+ expect(input.props('disabled')).toBe(false);
197
+ });
198
+ });
199
+
200
+ describe('template: LabeledInput required state', () => {
201
+ it('should have the identifier input required when resourceType is TYPES.EXTENDED', () => {
202
+ const wrapper = createWrapper({ resourceType: TYPES.EXTENDED });
203
+ const input = wrapper.findComponent(LabeledInput as any);
204
+
205
+ expect(input.props('required')).toBe(true);
206
+ });
207
+
208
+ it('should not have the identifier input required when resourceType is a standard type', () => {
209
+ const wrapper = createWrapper({ resourceType: 'configMaps' });
210
+ const input = wrapper.findComponent(LabeledInput as any);
211
+
212
+ expect(input.props('required')).toBe(false);
213
+ });
214
+ });
215
+ });
@@ -7,6 +7,10 @@ export default {
7
7
  components: { Checkbox, Banner },
8
8
  emits: ['scheduling-customization-changed'],
9
9
  props: {
10
+ type: {
11
+ type: String,
12
+ required: true,
13
+ },
10
14
  value: {
11
15
  type: Object,
12
16
  default: () => {},
@@ -26,6 +30,10 @@ export default {
26
30
  defaultPDB: {
27
31
  type: Object,
28
32
  default: () => {},
33
+ },
34
+ checkboxWithOnlyAgentName: {
35
+ type: Boolean,
36
+ default: false
29
37
  }
30
38
  },
31
39
  data() {
@@ -59,10 +67,10 @@ export default {
59
67
  <Checkbox
60
68
  :value="enabled"
61
69
  :mode="mode"
62
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.label"
63
- descriptionKey="cluster.agentConfig.subGroups.schedulingCustomization.description"
70
+ :label="checkboxWithOnlyAgentName ? t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`) : t('cluster.agentConfig.subGroups.agentsScheduling.label', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
71
+ :description="checkboxWithOnlyAgentName ? '' :t('cluster.agentConfig.subGroups.agentsScheduling.description', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
64
72
  data-testid="scheduling-customization-checkbox"
65
- @update:value="$emit('scheduling-customization-changed', $event)"
73
+ @update:value="$emit('scheduling-customization-changed', { event: $event, agentType: type })"
66
74
  >
67
75
  <template
68
76
  v-if="feature && isEdit && settingMissmatch"
@@ -71,13 +79,13 @@ export default {
71
79
  <Banner
72
80
  class="mt-10 mb-10"
73
81
  color="info"
74
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.banner"
82
+ :label="t('cluster.agentConfig.subGroups.agentsScheduling.banner', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
75
83
  />
76
84
  <Checkbox
77
85
  :value="applyGlobal"
78
86
  :mode="mode"
79
- label-key="cluster.agentConfig.subGroups.schedulingCustomization.innerCheckbox"
80
- @update:value="$emit('scheduling-customization-changed', feature)"
87
+ :label="t('cluster.agentConfig.subGroups.agentsScheduling.innerCheckbox', { agent: t(`cluster.agentConfig.subGroups.agentsScheduling.${type}`)})"
88
+ @update:value="$emit('scheduling-customization-changed', { event: $event, agentType: type })"
81
89
  />
82
90
  </template>
83
91
  </Checkbox>
@@ -71,6 +71,11 @@ export default {
71
71
  default: 'auth-',
72
72
  },
73
73
 
74
+ clientGeneratedName: {
75
+ type: String,
76
+ default: null,
77
+ },
78
+
74
79
  allowNone: {
75
80
  type: Boolean,
76
81
  default: true,
@@ -150,6 +155,42 @@ export default {
150
155
  default: false,
151
156
  },
152
157
 
158
+ /**
159
+ * Used specifically to fix the HTTP BASIC auth to generate specific authentication
160
+ * This is used to clear up the SELECT to make sure it only has HTTP BASIC with some special conditions
161
+ */
162
+ fixedHttpBasicAuth: {
163
+ type: Boolean,
164
+ default: false,
165
+ },
166
+
167
+ /** Used together with fixedHttpBasicAuth
168
+ * It will filter all the cases to use this specific label at the start.
169
+ */
170
+ filterBasicAuth: {
171
+ type: String,
172
+ default: '',
173
+ },
174
+
175
+ /**
176
+ * This works similar to other allow* but this makes specific to create as ImagePullSecret since it is specific for one page that only uses it
177
+ * To avoid using it in other places.
178
+ */
179
+ fixedImagePullSecret: {
180
+ type: Boolean,
181
+ default: false,
182
+ },
183
+ /**
184
+ * Whenever the fixedImagePullSecret is setup the dockerJsonUrlConfig needs to be passed that will be used to create the DockerJsonConfig file
185
+ */
186
+ imagePullSecretDockerJsonUrlConfig: {
187
+ type: String,
188
+ default: '',
189
+ },
190
+
191
+ /**
192
+ * Specific property to change labels if it is Github.com repository
193
+ */
153
194
  isGithubDotComRepository: {
154
195
  type: Boolean,
155
196
  default: false,
@@ -157,7 +198,7 @@ export default {
157
198
  },
158
199
 
159
200
  async fetch() {
160
- if ( (this.allowSsh || this.allowBasic || this.allowRke) && this.$store.getters[`${ this.inStore }/schemaFor`](SECRET) ) {
201
+ if ( (this.allowSsh || this.allowBasic || this.allowRke || this.fixedImagePullSecret) && this.$store.getters[`${ this.inStore }/schemaFor`](SECRET) ) {
161
202
  if (this.$store.getters[`${ this.inStore }/paginationEnabled`](SECRET)) {
162
203
  // Filter results via api (because we shouldn't be fetching them all...)
163
204
  this.filteredSecrets = await this.filterSecretsByApi();
@@ -200,6 +241,8 @@ export default {
200
241
 
201
242
  selected: null,
202
243
 
244
+ previousValue: null,
245
+
203
246
  filterByNamespace: this.namespace && this.limitToNamespace,
204
247
 
205
248
  publicKey: '',
@@ -207,10 +250,11 @@ export default {
207
250
  sshKnownHosts: '',
208
251
  uniqueId: new Date().getTime(), // Allows form state to be individually tracked if the form is in a list
209
252
 
210
- SSH: AUTH_TYPE._SSH,
211
- BASIC: AUTH_TYPE._BASIC,
212
- S3: AUTH_TYPE._S3,
213
- RKE: AUTH_TYPE._RKE,
253
+ SSH: AUTH_TYPE._SSH,
254
+ BASIC: AUTH_TYPE._BASIC,
255
+ IMAGE_PULL_SECRET: AUTH_TYPE._IMAGE_PULL_SECRET,
256
+ S3: AUTH_TYPE._S3,
257
+ RKE: AUTH_TYPE._RKE,
214
258
  };
215
259
  },
216
260
 
@@ -218,6 +262,12 @@ export default {
218
262
  secretTypes() {
219
263
  const types = [];
220
264
 
265
+ if ( this.fixedImagePullSecret ) {
266
+ types.push(SECRET_TYPES.DOCKER_JSON);
267
+
268
+ return types;
269
+ }
270
+
221
271
  if ( this.allowSsh ) {
222
272
  types.push(SECRET_TYPES.SSH);
223
273
  }
@@ -325,7 +375,7 @@ export default {
325
375
  });
326
376
  }
327
377
 
328
- if (this.allowSsh || this.allowS3 || this.allowBasic || this.allowRke) {
378
+ if (this.allowSsh || this.allowS3 || this.allowBasic || this.allowRke || this.fixedImagePullSecret) {
329
379
  out.unshift({
330
380
  label: 'divider',
331
381
  disabled: true,
@@ -366,6 +416,18 @@ export default {
366
416
  });
367
417
  }
368
418
 
419
+ if ( this.fixedImagePullSecret ) {
420
+ out.unshift({
421
+ label: this.t('selectOrCreateAuthSecret.createImagePullSecret'),
422
+ value: AUTH_TYPE._IMAGE_PULL_SECRET,
423
+ kind: 'highlighted'
424
+ });
425
+ }
426
+
427
+ if (this.fixedHttpBasicAuth) {
428
+ out = out.filter((o) => o.label.search(this.filterBasicAuth) === 0 || ['title', 'divider'].includes(o.kind) || o.value === AUTH_TYPE._BASIC);
429
+ }
430
+
369
431
  return out;
370
432
  },
371
433
 
@@ -374,7 +436,7 @@ export default {
374
436
  return '';
375
437
  }
376
438
 
377
- if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._RKE || this.selected === AUTH_TYPE._S3 ) {
439
+ if ( this.selected === AUTH_TYPE._SSH || this.selected === AUTH_TYPE._BASIC || this.selected === AUTH_TYPE._RKE || this.selected === AUTH_TYPE._S3 || this.selected === AUTH_TYPE._IMAGE_PULL_SECRET ) {
378
440
  return 'col span-4';
379
441
  }
380
442
 
@@ -395,6 +457,7 @@ export default {
395
457
  publicKey: 'updateKeyVal',
396
458
  privateKey: 'updateKeyVal',
397
459
  sshKnownHosts: 'updateKeyVal',
460
+ preSelect: 'updateSelectedFromValue',
398
461
  value: 'updateSelectedFromValue',
399
462
 
400
463
  async namespace(ns) {
@@ -478,7 +541,7 @@ export default {
478
541
  },
479
542
 
480
543
  updateKeyVal() {
481
- if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) ) {
544
+ if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected) ) {
482
545
  this.privateKey = '';
483
546
  this.publicKey = '';
484
547
  this.sshKnownHosts = '';
@@ -498,9 +561,9 @@ export default {
498
561
  },
499
562
 
500
563
  update() {
501
- if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._NONE].includes(this.selected))) {
564
+ if ( (!this.selected || [AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._NONE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected))) {
502
565
  this.$emit('update:value', null);
503
- } else if ( this.selected.includes(':') ) {
566
+ } else if ( this.selected.includes(':')) {
504
567
  // Cloud creds
505
568
  this.$emit('update:value', this.selected);
506
569
  } else {
@@ -522,7 +585,7 @@ export default {
522
585
  },
523
586
 
524
587
  async doCreate() {
525
- if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE].includes(this.selected) || this.delegateCreateToParent ) {
588
+ if ( ![AUTH_TYPE._SSH, AUTH_TYPE._BASIC, AUTH_TYPE._S3, AUTH_TYPE._RKE, AUTH_TYPE._IMAGE_PULL_SECRET].includes(this.selected) || this.delegateCreateToParent ) {
526
589
  return;
527
590
  }
528
591
 
@@ -537,12 +600,17 @@ export default {
537
600
  },
538
601
  });
539
602
  } else {
603
+ const metadata = { namespace: this.namespace };
604
+
605
+ if (this.clientGeneratedName) {
606
+ metadata.name = this.clientGeneratedName;
607
+ } else {
608
+ metadata.generateName = this.generateName;
609
+ }
610
+
540
611
  secret = await this.$store.dispatch(`${ this.inStore }/create`, {
541
- type: SECRET,
542
- metadata: {
543
- namespace: this.namespace,
544
- generateName: this.generateName
545
- },
612
+ type: SECRET,
613
+ metadata,
546
614
  });
547
615
 
548
616
  let type, publicField, privateField;
@@ -558,6 +626,11 @@ export default {
558
626
  publicField = 'username';
559
627
  privateField = 'password';
560
628
  break;
629
+ case AUTH_TYPE._IMAGE_PULL_SECRET:
630
+ type = SECRET_TYPES.DOCKER_JSON;
631
+ publicField = 'username';
632
+ privateField = 'password';
633
+ break;
561
634
  case AUTH_TYPE._RKE:
562
635
  type = SECRET_TYPES.RKE_AUTH_CONFIG;
563
636
  // Set the 'auth' key to be the base64 of the username and password concatenated with a ':' character
@@ -581,6 +654,22 @@ export default {
581
654
  if ((this.selected === AUTH_TYPE._SSH) && this.showSshKnownHosts) {
582
655
  secret.data.known_hosts = base64Encode(this.sshKnownHosts || '');
583
656
  }
657
+
658
+ if (this.selected === AUTH_TYPE._IMAGE_PULL_SECRET && this.imagePullSecretDockerJsonUrlConfig) {
659
+ const registryHost = this.imagePullSecretDockerJsonUrlConfig ? new URL(this.imagePullSecretDockerJsonUrlConfig).host : '';
660
+
661
+ const config = {
662
+ auths: {
663
+ [registryHost]: {
664
+ [publicField]: this.publicKey,
665
+ [privateField]: this.privateKey,
666
+ }
667
+ }
668
+ };
669
+ const json = JSON.stringify(config);
670
+
671
+ secret.setData('.dockerconfigjson', json);
672
+ }
584
673
  }
585
674
  }
586
675
 
@@ -609,7 +698,7 @@ export default {
609
698
  v-model:value="selected"
610
699
  data-testid="auth-secret-select"
611
700
  :mode="mode"
612
- :label-key="labelKey"
701
+ :label-key="fixedImagePullSecret ? 'selectOrCreateAuthSecret.imagePullSecret' : labelKey"
613
702
  :loading="$fetchState.pending"
614
703
  :options="options"
615
704
  :selectable="option => !option.disabled"
@@ -645,7 +734,7 @@ export default {
645
734
  />
646
735
  </div>
647
736
  </template>
648
- <template v-else-if="selected === BASIC || selected === RKE">
737
+ <template v-else-if="selected === BASIC || selected === RKE || selected === IMAGE_PULL_SECRET">
649
738
  <Banner
650
739
  v-if="selected === RKE"
651
740
  color="info"
@@ -6,12 +6,17 @@ const requiredSetup = () => {
6
6
  return {
7
7
  global: {
8
8
  mocks: {
9
- $store: {
9
+ $fetchState: { pending: false },
10
+ $store: {
10
11
  getters: {
11
- currentProduct: { inStore: 'cluster' },
12
- 'i18n/t': (text: string) => text,
13
- t: (text: string) => text,
14
- }
12
+ currentStore: () => 'cluster',
13
+ currentProduct: { inStore: 'cluster' },
14
+ 'i18n/t': (text: string) => text,
15
+ t: (text: string) => text,
16
+ 'cluster/paginationEnabled': () => false,
17
+ 'cluster/all': () => ['node-0', 'node-1'],
18
+ },
19
+ dispatch: jest.fn(),
15
20
  }
16
21
  },
17
22
  }
@@ -21,8 +26,6 @@ const requiredSetup = () => {
21
26
  describe('component: NodeScheduling', () => {
22
27
  const value = { nodeName: 'node-1' };
23
28
 
24
- const nodes = ['node-0', 'node-1'];
25
-
26
29
  it.each([
27
30
  _VIEW,
28
31
  _CREATE,
@@ -32,13 +35,13 @@ describe('component: NodeScheduling', () => {
32
35
  NodeScheduling,
33
36
  {
34
37
  props: {
35
- mode, loading: false, value, nodes
38
+ mode, loading: false, value
36
39
  },
37
40
  ...requiredSetup(),
38
41
  }
39
42
  );
40
43
 
41
44
  expect(wrapper.find('[data-testid="node-scheduling-selectNode"]').exists()).toBeTruthy();
42
- expect(wrapper.find('[data-testid="node-scheduling-nodeSelector"]').element.textContent).toContain(value.nodeName);
45
+ expect(wrapper.find('[data-testid="node-scheduling-nodeSelector"] .vs__selected').text()).toBe(value.nodeName);
43
46
  });
44
47
  });
@@ -3,6 +3,23 @@ import { mount } from '@vue/test-utils';
3
3
  import PodAffinity from '@shell/components/form/PodAffinity.vue';
4
4
  import { _CREATE } from '@shell/config/query-params';
5
5
 
6
+ const requiredSetup = () => {
7
+ return {
8
+ global: {
9
+ mocks: {
10
+ $store: {
11
+ getters: {
12
+ currentStore: () => 'cluster',
13
+ 'i18n/t': (text: string) => text,
14
+ 'cluster/schemaFor': jest.fn(),
15
+ 'cluster/canList': jest.fn(),
16
+ }
17
+ }
18
+ }
19
+ }
20
+ };
21
+ };
22
+
6
23
  describe('component: PodAffinity', () => {
7
24
  it('should display the weight input when the priority is preferred', () => {
8
25
  const podAffinity = {
@@ -15,7 +32,8 @@ describe('component: PodAffinity', () => {
15
32
  const wrapper = mount(PodAffinity, {
16
33
  props: {
17
34
  mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
18
- }
35
+ },
36
+ ...requiredSetup()
19
37
  });
20
38
 
21
39
  expect(wrapper.find('[data-testid="pod-affinity-weight-index0"]').exists()).toBeTruthy();
@@ -33,7 +51,8 @@ describe('component: PodAffinity', () => {
33
51
  const wrapper = mount(PodAffinity, {
34
52
  props: {
35
53
  mode: _CREATE, field: 'overrideAffinity', value: { overrideAffinity: { podAffinity } }
36
- }
54
+ },
55
+ ...requiredSetup()
37
56
  });
38
57
 
39
58
  const weightInput = wrapper.find('[data-testid="pod-affinity-weight-index0"]');