@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,319 @@
1
+ <script setup lang="ts">
2
+ import { _CREATE, _VIEW, _EDIT } from '@shell/config/query-params';
3
+ import { ref, computed, useTemplateRef } from 'vue';
4
+ import { useStore } from 'vuex';
5
+ import { useI18n } from '@shell/composables/useI18n';
6
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
7
+ import { Banner } from '@components/Banner';
8
+ import IngressCards from '@shell/edit/provisioning.cattle.io.cluster/ingress/IngressCards.vue';
9
+ import {
10
+ INGRESS_OPTIONS, INGRESS_DUAL, TRAEFIK, INGRESS_NGINX, INGRESS_NONE, INGRESS_MIGRATION_KB_LINK, INGRESS_CLASS_DEFAULT, INGRESS_CONTROLLER_CLASS_DEFAULT, INGRESS_CLASS_MIGRATION, INGRESS_CONTROLLER_CLASS_MIGRATION
11
+ } from '@shell/edit/provisioning.cattle.io.cluster/shared';
12
+ import IngressConfiguration from '@shell/edit/provisioning.cattle.io.cluster/ingress/IngressConfiguration.vue';
13
+ import YamlEditor, { EDITOR_MODES } from '@shell/components/YamlEditor';
14
+ import { set, get, mergeWithReplace } from '@shell/utils/object';
15
+ import { saferDump } from '@shell/utils/create-yaml';
16
+ import RichTranslation from '@shell/components/RichTranslation.vue';
17
+
18
+ interface Props {
19
+ mode?: string;
20
+ value: string | string[];
21
+ nginxSupported: boolean;
22
+ nginxChart: string;
23
+ traefikChart: string;
24
+ userChartValues: any;
25
+ versionInfo: any;
26
+ }
27
+ const {
28
+ mode = _CREATE,
29
+ value,
30
+ nginxChart,
31
+ traefikChart,
32
+ nginxSupported,
33
+ userChartValues,
34
+ versionInfo
35
+ } = defineProps<Props>();
36
+
37
+ const emit = defineEmits(['update:value', 'error', 'config-validation-changed', 'yaml-validation-changed', 'update-values']);
38
+ const store = useStore();
39
+ const { t } = useI18n(store);
40
+ const nginxYaml = useTemplateRef('nginx-yaml');
41
+ const traefikYaml = useTemplateRef('traefik-yaml');
42
+
43
+ const showAdvanced = ref<Boolean>(false);
44
+ const isView = computed(() => mode === _VIEW);
45
+ const isEdit = computed(() => mode === _EDIT);
46
+ const showTraefikBanner = ref<Boolean>(false);
47
+
48
+ const ingressSelection = computed(() => {
49
+ if (Array.isArray(value) ) {
50
+ return INGRESS_DUAL;
51
+ } else if (!value || value.length === 0) {
52
+ return INGRESS_NONE;
53
+ } else {
54
+ return value;
55
+ }
56
+ });
57
+ const ingressOptions = computed(() => {
58
+ return INGRESS_OPTIONS.filter((option) => !(option.id === INGRESS_DUAL && mode === _CREATE) &&
59
+ !((option.id === INGRESS_NGINX || option.id === INGRESS_DUAL) && !nginxSupported)
60
+ ).map((option) => {
61
+ return {
62
+ ...option,
63
+ selected: option.id === ingressSelection.value
64
+ };
65
+ });
66
+ });
67
+
68
+ const ingressEnabled = computed({
69
+ get() {
70
+ return ingressSelection.value !== INGRESS_NONE;
71
+ },
72
+ set(val) {
73
+ if (!val) {
74
+ emit('update:value', INGRESS_NONE);
75
+ } else {
76
+ emit('update:value', TRAEFIK);
77
+ }
78
+ }
79
+ });
80
+
81
+ function initYamlEditor(chart: string) {
82
+ const defaultChartValue = versionInfo[chart];
83
+
84
+ return mergeWithReplace(defaultChartValue?.values, userChartValues[chart]);
85
+ }
86
+
87
+ function setCompatibilityModeValues(val: boolean) {
88
+ set(traefikMerged.value, 'providers.kubernetesIngressNginx.enabled', val);
89
+ if (!val) {
90
+ set(traefikMerged.value, 'providers.kubernetesIngressNginx.ingressClass', INGRESS_CLASS_DEFAULT);
91
+ set(traefikMerged.value, 'providers.kubernetesIngressNginx.controllerClass', INGRESS_CONTROLLER_CLASS_DEFAULT);
92
+ } else {
93
+ set(traefikMerged.value, 'providers.kubernetesIngressNginx.ingressClass', INGRESS_CLASS_MIGRATION);
94
+ set(traefikMerged.value, 'providers.kubernetesIngressNginx.controllerClass', INGRESS_CONTROLLER_CLASS_MIGRATION);
95
+ }
96
+ }
97
+
98
+ function preconfigureTraefik() {
99
+ set(traefikMerged.value, 'ports.web.hostPort', 8000);
100
+ set(traefikMerged.value, 'ports.websecure.hostPort', 8443);
101
+ setCompatibilityModeValues(true);
102
+ emit('update-values', traefikChart, traefikMerged.value);
103
+ }
104
+
105
+ const traefikMerged = ref(initYamlEditor(traefikChart));
106
+ const nginxMerged = ref(initYamlEditor(nginxChart));
107
+
108
+ const nginxHttp = computed({
109
+ get() {
110
+ return get(nginxMerged.value, 'controller.hostPort.ports.http');
111
+ },
112
+ set(val: string) {
113
+ set(nginxMerged.value, 'controller.hostPort.ports.http', Number(val));
114
+ emit('update-values', nginxChart, nginxMerged.value);
115
+ updateYaml(nginxYaml.value, nginxMerged.value);
116
+ }
117
+ });
118
+ const nginxHttps = computed({
119
+ get() {
120
+ return get(nginxMerged.value, 'controller.hostPort.ports.https');
121
+ },
122
+ set(val: string) {
123
+ set(nginxMerged.value, 'controller.hostPort.ports.https', Number(val));
124
+ emit('update-values', nginxChart, nginxMerged.value);
125
+ updateYaml(nginxYaml.value, nginxMerged.value);
126
+ }
127
+ });
128
+ const traefikHttp = computed({
129
+ get() {
130
+ return get(traefikMerged.value, 'ports.web.hostPort');
131
+ },
132
+ set(val: string) {
133
+ set(traefikMerged.value, 'ports.web.hostPort', Number(val));
134
+ emit('update-values', traefikChart, traefikMerged.value);
135
+ updateYaml(traefikYaml.value, traefikMerged.value);
136
+ }
137
+ });
138
+ const traefikHttps = computed({
139
+ get() {
140
+ return get(traefikMerged.value, 'ports.websecure.hostPort');
141
+ },
142
+ set(val: string) {
143
+ set(traefikMerged.value, 'ports.websecure.hostPort', Number(val));
144
+ emit('update-values', traefikChart, traefikMerged.value);
145
+ updateYaml(traefikYaml.value, traefikMerged.value);
146
+ }
147
+ });
148
+
149
+ const compatibilityMode = computed({
150
+ get() {
151
+ return get(traefikMerged.value, 'providers.kubernetesIngressNginx.enabled');
152
+ },
153
+ set(val: boolean) {
154
+ setCompatibilityModeValues(val);
155
+ emit('update-values', traefikChart, traefikMerged.value);
156
+ updateYaml(traefikYaml.value, traefikMerged.value);
157
+ }
158
+ });
159
+
160
+ function selectIngress(id: string) {
161
+ if ( id === INGRESS_DUAL) {
162
+ emit('update:value', [TRAEFIK, INGRESS_NGINX]);
163
+ preconfigureTraefik();
164
+ } else {
165
+ emit('update:value', id);
166
+ if (id === TRAEFIK && !!isEdit.value) {
167
+ showTraefikBanner.value = true;
168
+ }
169
+ }
170
+ }
171
+
172
+ function updateYaml(component: any, value: any) {
173
+ if (component) {
174
+ component.updateValue(saferDump(value));
175
+ }
176
+ }
177
+
178
+ </script>
179
+ <template>
180
+ <h3 class="mb-10">
181
+ {{ t('cluster.ingress.title') }}
182
+ </h3>
183
+ <Checkbox
184
+ v-model:value="ingressEnabled"
185
+ :mode="mode"
186
+ :label="t('cluster.ingress.enableIngress')"
187
+ />
188
+ <div v-if="!ingressEnabled">
189
+ <Banner
190
+ color="warning"
191
+ label-key="cluster.ingress.banners.disabled.label"
192
+ />
193
+ </div>
194
+ <div v-else>
195
+ <Banner
196
+ color="info"
197
+ label-key="cluster.ingress.banners.transitioning.label"
198
+ />
199
+ <IngressCards
200
+ :options="ingressOptions"
201
+ :mode="mode"
202
+ @select="selectIngress"
203
+ />
204
+ <Banner
205
+ v-if="(isEdit && ingressSelection !== TRAEFIK) || showTraefikBanner"
206
+ color="warning"
207
+ >
208
+ <RichTranslation :k="`cluster.ingress.banners.selected.${ingressSelection}.label`">
209
+ <template #docsUrl="{ content }">
210
+ <a
211
+ :href="`${INGRESS_MIGRATION_KB_LINK}`"
212
+ tabindex="0"
213
+ target="_blank"
214
+ rel="noopener noreferrer nofollow"
215
+ >
216
+ {{ content }} <i class="icon icon-external-link" />
217
+ <span class="sr-only">{{ t('generic.opensInNewTab') }}</span>
218
+ </a>
219
+ </template>
220
+ </RichTranslation>
221
+ </Banner>
222
+ <div class="mt-20">
223
+ <IngressConfiguration
224
+ v-model:compatibility-mode="compatibilityMode"
225
+ v-model:nginxHttp="nginxHttp"
226
+ v-model:nginxHttps="nginxHttps"
227
+ v-model:traefikHttp="traefikHttp"
228
+ v-model:traefikHttps="traefikHttps"
229
+ :mode="mode"
230
+ :ingress-selection="ingressSelection"
231
+ @validation-changed="emit('config-validation-changed', $event)"
232
+ />
233
+ </div>
234
+ <div>
235
+ <button
236
+ type="button"
237
+ class="btn role-link advanced-toggle mb-0"
238
+ @click="showAdvanced = !showAdvanced"
239
+ >
240
+ {{ showAdvanced ? t('cluster.ingress.hideAdvanced') : t('cluster.ingress.showAdvanced') }}
241
+ </button>
242
+ </div>
243
+ <template v-if="showAdvanced">
244
+ <div class="row">
245
+ <div
246
+ v-if="ingressSelection === TRAEFIK || ingressSelection === INGRESS_DUAL"
247
+ :class="{ 'col': true, 'span-6': ingressSelection === INGRESS_DUAL, 'span-12': ingressSelection !== INGRESS_DUAL }"
248
+ >
249
+ <p
250
+ v-if="ingressSelection === INGRESS_DUAL"
251
+ class="mb-10"
252
+ >
253
+ {{ t('cluster.ingress.traefik.header') }}
254
+ </p>
255
+ <YamlEditor
256
+ ref="traefik-yaml"
257
+ class="ingress-yaml-editor"
258
+ data-testid="traefik-yaml-editor"
259
+ :value="traefikMerged"
260
+ :mode="mode"
261
+ :scrolling="true"
262
+ :as-object="true"
263
+ :editor-mode="isView ? EDITOR_MODES.VIEW_CODE : EDITOR_MODES.EDIT_CODE"
264
+ :hide-preview-buttons="true"
265
+ @update:value="emit('update-values', traefikChart, $event)"
266
+ @validationChanged="emit('yaml-validation-changed', {name: traefikChart, val: $event})"
267
+ />
268
+ </div>
269
+ <div
270
+ v-if="ingressSelection === INGRESS_NGINX || ingressSelection === INGRESS_DUAL"
271
+ :class="{ 'col': true, 'span-6': ingressSelection === INGRESS_DUAL, 'span-12': ingressSelection !== INGRESS_DUAL }"
272
+ >
273
+ <p
274
+ v-if="ingressSelection === INGRESS_DUAL"
275
+ class="mb-10"
276
+ >
277
+ {{ t('cluster.ingress.nginx.header') }}
278
+ </p>
279
+ <YamlEditor
280
+ ref="nginx-yaml"
281
+ class="ingress-yaml-editor"
282
+ data-testid="ingress-nginx-yaml-editor"
283
+ :value="nginxMerged"
284
+ :mode="mode"
285
+ :scrolling="true"
286
+ :as-object="true"
287
+ :editor-mode="isView ? EDITOR_MODES.VIEW_CODE : EDITOR_MODES.EDIT_CODE"
288
+ :hide-preview-buttons="true"
289
+ @update:value="emit('update-values', nginxChart, $event)"
290
+ @validationChanged="emit('yaml-validation-changed', {name: nginxChart, val: $event})"
291
+ />
292
+ </div>
293
+ </div>
294
+ </template>
295
+ </div>
296
+ </template>
297
+
298
+ <style scoped lang="scss">
299
+ .advanced-toggle {
300
+ padding: 0;
301
+ gap: 0;
302
+ min-height: 20px;
303
+
304
+ &:focus-visible {
305
+ border-color: var(--primary);
306
+ @include focus-outline;
307
+ outline-offset: -2px;
308
+ }
309
+ }
310
+
311
+ .ingress-yaml-editor {
312
+ :deep(.CodeMirror) {
313
+ height: auto !important;
314
+ }
315
+ :deep(.CodeMirror-scroll) {
316
+ max-height: 600px;
317
+ }
318
+ }
319
+ </style>
@@ -354,7 +354,8 @@ export default {
354
354
  :is="configComponent"
355
355
  v-if="value.config && configComponent"
356
356
  ref="configComponent"
357
- v-model:has-ipv6="value.hasIpv6"
357
+ v-model:is-ipv6="value.isIpv6"
358
+ v-model:is-dualStack="value.isDualStack"
358
359
  :cluster="cluster"
359
360
  :uuid="uuid"
360
361
  :mode="mode"
@@ -1,6 +1,18 @@
1
1
  import { shallowMount } from '@vue/test-utils';
2
2
  import S3Config from '@shell/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue';
3
-
3
+ jest.mock('@shell/edit/provisioning.cattle.io.cluster/shared', () => ({
4
+ RETENTION_DEFAULT: 5,
5
+ RKE2_INGRESS_NGINX: 'rke2-ingress-nginx',
6
+ RKE2_TRAEFIK: 'rke2-traefik',
7
+ INGRESS_NGINX: 'ingress-nginx',
8
+ INGRESS_CONTROLLER: 'ingress-controller',
9
+ TRAEFIK: 'traefik',
10
+ HARVESTER: 'harvester',
11
+ INGRESS_DUAL: 'dual',
12
+ INGRESS_NONE: 'none',
13
+ INGRESS_OPTIONS: [],
14
+ INGRESS_MIGRATION_KB_LINK: 'mock-link'
15
+ }));
4
16
  describe('s3Config', () => {
5
17
  const defaultProps = {
6
18
  mode: 'create',
@@ -2,7 +2,7 @@
2
2
  import { LabeledInput } from '@components/Form/LabeledInput';
3
3
  import { Banner } from '@components/Banner';
4
4
  import { Checkbox } from '@components/Form/Checkbox';
5
- import { _EDIT, _VIEW, _CREATE } from '@shell/config/query-params';
5
+ import { _EDIT, _VIEW } from '@shell/config/query-params';
6
6
  import ArrayList from '@shell/components/form/ArrayList';
7
7
  import ACE from '@shell/edit/provisioning.cattle.io.cluster/tabs/networking/ACE';
8
8
  import LabeledSelect from '@shell/components/form/LabeledSelect';
@@ -20,7 +20,7 @@ export default {
20
20
  'update:value', 'cluster-cidr-changed', 'local-cluster-auth-endpoint-changed',
21
21
  'service-cidr-changed', 'cluster-domain-changed', 'cluster-dns-changed',
22
22
  'truncate-hostname-changed', 'ca-certs-changed', 'service-node-port-range-changed',
23
- 'fqdn-changed', 'tls-san-changed', 'stack-preference-changed', 'validationChanged', 'enable-flannel-masq-changed'
23
+ 'fqdn-changed', 'tls-san-changed', 'stack-preference-changed', 'validationChanged', 'flannel-ipv6-masq-changed'
24
24
  ],
25
25
 
26
26
  components: {
@@ -59,29 +59,12 @@ export default {
59
59
  default: false
60
60
  },
61
61
 
62
- enableFlannelMasq: {
62
+ flannelIpv6Masq: {
63
63
  type: Boolean,
64
64
  default: false
65
65
  }
66
66
  },
67
67
 
68
- watch: {
69
- isProbablyIPv6(neu) {
70
- if (this.mode === _CREATE && this.showFlannelMasq) {
71
- this.$emit('enable-flannel-masq-changed', neu);
72
- }
73
- },
74
-
75
- isK3s(neu) {
76
- if (!neu) {
77
- this.$emit('enable-flannel-masq-changed', null);
78
- } else if (this.isProbablyIPv6) {
79
- this.$emit('enable-flannel-masq-changed', true);
80
- }
81
- },
82
-
83
- },
84
-
85
68
  data() {
86
69
  return { STACK_PREFS };
87
70
  },
@@ -157,11 +140,11 @@ export default {
157
140
  return this.isK3s && flannelEnabled;
158
141
  },
159
142
 
160
- hasIpv6StackPref() {
143
+ isIpv6StackPref() {
161
144
  return [STACK_PREFS.IPV6, STACK_PREFS.DUAL].includes(this.value?.spec?.rkeConfig?.networking?.stackPreference);
162
145
  },
163
146
 
164
- hasIpv6ServerConfig() {
147
+ isIpv6ServerConfig() {
165
148
  const clusterCIDR = this.serverConfig['cluster-cidr'] || '';
166
149
  const serviceCIDR = this.serverConfig['service-cidr'] || '';
167
150
 
@@ -169,25 +152,7 @@ export default {
169
152
  },
170
153
 
171
154
  isProbablyIPv6() {
172
- return this.hasSomeIpv6Pools || this.hasIpv6StackPref || this.hasIpv6ServerConfig;
173
- },
174
- },
175
-
176
- methods: {
177
- // if ipv6 pools are detected, we enforce dual-stack or ipv6 stack prefs.
178
- // If ipv6 pools are not detected we don't know for sure they aren't there so we don't validate the input
179
- // also not validating the input when editing existing clusters to ensure we don't prevent editing clusters using dual-stack VPCs provisioned before the ipv6 feature was added
180
- stackPreferenceValidator(val) {
181
- const value = val?.value || val;
182
- let isValid;
183
-
184
- if (this.hasSomeIpv6Pools && this.mode === _CREATE) {
185
- isValid = value !== STACK_PREFS.IPV4;
186
-
187
- return isValid ? null : this.t('cluster.rke2.stackPreference.errorNeedsIpv6');
188
- } else {
189
- return null;
190
- }
155
+ return this.hasSomeIpv6Pools || this.isIpv6StackPref || this.isIpv6ServerConfig;
191
156
  },
192
157
  }
193
158
  };
@@ -212,6 +177,7 @@ export default {
212
177
  :mode="mode"
213
178
  :disabled="isEdit"
214
179
  :label="t('cluster.rke2.address.clusterCidr.label')"
180
+ data-testid="cluster-cidr"
215
181
  @update:value="$emit('cluster-cidr-changed', $event)"
216
182
  />
217
183
  </div>
@@ -224,6 +190,7 @@ export default {
224
190
  :mode="mode"
225
191
  :disabled="isEdit"
226
192
  :label="t('cluster.rke2.address.serviceCidr.label')"
193
+ data-testid="service-cidr"
227
194
  @update:value="$emit('service-cidr-changed', $event)"
228
195
  />
229
196
  </div>
@@ -331,7 +298,6 @@ export default {
331
298
  :value="localValue?.spec?.rkeConfig?.networking?.stackPreference || STACK_PREFS.IPV4"
332
299
  :mode="mode"
333
300
  :options="stackPreferenceOptions"
334
- :rules="[stackPreferenceValidator]"
335
301
  data-testid="network-tab-stackpreferences"
336
302
  :require-dirty="false"
337
303
  @selecting="e=>$emit('stack-preference-changed', e)"
@@ -357,11 +323,11 @@ export default {
357
323
  class="col"
358
324
  >
359
325
  <Checkbox
360
- :value="enableFlannelMasq"
326
+ :value="flannelIpv6Masq"
361
327
  data-testid="cluster-rke2-flannel-masq-checkbox"
362
328
  :mode="mode"
363
329
  :label="t('cluster.k3s.flannelMasq.label')"
364
- @update:value="e=>$emit('enable-flannel-masq-changed', e)"
330
+ @update:value="e=>$emit('flannel-ipv6-masq-changed', e)"
365
331
  />
366
332
  </div>
367
333
  </div>
@@ -72,7 +72,7 @@ export default {
72
72
  this.value.metadata.namespace = this.filteredProjects[0].status.backingNamespace;
73
73
  this.value.metadata.labels[UI_PROJECT_SECRET] = this.filteredProjects[0].metadata.name;
74
74
  } else {
75
- this.projectName = this.filteredProjects.find((p) => p.metadata.name === projectScopedLabel).metadata.name;
75
+ this.projectName = this.filteredProjects.find((p) => p.metadata.name === projectScopedLabel)?.metadata.name;
76
76
  }
77
77
  }
78
78
  },
package/edit/token.vue CHANGED
@@ -2,7 +2,7 @@
2
2
  import { mapGetters } from 'vuex';
3
3
  import day from 'dayjs';
4
4
  import sortBy from 'lodash/sortBy';
5
- import { MANAGEMENT, NORMAN } from '@shell/config/types';
5
+ import { MANAGEMENT, EXT } from '@shell/config/types';
6
6
  import { Banner } from '@components/Banner';
7
7
  import DetailText from '@shell/components/DetailText';
8
8
  import Footer from '@shell/components/form/Footer';
@@ -14,6 +14,7 @@ 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
16
  import { SETTING } from '@shell/config/settings';
17
+ import Checkbox from '@components/Form/Checkbox/Checkbox.vue';
17
18
 
18
19
  export default {
19
20
  components: {
@@ -24,6 +25,7 @@ export default {
24
25
  LabeledSelect,
25
26
  RadioGroup,
26
27
  Select,
28
+ Checkbox,
27
29
  },
28
30
 
29
31
  mixins: [CreateEditView],
@@ -41,7 +43,11 @@ export default {
41
43
 
42
44
  return {
43
45
  errors: null,
46
+ user: null,
44
47
  form: {
48
+ enabled: true,
49
+ description: '',
50
+ clusterName: '',
45
51
  expiryType: 'never',
46
52
  customExpiry: 0,
47
53
  customExpiryUnits: 'minute',
@@ -51,11 +57,13 @@ export default {
51
57
  accessKey: '',
52
58
  secretKey: '',
53
59
  maxTTL,
60
+ ttl: ''
54
61
  };
55
62
  },
56
63
 
57
64
  computed: {
58
65
  ...mapGetters({ t: 'i18n/t' }),
66
+
59
67
  scopes() {
60
68
  const all = this.$store.getters['management/all'](MANAGEMENT.CLUSTER);
61
69
  const kubeClusters = filterHiddenLocalCluster(filterOnlyKubernetesClusters(all, this.$store), this.$store);
@@ -71,16 +79,20 @@ export default {
71
79
  const options = ['never', 'day', 'month', 'year', 'custom'];
72
80
  let opts = options.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.expiry.options.${ opt }`) }));
73
81
 
74
- // When the TTL is anything other than 0, present only two options
82
+ // When the TTL is greater than 0, present only two options
75
83
  // (1) The maximum allowed
76
84
  // (2) Custom
77
- if (this.maxTTL !== 0 ) {
85
+ if (this.maxTTL > 0 ) {
78
86
  const now = day();
79
87
  const expiry = now.add(this.maxTTL, 'minute');
80
88
  const max = diffFrom(expiry, now, this.t);
81
89
 
82
90
  opts = opts.filter((opt) => opt.value === 'custom');
83
91
  opts.unshift({ value: 'max', label: this.t('accountAndKeys.apiKeys.add.expiry.options.maximum', { value: max.string }) });
92
+ } else {
93
+ // maxTTL <= 0 means there is no maximum, so we can show the 'never' option which results in an infinite TTL
94
+ // OR if we set a positive TTL, then it assumes that value
95
+ opts = opts.filter((opt) => opt.value === 'never' || opt.value === 'custom');
84
96
  }
85
97
 
86
98
  return opts;
@@ -91,6 +103,9 @@ export default {
91
103
 
92
104
  return filtered.map((opt) => ({ value: opt, label: this.t(`accountAndKeys.apiKeys.add.customExpiry.options.${ opt }`) }));
93
105
  },
106
+ hasNeverOption() {
107
+ return this.expiryOptions?.filter((opt) => opt.value === 'never')?.length === 1;
108
+ }
94
109
  },
95
110
 
96
111
  mounted() {
@@ -115,31 +130,33 @@ export default {
115
130
  });
116
131
  },
117
132
 
118
- async actuallySave(url) {
133
+ async actuallySave() {
134
+ // update expiration value before save
119
135
  this.updateExpiry();
120
- if ( this.isCreate ) {
121
- // Description is a bit weird, so need to clone and set this
122
- // rather than use this.value - need to find a way to set this if we ever
123
- // want to allow edit (which I don't think we do)
124
- const res = await this.value.save();
125
136
 
126
- this.created = res;
127
- this.ttlLimited = res.ttl !== this.value.ttl;
128
- const token = this.created.token.split(':');
137
+ if ( this.isCreate ) {
138
+ const steveToken = await this.$store.dispatch('management/create', {
139
+ type: EXT.TOKEN,
140
+ spec: {
141
+ description: this.form.description,
142
+ kind: '',
143
+ userPrincipal: null, // will be set by the backend to the current user
144
+ clusterName: this.form.clusterName,
145
+ enabled: this.form.enabled,
146
+ ttl: this.ttl
147
+ // userID: not needed as it will be set by the backend to the current user
148
+ }
149
+ });
150
+
151
+ const steveTokenSaved = await steveToken.save();
152
+
153
+ this.created = steveTokenSaved;
154
+ this.ttlLimited = this.created?.spec?.ttl !== this.ttl;
155
+ const token = this.created?.status?.bearerToken?.split(':');
129
156
 
130
157
  this.accessKey = token[0];
131
158
  this.secretKey = (token.length > 1) ? token[1] : '';
132
- this.token = this.created.token;
133
-
134
- // Force a refresh of the token so we get the expiry date correctly
135
- await this.$store.dispatch('rancher/find', {
136
- type: NORMAN.TOKEN,
137
- id: res.id,
138
- opt: { force: true }
139
- }, { root: true });
140
- } else {
141
- // Note: update of existing key not supported currently
142
- await this.value.save();
159
+ this.token = this.created?.status?.bearerToken;
143
160
  }
144
161
  },
145
162
 
@@ -159,7 +176,9 @@ export default {
159
176
  const units = (v === 'custom') ? this.form.customExpiryUnits : v;
160
177
  let ttl = 0;
161
178
 
162
- if (units === 'max') {
179
+ if (v === 'never') {
180
+ ttl = -1;
181
+ } else if (units === 'max') {
163
182
  ttl = this.maxTTL * 60 * 1000;
164
183
  } else if ( units !== 'never' ) {
165
184
  const now = day();
@@ -167,7 +186,8 @@ export default {
167
186
 
168
187
  ttl = expiry.diff(now);
169
188
  }
170
- this.value.ttl = ttl;
189
+
190
+ this.ttl = ttl;
171
191
  }
172
192
  }
173
193
  };
@@ -178,7 +198,7 @@ export default {
178
198
  <div class="pl-10 pr-10">
179
199
  <LabeledInput
180
200
  key="description"
181
- v-model:value="value.description"
201
+ v-model:value="form.description"
182
202
  :placeholder="t('accountAndKeys.apiKeys.add.description.placeholder')"
183
203
  label-key="accountAndKeys.apiKeys.add.description.label"
184
204
  mode="edit"
@@ -186,13 +206,30 @@ export default {
186
206
  />
187
207
 
188
208
  <LabeledSelect
189
- v-model:value="value.clusterId"
209
+ v-model:value="form.clusterName"
190
210
  class="mt-20 scope-select"
191
211
  label-key="accountAndKeys.apiKeys.add.scope"
192
212
  :options="scopes"
193
213
  />
194
214
 
195
- <h5 class="pt-20">
215
+ <Checkbox
216
+ v-model:value="form.enabled"
217
+ class="mt-20 mb-20"
218
+ :mode="mode"
219
+ label-key="accountAndKeys.apiKeys.add.enabled"
220
+ />
221
+
222
+ <Banner
223
+ v-if="hasNeverOption"
224
+ color="warning"
225
+ class="mt-20"
226
+ >
227
+ <div>
228
+ {{ t('accountAndKeys.apiKeys.info.expiryOptionsWithNever') }}
229
+ </div>
230
+ </Banner>
231
+
232
+ <h5 class="mb-20">
196
233
  {{ t('accountAndKeys.apiKeys.add.expiry.label') }}
197
234
  </h5>
198
235
 
@@ -204,7 +241,9 @@ export default {
204
241
  class="mr-20"
205
242
  name="expiryGroup"
206
243
  />
207
- <div class="ml-20 mt-10 expiry">
244
+ <div
245
+ class="ml-20 mt-10 expiry"
246
+ >
208
247
  <input
209
248
  v-model="form.customExpiry"
210
249
  :disabled="form.expiryType !== 'custom'"