@rancher/shell 3.0.12-rc.3 → 3.0.12-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/global/_layout.scss +4 -0
- package/assets/translations/en-us.yaml +144 -41
- package/assets/translations/zh-hans.yaml +1 -7
- package/chart/monitoring/ClusterSelector.vue +0 -21
- package/chart/monitoring/prometheus/index.vue +6 -3
- package/components/CruResource.vue +161 -14
- package/components/ExplorerMembers.vue +8 -4
- package/components/ExplorerProjectsNamespaces.vue +10 -6
- package/components/GrowlManager.vue +4 -0
- package/components/MgmtNodeList.vue +184 -0
- package/components/Resource/Detail/Card/StateCard/__tests__/composables.test.ts +90 -1
- package/components/Resource/Detail/Card/StateCard/composables.ts +57 -87
- package/components/Resource/Detail/Card/StatusCard/__tests__/StatusCard.test.ts +61 -0
- package/components/Resource/Detail/Card/StatusCard/index.vue +61 -15
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +2 -0
- package/components/Resource/Detail/Metadata/KeyValue.vue +5 -2
- package/components/Resource/Detail/Metadata/KeyValueRow.vue +2 -6
- package/components/ResourceDetail/index.vue +1 -1
- package/components/ResourceList/Masthead.vue +7 -1
- package/components/ResourceList/index.vue +82 -1
- package/components/RichTranslation.vue +5 -2
- package/components/Setting.vue +1 -0
- package/components/SubtleLink.vue +31 -6
- package/components/Tabbed/Tab.vue +29 -3
- package/components/Tabbed/index.vue +25 -3
- package/components/TableOfContents/TableOfContents.vue +109 -0
- package/components/TableOfContents/composables.ts +258 -0
- package/components/Window/ContainerShell.vue +21 -11
- package/components/Window/__tests__/ContainerShell.test.ts +107 -37
- package/components/Wizard.vue +9 -4
- package/components/fleet/AppCoChartGrid.vue +401 -0
- package/components/fleet/AppCoEmptyState.vue +127 -0
- package/components/fleet/AppCoPageHeader.vue +119 -0
- package/components/fleet/AppCoVersionSelect.vue +70 -0
- package/components/fleet/FleetClusterTargets/ClusterSelectionFields.vue +217 -0
- package/components/fleet/FleetClusterTargets/TargetsList.vue +123 -35
- package/components/fleet/FleetClusterTargets/index.vue +189 -146
- package/components/fleet/FleetIntro.vue +7 -3
- package/components/fleet/FleetNoWorkspaces.vue +7 -3
- package/components/fleet/FleetSecretSelector.vue +5 -3
- package/components/fleet/FleetValuesFrom.vue +8 -2
- package/components/fleet/GitRepoTargetTab.vue +0 -2
- package/components/fleet/HelmOpAdvancedTab.vue +19 -53
- package/components/fleet/HelmOpAppCoConfigTab.vue +593 -0
- package/components/fleet/HelmOpAppCoResourcesSection.vue +162 -0
- package/components/fleet/HelmOpResourcesSection.vue +82 -0
- package/components/fleet/HelmOpTargetOptionsSection.vue +89 -0
- package/components/fleet/HelmOpTargetTab.vue +64 -60
- package/components/fleet/HelmOpValuesTab.vue +129 -105
- package/components/fleet/__tests__/AppCoEmptyState.test.ts +71 -0
- package/components/fleet/__tests__/AppCoVersionSelect.test.ts +36 -0
- package/components/fleet/__tests__/ClusterSelectionFields.test.ts +62 -0
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +253 -0
- package/components/fleet/__tests__/FleetSecretSelector.test.ts +16 -0
- package/components/fleet/__tests__/FleetValuesFrom.test.ts +44 -0
- package/components/fleet/__tests__/HelmOpAppCoConfigTab.test.ts +59 -0
- package/components/fleet/__tests__/HelmOpAppCoResourcesSection.test.ts +62 -0
- package/components/fleet/__tests__/HelmOpResourcesSection.test.ts +43 -0
- package/components/fleet/__tests__/HelmOpTargetOptionsSection.test.ts +34 -0
- package/components/fleet/__tests__/HelmOpValuesTab.test.ts +39 -0
- package/components/fleet/__tests__/__snapshots__/AppCoEmptyState.test.ts.snap +97 -0
- package/components/fleet/__tests__/__snapshots__/AppCoVersionSelect.test.ts.snap +30 -0
- package/components/fleet/__tests__/__snapshots__/ClusterSelectionFields.test.ts.snap +209 -0
- package/components/fleet/__tests__/__snapshots__/HelmOpTargetOptionsSection.test.ts.snap +140 -0
- package/components/fleet/dashboard/Empty.vue +8 -4
- package/components/fleet/dashboard/ResourceCard.vue +28 -0
- package/components/fleet/dashboard/ResourceDetails.vue +28 -0
- package/components/fleet/dashboard/__tests__/ResourceCard.test.ts +87 -0
- package/components/form/ArrayList.vue +61 -4
- package/components/form/KeyValue.vue +23 -2
- package/components/form/LabeledSelect.vue +39 -1
- package/components/form/Labels.vue +22 -3
- package/components/form/NameNsDescription.vue +13 -5
- package/components/form/ResourceTabs/index.vue +1 -0
- package/components/form/__tests__/NameNsDescription.test.ts +75 -0
- package/components/formatter/InternalExternalIP.vue +10 -4
- package/components/formatter/ServiceTargets.vue +26 -7
- package/components/formatter/__tests__/InternalExternalIP.test.ts +132 -0
- package/components/formatter/__tests__/ServiceTargets.test.ts +412 -0
- package/components/nav/Header.vue +4 -0
- package/components/nav/TopLevelMenu.vue +7 -2
- package/components/nav/__tests__/Header.test.ts +15 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +120 -2
- package/components/templates/default.vue +9 -4
- package/components/templates/home.vue +9 -4
- package/components/templates/plain.vue +9 -4
- package/composables/useHelmOpResources.test.ts +56 -0
- package/composables/useHelmOpResources.ts +32 -0
- package/composables/useStateColor.test.ts +325 -0
- package/composables/useStateColor.ts +128 -0
- package/config/home-links.js +1 -1
- package/config/labels-annotations.js +1 -0
- package/config/product/explorer.js +17 -4
- package/config/product/manager.js +2 -0
- package/config/router/index.js +16 -0
- package/config/router/navigation-guards/__tests__/authentication.test.ts +130 -0
- package/config/router/navigation-guards/authentication.js +10 -4
- package/config/router/routes.js +20 -6
- package/config/settings.ts +0 -2
- package/config/table-headers.js +3 -4
- package/config/types.js +9 -0
- package/core/plugin-products-base.ts +3 -3
- package/core/plugin-types.ts +83 -30
- package/core/plugin.ts +3 -0
- package/core/types-provisioning.ts +34 -1
- package/core/types.ts +15 -2
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +114 -0
- package/detail/__tests__/workload.test.ts +3 -152
- package/detail/catalog.cattle.io.clusterrepo.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +30 -4
- package/detail/workload/index.vue +12 -55
- package/edit/__tests__/catalog.cattle.io.clusterrepo.test.ts +248 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +105 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/index.test.ts.snap +1 -0
- package/edit/auth/__tests__/azuread.test.ts +34 -9
- package/edit/auth/__tests__/github.test.ts +234 -0
- package/edit/auth/__tests__/oidc.test.ts +26 -6
- package/edit/auth/__tests__/saml.test.ts +196 -0
- package/edit/auth/azuread.vue +128 -95
- package/edit/auth/github.vue +72 -13
- package/edit/auth/ldap/__tests__/index.test.ts +206 -0
- package/edit/auth/ldap/config.vue +8 -0
- package/edit/auth/ldap/index.vue +75 -1
- package/edit/auth/oidc.vue +119 -73
- package/edit/auth/saml.vue +76 -12
- package/edit/catalog.cattle.io.clusterrepo.vue +140 -32
- package/edit/fleet.cattle.io.helmop.vue +491 -136
- package/edit/management.cattle.io.user.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +84 -10
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/list/group.principal.vue +5 -4
- package/list/harvesterhci.io.management.cluster.vue +8 -9
- package/list/management.cattle.io.user.vue +12 -9
- package/list/provisioning.cattle.io.cluster.vue +16 -10
- package/mixins/__tests__/auth-config.test.ts +90 -0
- package/mixins/__tests__/chart.test.ts +94 -0
- package/mixins/__tests__/resource-fetch-api-pagination.test.ts +48 -0
- package/mixins/auth-config.js +7 -0
- package/mixins/chart.js +11 -2
- package/mixins/child-hook.js +12 -6
- package/mixins/create-edit-view/impl.js +5 -3
- package/mixins/resource-fetch-api-pagination.js +21 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +57 -0
- package/models/__tests__/compliance.cattle.io.clusterscan.test.ts +144 -0
- package/models/__tests__/fleet-application.test.ts +175 -0
- package/models/__tests__/fleet.cattle.io.bundle.test.ts +169 -0
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +84 -0
- package/models/__tests__/management.cattle.io.node.ts +22 -0
- package/models/__tests__/namespace.test.ts +36 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +49 -0
- package/models/__tests__/workload.test.ts +401 -26
- package/models/catalog.cattle.io.clusterrepo.js +28 -4
- package/models/compliance.cattle.io.clusterscan.js +39 -4
- package/models/fleet-application.js +4 -0
- package/models/fleet.cattle.io.helmop.js +20 -1
- package/models/management.cattle.io.cluster.js +18 -2
- package/models/management.cattle.io.node.js +44 -3
- package/models/namespace.js +1 -1
- package/models/pod.js +33 -1
- package/models/provisioning.cattle.io.cluster.js +5 -5
- package/models/workload.js +108 -13
- package/models/workload.service.js +5 -0
- package/package.json +14 -13
- package/pages/about.vue +5 -6
- package/pages/auth/login.vue +0 -35
- package/pages/auth/setup.vue +11 -0
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +2 -2
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +10 -1
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +93 -0
- package/pages/c/_cluster/apps/charts/chart.vue +2 -1
- package/pages/c/_cluster/apps/charts/index.vue +48 -10
- package/pages/c/_cluster/apps/charts/install.vue +122 -116
- package/pages/c/_cluster/auth/roles/index.vue +5 -4
- package/pages/c/_cluster/explorer/workload-dashboard/ByNamespaceSection.vue +31 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByStateSection.vue +138 -0
- package/pages/c/_cluster/explorer/workload-dashboard/ByTypeSection.vue +30 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadCard.vue +155 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadNamespaceCard.vue +142 -0
- package/pages/c/_cluster/explorer/workload-dashboard/WorkloadTypeCard.vue +159 -0
- package/pages/c/_cluster/explorer/workload-dashboard/__tests__/composable.test.ts +561 -0
- package/pages/c/_cluster/explorer/workload-dashboard/composable.ts +440 -0
- package/pages/c/_cluster/explorer/workload-dashboard/index.vue +187 -0
- package/pages/c/_cluster/explorer/workload-dashboard/types.ts +80 -0
- package/pages/c/_cluster/fleet/application/create.vue +187 -136
- package/pages/c/_cluster/fleet/application/index.vue +5 -3
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailBody.vue +338 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/ChartDetailHeader.vue +121 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/chart.vue +369 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/charts.vue +248 -0
- package/pages/c/_cluster/fleet/application/suse-app-collection/credentials.vue +310 -0
- package/pages/c/_cluster/fleet/index.vue +2 -2
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +96 -0
- package/pages/c/_cluster/uiplugins/index.vue +15 -0
- package/pages/fail-whale.vue +16 -11
- package/pages/home.vue +16 -46
- package/plugins/clean-html.d.ts +9 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +93 -0
- package/plugins/dashboard-store/resource-class.js +62 -7
- package/plugins/steve/__tests__/actions.test.ts +212 -0
- package/plugins/steve/actions.js +96 -0
- package/plugins/steve/steve-pagination-utils.ts +1 -1
- package/rancher-components/Accordion/Accordion.vue +53 -9
- package/rancher-components/Form/Checkbox/Checkbox.vue +14 -0
- package/rancher-components/Form/Radio/RadioButton.vue +17 -1
- package/rancher-components/Form/Radio/RadioGroup.vue +10 -0
- package/rancher-components/Pill/RcTag/RcTag.vue +3 -2
- package/rancher-components/RcButton/RcButton.test.ts +103 -0
- package/rancher-components/RcButton/RcButton.vue +94 -15
- package/rancher-components/RcButton/types.ts +3 -0
- package/rancher-components/RcItemCard/RcItemCard.test.ts +18 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +2 -2
- package/rancher-components/RcSection/RcSection.vue +28 -3
- package/scripts/extension/helm/package/Dockerfile +1 -1
- package/scripts/test-plugins-build.sh +2 -1
- package/store/__tests__/notifications.test.ts +434 -0
- package/store/catalog.js +57 -0
- package/store/plugins.js +7 -4
- package/types/components/buttonGroup.ts +5 -0
- package/types/shell/index.d.ts +104 -70
- package/utils/__tests__/auth.test.ts +273 -0
- package/utils/__tests__/computed.test.ts +193 -0
- package/utils/__tests__/cspAdaptor.test.ts +163 -0
- package/utils/__tests__/dom.test.ts +81 -0
- package/utils/__tests__/duration.test.ts +37 -1
- package/utils/__tests__/dynamic-importer.test.ts +102 -0
- package/utils/__tests__/fleet-appco.test.ts +312 -0
- package/utils/__tests__/monitoring.test.ts +130 -0
- package/utils/__tests__/object.test.ts +22 -0
- package/utils/__tests__/platform.test.ts +91 -0
- package/utils/__tests__/position.test.ts +237 -0
- package/utils/__tests__/provider.test.ts +51 -1
- package/utils/__tests__/queue.test.ts +232 -0
- package/utils/__tests__/release-notes.test.ts +221 -0
- package/utils/__tests__/router.test.js +254 -1
- package/utils/__tests__/select.test.ts +208 -0
- package/utils/__tests__/time.test.ts +265 -1
- package/utils/__tests__/title.test.ts +47 -0
- package/utils/__tests__/width.test.ts +53 -0
- package/utils/__tests__/window.test.ts +158 -0
- package/utils/__tests__/xccdf.test.ts +126 -6
- package/utils/crypto/__tests__/browserHashUtils.test.ts +98 -0
- package/utils/crypto/__tests__/index.test.ts +144 -0
- package/utils/duration.ts +104 -0
- package/utils/dynamic-content/__tests__/notification-handler.test.ts +196 -0
- package/utils/dynamic-content/info.ts +2 -1
- package/utils/error.js +13 -0
- package/utils/fleet-appco.ts +323 -0
- package/utils/object.js +22 -2
- package/utils/provider.ts +12 -0
- package/utils/validators/__tests__/container-images.test.ts +104 -0
- package/utils/validators/__tests__/flow-output.test.ts +91 -0
- package/utils/validators/__tests__/logging-outputs.test.ts +58 -0
- package/utils/validators/__tests__/monitoring-route.test.ts +119 -0
- package/utils/xccdf.ts +39 -42
- package/vue.config.js +1 -1
- package/pages/support/index.vue +0 -264
- package/utils/duration.js +0 -43
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import { ref, watch, computed } from 'vue';
|
|
2
|
+
import { ref, watch, computed, toRef } from 'vue';
|
|
3
3
|
import debounce from 'lodash/debounce';
|
|
4
4
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
5
5
|
import { removeAt } from '@shell/utils/array';
|
|
6
6
|
import { TextAreaAutoGrow } from '@components/Form/TextArea';
|
|
7
7
|
import { clone } from '@shell/utils/object';
|
|
8
8
|
import { LabeledInput } from '@components/Form/LabeledInput';
|
|
9
|
+
import Banner from '@components/Banner/Banner.vue';
|
|
10
|
+
import { useVeeValidateField } from '@shell/composables/useVeeValidateField';
|
|
9
11
|
const DEFAULT_PROTIP = 'Tip: Paste lines into any list field for easy bulk entry';
|
|
10
12
|
|
|
11
13
|
export default {
|
|
12
14
|
emits: ['add', 'remove', 'update:value'],
|
|
13
15
|
|
|
14
|
-
components: {
|
|
15
|
-
|
|
16
|
+
components: {
|
|
17
|
+
TextAreaAutoGrow, LabeledInput, Banner
|
|
18
|
+
},
|
|
19
|
+
props: {
|
|
16
20
|
value: {
|
|
17
21
|
type: Array,
|
|
18
22
|
default: null,
|
|
@@ -106,7 +110,17 @@ export default {
|
|
|
106
110
|
componentTestid: {
|
|
107
111
|
type: String,
|
|
108
112
|
default: 'array-list',
|
|
109
|
-
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Field name for vee-validate integration. When provided, the component
|
|
117
|
+
* registers with a parent form context for schema-level validation.
|
|
118
|
+
*/
|
|
119
|
+
name: {
|
|
120
|
+
type: String,
|
|
121
|
+
default: null,
|
|
122
|
+
},
|
|
123
|
+
|
|
110
124
|
},
|
|
111
125
|
|
|
112
126
|
setup(props, { emit }) {
|
|
@@ -126,6 +140,26 @@ export default {
|
|
|
126
140
|
return props.mode === _VIEW;
|
|
127
141
|
});
|
|
128
142
|
|
|
143
|
+
// vee-validate integration: array-level validation via a parent form schema.
|
|
144
|
+
// Per-item validation continues to use the existing `rules` prop on
|
|
145
|
+
// LabeledInput.
|
|
146
|
+
const arrayValue = computed(() => props.value || []);
|
|
147
|
+
const { effectiveValidationMessage, veeHandleBlur, veeValidate } = useVeeValidateField({
|
|
148
|
+
name: toRef(props, 'name'),
|
|
149
|
+
rules: ref([]),
|
|
150
|
+
value: arrayValue,
|
|
151
|
+
validationMessage: ref(null),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Only structural changes (add, delete, paste) mark trigger validation
|
|
155
|
+
// when invoking update()
|
|
156
|
+
const isStructuralChange = ref(false);
|
|
157
|
+
|
|
158
|
+
const validate = () => {
|
|
159
|
+
veeHandleBlur(undefined, false);
|
|
160
|
+
veeValidate();
|
|
161
|
+
};
|
|
162
|
+
|
|
129
163
|
/**
|
|
130
164
|
* Cleanup rows and emit input
|
|
131
165
|
*/
|
|
@@ -144,6 +178,10 @@ export default {
|
|
|
144
178
|
}
|
|
145
179
|
}
|
|
146
180
|
emit('update:value', out);
|
|
181
|
+
if (isStructuralChange.value) {
|
|
182
|
+
validate();
|
|
183
|
+
isStructuralChange.value = false;
|
|
184
|
+
}
|
|
147
185
|
};
|
|
148
186
|
|
|
149
187
|
const lastUpdateWasFromValue = ref(false);
|
|
@@ -177,6 +215,9 @@ export default {
|
|
|
177
215
|
queueUpdate,
|
|
178
216
|
isView,
|
|
179
217
|
update,
|
|
218
|
+
effectiveValidationMessage,
|
|
219
|
+
isStructuralChange,
|
|
220
|
+
validate,
|
|
180
221
|
};
|
|
181
222
|
},
|
|
182
223
|
|
|
@@ -211,6 +252,7 @@ export default {
|
|
|
211
252
|
},
|
|
212
253
|
methods: {
|
|
213
254
|
add() {
|
|
255
|
+
this.isStructuralChange = true;
|
|
214
256
|
this.rows.push({ value: clone(this.defaultAddValue) });
|
|
215
257
|
if (this.defaultAddValue) {
|
|
216
258
|
this.queueUpdate();
|
|
@@ -229,6 +271,7 @@ export default {
|
|
|
229
271
|
*/
|
|
230
272
|
remove(row, index) {
|
|
231
273
|
this.$emit('remove', { row, index });
|
|
274
|
+
this.isStructuralChange = true;
|
|
232
275
|
removeAt(this.rows, index);
|
|
233
276
|
this.queueUpdate();
|
|
234
277
|
},
|
|
@@ -240,6 +283,7 @@ export default {
|
|
|
240
283
|
event.preventDefault();
|
|
241
284
|
const text = event.clipboardData.getData('text/plain');
|
|
242
285
|
|
|
286
|
+
this.isStructuralChange = true;
|
|
243
287
|
if (this.valueMultiline) {
|
|
244
288
|
// Allow to paste multiple lines
|
|
245
289
|
this.rows[index].value = text;
|
|
@@ -285,6 +329,12 @@ export default {
|
|
|
285
329
|
</h3>
|
|
286
330
|
</slot>
|
|
287
331
|
</div>
|
|
332
|
+
<Banner
|
|
333
|
+
v-if="effectiveValidationMessage"
|
|
334
|
+
class="validation-banner"
|
|
335
|
+
color="error"
|
|
336
|
+
:label="effectiveValidationMessage"
|
|
337
|
+
/>
|
|
288
338
|
|
|
289
339
|
<div>
|
|
290
340
|
<template v-if="rows.length">
|
|
@@ -335,6 +385,7 @@ export default {
|
|
|
335
385
|
:aria-label="a11yLabel ? `${a11yLabel} ${t('generic.ariaLabel.genericRow', {index: idx+1})}` : undefined"
|
|
336
386
|
@paste="onPaste(idx, $event)"
|
|
337
387
|
@update:value="queueUpdate"
|
|
388
|
+
@blur="validate"
|
|
338
389
|
/>
|
|
339
390
|
<LabeledInput
|
|
340
391
|
v-else-if="rules.length > 0"
|
|
@@ -348,6 +399,7 @@ export default {
|
|
|
348
399
|
:aria-label="a11yLabel ? `${a11yLabel} ${t('generic.ariaLabel.genericRow', {index: idx+1})}` : undefined"
|
|
349
400
|
@paste="onPaste(idx, $event)"
|
|
350
401
|
@update:value="queueUpdate"
|
|
402
|
+
@blur="validate"
|
|
351
403
|
/>
|
|
352
404
|
<input
|
|
353
405
|
v-else
|
|
@@ -358,6 +410,7 @@ export default {
|
|
|
358
410
|
:disabled="isView || disabled"
|
|
359
411
|
:aria-label="a11yLabel ? `${a11yLabel} ${t('generic.ariaLabel.genericRow', {index: idx+1})}` : undefined"
|
|
360
412
|
@paste="onPaste(idx, $event)"
|
|
413
|
+
@blur="validate"
|
|
361
414
|
>
|
|
362
415
|
</slot>
|
|
363
416
|
</div>
|
|
@@ -443,6 +496,10 @@ export default {
|
|
|
443
496
|
color: var(--error);
|
|
444
497
|
}
|
|
445
498
|
|
|
499
|
+
.validation-banner {
|
|
500
|
+
margin-top: 0;
|
|
501
|
+
}
|
|
502
|
+
|
|
446
503
|
.box {
|
|
447
504
|
display: grid;
|
|
448
505
|
grid-template-columns: auto $array-list-remove-margin;
|
|
@@ -13,6 +13,7 @@ import { asciiLike } from '@shell/utils/string';
|
|
|
13
13
|
import CodeMirror from '@shell/components/CodeMirror';
|
|
14
14
|
import isEqual from 'lodash/isEqual';
|
|
15
15
|
import { LabeledTooltip } from '@components/LabeledTooltip';
|
|
16
|
+
import { RcButton } from '@components/RcButton';
|
|
16
17
|
|
|
17
18
|
export default {
|
|
18
19
|
name: 'KeyValue',
|
|
@@ -24,7 +25,8 @@ export default {
|
|
|
24
25
|
Select,
|
|
25
26
|
TextAreaAutoGrow,
|
|
26
27
|
FileSelector,
|
|
27
|
-
LabeledTooltip
|
|
28
|
+
LabeledTooltip,
|
|
29
|
+
RcButton
|
|
28
30
|
},
|
|
29
31
|
props: {
|
|
30
32
|
value: {
|
|
@@ -241,6 +243,10 @@ export default {
|
|
|
241
243
|
keyErrors: {
|
|
242
244
|
type: Object,
|
|
243
245
|
default: () => ({})
|
|
246
|
+
},
|
|
247
|
+
useRcButton: {
|
|
248
|
+
type: Boolean,
|
|
249
|
+
default: false
|
|
244
250
|
}
|
|
245
251
|
},
|
|
246
252
|
data() {
|
|
@@ -879,8 +885,23 @@ export default {
|
|
|
879
885
|
name="add"
|
|
880
886
|
:add="add"
|
|
881
887
|
>
|
|
888
|
+
<RcButton
|
|
889
|
+
v-if="addAllowed && useRcButton"
|
|
890
|
+
size="small"
|
|
891
|
+
variant="secondary"
|
|
892
|
+
:class="[addClass]"
|
|
893
|
+
data-testid="add_row_item_button"
|
|
894
|
+
:disabled="loading || disabled || (keyOptions && filteredKeyOptions.length === 0)"
|
|
895
|
+
:aria-label="t('generic.ariaLabel.addKeyValue')"
|
|
896
|
+
@click="add()"
|
|
897
|
+
>
|
|
898
|
+
<i
|
|
899
|
+
class="mr-5 icon"
|
|
900
|
+
:class="loading ? ['icon-lg', 'icon-spinner','icon-spin']: [addIcon]"
|
|
901
|
+
/> {{ _addLabel }}
|
|
902
|
+
</RcButton>
|
|
882
903
|
<button
|
|
883
|
-
v-if="addAllowed"
|
|
904
|
+
v-else-if="addAllowed"
|
|
884
905
|
type="button"
|
|
885
906
|
role="button"
|
|
886
907
|
class="btn role-tertiary add"
|
|
@@ -117,6 +117,10 @@ export default {
|
|
|
117
117
|
type: String,
|
|
118
118
|
default: 'labelSelect.noOptions.empty'
|
|
119
119
|
},
|
|
120
|
+
lockedOptions: {
|
|
121
|
+
type: Array,
|
|
122
|
+
default: () => []
|
|
123
|
+
},
|
|
120
124
|
|
|
121
125
|
name: {
|
|
122
126
|
type: String,
|
|
@@ -382,7 +386,17 @@ export default {
|
|
|
382
386
|
}
|
|
383
387
|
|
|
384
388
|
return this.getOptionLabel(opt);
|
|
385
|
-
}
|
|
389
|
+
},
|
|
390
|
+
|
|
391
|
+
isOptionLocked(option) {
|
|
392
|
+
if (!this.lockedOptions.length) {
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const label = this.getOptionLabel(option);
|
|
397
|
+
|
|
398
|
+
return this.lockedOptions.includes(typeof label === 'string' ? label.trim() : String(label));
|
|
399
|
+
},
|
|
386
400
|
},
|
|
387
401
|
};
|
|
388
402
|
</script>
|
|
@@ -513,6 +527,14 @@ export default {
|
|
|
513
527
|
/>
|
|
514
528
|
</div>
|
|
515
529
|
</template>
|
|
530
|
+
<template
|
|
531
|
+
v-if="lockedOptions.length"
|
|
532
|
+
#selected-option="option"
|
|
533
|
+
>
|
|
534
|
+
<span :data-locked="isOptionLocked(option) || undefined">
|
|
535
|
+
{{ getOptionLabel(option) }}
|
|
536
|
+
</span>
|
|
537
|
+
</template>
|
|
516
538
|
<!-- Pass down templates provided by the caller -->
|
|
517
539
|
<template
|
|
518
540
|
v-for="(_, slot) of $slots"
|
|
@@ -676,6 +698,14 @@ export default {
|
|
|
676
698
|
min-height: unset !important;
|
|
677
699
|
padding: 0 0 0 7px !important;
|
|
678
700
|
|
|
701
|
+
&:has([data-locked]) {
|
|
702
|
+
padding: 0 7px 0 7px !important;
|
|
703
|
+
|
|
704
|
+
.vs__deselect {
|
|
705
|
+
display: none;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
679
709
|
> button {
|
|
680
710
|
height: 20px;
|
|
681
711
|
line-height: 14px;
|
|
@@ -691,6 +721,14 @@ export default {
|
|
|
691
721
|
}
|
|
692
722
|
}
|
|
693
723
|
}
|
|
724
|
+
|
|
725
|
+
:deep() .vs--disabled .vs__selected-options .vs__selected {
|
|
726
|
+
padding: 0 7px 0 7px !important;
|
|
727
|
+
|
|
728
|
+
.vs__deselect {
|
|
729
|
+
display: none;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
694
732
|
}
|
|
695
733
|
|
|
696
734
|
:deep() .vs__selected-options {
|
|
@@ -126,6 +126,16 @@ export default {
|
|
|
126
126
|
type: String,
|
|
127
127
|
default: '',
|
|
128
128
|
},
|
|
129
|
+
|
|
130
|
+
compact: {
|
|
131
|
+
type: Boolean,
|
|
132
|
+
default: false
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
useRcButton: {
|
|
136
|
+
type: Boolean,
|
|
137
|
+
default: false
|
|
138
|
+
}
|
|
129
139
|
},
|
|
130
140
|
|
|
131
141
|
data(): DataType {
|
|
@@ -162,9 +172,12 @@ export default {
|
|
|
162
172
|
<div :class="defaultSectionClass">
|
|
163
173
|
<div class="labels">
|
|
164
174
|
<div class="labels__header">
|
|
165
|
-
<
|
|
175
|
+
<component
|
|
176
|
+
:is="!compact ? 'h3' : 'h4'"
|
|
177
|
+
v-if="showLabelTitle"
|
|
178
|
+
>
|
|
166
179
|
<t k="labels.labels.title" />
|
|
167
|
-
</
|
|
180
|
+
</component>
|
|
168
181
|
<ToggleSwitch
|
|
169
182
|
v-if="showToggler"
|
|
170
183
|
v-model:value="toggler"
|
|
@@ -186,13 +199,14 @@ export default {
|
|
|
186
199
|
:read-allowed="false"
|
|
187
200
|
:value-can-be-empty="true"
|
|
188
201
|
:key-errors="labels.keyErrors"
|
|
202
|
+
:use-rc-button="useRcButton"
|
|
189
203
|
@update:value="labels.update($event, (x) => value.setLabels(x))"
|
|
190
204
|
/>
|
|
191
205
|
</slot>
|
|
192
206
|
</div>
|
|
193
207
|
</div>
|
|
194
208
|
</div>
|
|
195
|
-
<div class="spacer" />
|
|
209
|
+
<div :class="compact ? 'compact-spacer' : 'spacer'" />
|
|
196
210
|
<div
|
|
197
211
|
v-if="showAnnotations"
|
|
198
212
|
:class="sectionClass"
|
|
@@ -208,6 +222,7 @@ export default {
|
|
|
208
222
|
:read-allowed="false"
|
|
209
223
|
:value-can-be-empty="true"
|
|
210
224
|
:key-errors="annotations.keyErrors"
|
|
225
|
+
:use-rc-button="useRcButton"
|
|
211
226
|
@update:value="annotations.update($event, (x) => value.setAnnotations(x))"
|
|
212
227
|
/>
|
|
213
228
|
</div>
|
|
@@ -221,4 +236,8 @@ export default {
|
|
|
221
236
|
justify-content: space-between;
|
|
222
237
|
}
|
|
223
238
|
}
|
|
239
|
+
|
|
240
|
+
.compact-spacer {
|
|
241
|
+
height: 24px;
|
|
242
|
+
}
|
|
224
243
|
</style>
|
|
@@ -156,6 +156,14 @@ export default {
|
|
|
156
156
|
type: Boolean,
|
|
157
157
|
default: true,
|
|
158
158
|
},
|
|
159
|
+
nameColSpan: {
|
|
160
|
+
type: Number,
|
|
161
|
+
default: 3,
|
|
162
|
+
},
|
|
163
|
+
noBottomMargin: {
|
|
164
|
+
type: Boolean,
|
|
165
|
+
default: false,
|
|
166
|
+
},
|
|
159
167
|
rules: {
|
|
160
168
|
default: () => ({
|
|
161
169
|
namespace: [],
|
|
@@ -310,8 +318,8 @@ export default {
|
|
|
310
318
|
|
|
311
319
|
if (props.namespaced) {
|
|
312
320
|
if (props.forceNamespace) {
|
|
313
|
-
namespace.value =
|
|
314
|
-
updateNamespace(namespace);
|
|
321
|
+
namespace.value = props.forceNamespace;
|
|
322
|
+
updateNamespace(namespace.value);
|
|
315
323
|
} else if (props.namespaceKey) {
|
|
316
324
|
namespace.value = get(v.value, props.namespaceKey);
|
|
317
325
|
} else {
|
|
@@ -321,7 +329,7 @@ export default {
|
|
|
321
329
|
if (!namespace.value && !props.noDefaultNamespace) {
|
|
322
330
|
namespace.value = store.getters['defaultNamespace'];
|
|
323
331
|
if (metadata) {
|
|
324
|
-
metadata.namespace = namespace;
|
|
332
|
+
metadata.namespace = namespace.value;
|
|
325
333
|
}
|
|
326
334
|
}
|
|
327
335
|
}
|
|
@@ -436,7 +444,7 @@ export default {
|
|
|
436
444
|
</script>
|
|
437
445
|
|
|
438
446
|
<template>
|
|
439
|
-
<div class="row mb-20">
|
|
447
|
+
<div :class="['row', { 'mb-20': !noBottomMargin }]">
|
|
440
448
|
<slot name="project-selector" />
|
|
441
449
|
<div
|
|
442
450
|
v-if="namespaced && !nameNsHidden && createNamespace"
|
|
@@ -491,7 +499,7 @@ export default {
|
|
|
491
499
|
<div
|
|
492
500
|
v-if="!nameHidden && !nameNsHidden"
|
|
493
501
|
:data-testid="componentTestid + '-name'"
|
|
494
|
-
class="col span
|
|
502
|
+
:class="['col', `span-${nameColSpan}`]"
|
|
495
503
|
>
|
|
496
504
|
<LabeledInput
|
|
497
505
|
ref="nameInput"
|
|
@@ -170,6 +170,81 @@ describe('component: NameNsDescription', () => {
|
|
|
170
170
|
expect(nameInput.element.value).toBe('Default');
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
+
it('should set namespace to a plain string when forceNamespace prop is provided', () => {
|
|
174
|
+
const forcedNs = 'cert-manager';
|
|
175
|
+
const store = createStore({
|
|
176
|
+
getters: {
|
|
177
|
+
allowedNamespaces: () => () => ({}),
|
|
178
|
+
currentStore: () => () => 'cluster',
|
|
179
|
+
'cluster/schemaFor': () => jest.fn()
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const wrapper = mount(NameNsDescription, {
|
|
184
|
+
props: {
|
|
185
|
+
value: {
|
|
186
|
+
setAnnotation: jest.fn(),
|
|
187
|
+
metadata: { namespace: '' }
|
|
188
|
+
},
|
|
189
|
+
mode: 'create',
|
|
190
|
+
forceNamespace: forcedNs,
|
|
191
|
+
},
|
|
192
|
+
global: {
|
|
193
|
+
provide: { store },
|
|
194
|
+
mocks: {
|
|
195
|
+
$store: {
|
|
196
|
+
dispatch: jest.fn(),
|
|
197
|
+
getters: {
|
|
198
|
+
namespaces: jest.fn(),
|
|
199
|
+
'i18n/t': jest.fn(),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(typeof (wrapper.vm as any).namespace).toBe('string');
|
|
207
|
+
expect((wrapper.vm as any).namespace).toBe(forcedNs);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should set metadata.namespace to a plain string when falling back to defaultNamespace', () => {
|
|
211
|
+
const defaultNs = 'default';
|
|
212
|
+
const metadata: Record<string, unknown> = {};
|
|
213
|
+
const store = createStore({
|
|
214
|
+
getters: {
|
|
215
|
+
allowedNamespaces: () => () => ({}),
|
|
216
|
+
currentStore: () => () => 'cluster',
|
|
217
|
+
'cluster/schemaFor': () => jest.fn(),
|
|
218
|
+
defaultNamespace: () => defaultNs,
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
mount(NameNsDescription, {
|
|
223
|
+
props: {
|
|
224
|
+
value: {
|
|
225
|
+
setAnnotation: jest.fn(),
|
|
226
|
+
metadata,
|
|
227
|
+
},
|
|
228
|
+
mode: 'create',
|
|
229
|
+
},
|
|
230
|
+
global: {
|
|
231
|
+
provide: { store },
|
|
232
|
+
mocks: {
|
|
233
|
+
$store: {
|
|
234
|
+
dispatch: jest.fn(),
|
|
235
|
+
getters: {
|
|
236
|
+
namespaces: jest.fn(),
|
|
237
|
+
'i18n/t': jest.fn(),
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
expect(typeof metadata.namespace).toBe('string');
|
|
245
|
+
expect(metadata.namespace).toBe(defaultNs);
|
|
246
|
+
});
|
|
247
|
+
|
|
173
248
|
it('sets the name using the nameKey prop', () => {
|
|
174
249
|
const namespaceName = 'test';
|
|
175
250
|
const store = createStore({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script>
|
|
2
|
-
import
|
|
2
|
+
import ipaddr from 'ipaddr.js';
|
|
3
3
|
import CopyToClipboard from '@shell/components/CopyToClipboard';
|
|
4
4
|
import { mapGetters } from 'vuex';
|
|
5
5
|
import RcStatusBadge from '@components/Pill/RcStatusBadge/RcStatusBadge';
|
|
@@ -15,10 +15,16 @@ export default {
|
|
|
15
15
|
computed: {
|
|
16
16
|
...mapGetters({ t: 'i18n/t' }),
|
|
17
17
|
filteredExternalIps() {
|
|
18
|
-
|
|
18
|
+
const ips = this.row.externalIps ||
|
|
19
|
+
(this.row.externalIp && this.row.externalIp !== this.t('generic.none') ? [this.row.externalIp] : []);
|
|
20
|
+
|
|
21
|
+
return ips.filter((ip) => this.isIp(ip));
|
|
19
22
|
},
|
|
20
23
|
filteredInternalIps() {
|
|
21
|
-
|
|
24
|
+
const ips = this.row.internalIps ||
|
|
25
|
+
(this.row.internalIp ? [this.row.internalIp] : []);
|
|
26
|
+
|
|
27
|
+
return ips.filter((ip) => this.isIp(ip));
|
|
22
28
|
},
|
|
23
29
|
internalSameAsExternal() {
|
|
24
30
|
return this.externalIp && this.internalIp && this.externalIp === this.internalIp;
|
|
@@ -59,7 +65,7 @@ export default {
|
|
|
59
65
|
},
|
|
60
66
|
methods: {
|
|
61
67
|
isIp(ip) {
|
|
62
|
-
return ip && (
|
|
68
|
+
return ip && (ipaddr.IPv4.isValidFourPartDecimal(ip) || ipaddr.IPv6.isValid(ip));
|
|
63
69
|
}
|
|
64
70
|
}
|
|
65
71
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import isEmpty from 'lodash/isEmpty';
|
|
3
|
-
import { CATTLE_PUBLIC_ENDPOINTS } from '@shell/config/labels-annotations';
|
|
3
|
+
import { CATTLE_PUBLIC_ENDPOINTS, SERVICE_LINKS } from '@shell/config/labels-annotations';
|
|
4
4
|
import Endpoints from '@shell/components/formatter/Endpoints';
|
|
5
5
|
import has from 'lodash/has';
|
|
6
6
|
import { isMaybeSecure } from '@shell/utils/url';
|
|
@@ -52,6 +52,23 @@ export default {
|
|
|
52
52
|
return annotations[CATTLE_PUBLIC_ENDPOINTS];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const serviceLinksAnnotation = annotations[SERVICE_LINKS];
|
|
56
|
+
const serviceLinksMap = new Map();
|
|
57
|
+
|
|
58
|
+
if (serviceLinksAnnotation) {
|
|
59
|
+
serviceLinksAnnotation.split(',').forEach((entry) => {
|
|
60
|
+
const [portStr, scheme] = entry.trim().split('/');
|
|
61
|
+
const port = Number(portStr);
|
|
62
|
+
|
|
63
|
+
const validSchemes = ['http', 'https'];
|
|
64
|
+
const normalizedScheme = scheme?.toLowerCase();
|
|
65
|
+
|
|
66
|
+
if (port > 0 && !isNaN(port)) {
|
|
67
|
+
serviceLinksMap.set(port, validSchemes.includes(normalizedScheme) ? normalizedScheme : null);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
55
72
|
// <CLUSTER_IP>:<PORT>/<PROTOCOL> > <TARGET PORT>
|
|
56
73
|
if (isEmpty(ports)) {
|
|
57
74
|
if (!isEmpty(parsedClusterIp)) {
|
|
@@ -70,12 +87,14 @@ export default {
|
|
|
70
87
|
|
|
71
88
|
const stringPort = p.port.toString();
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
const isDefaultPort = stringPort.endsWith('80') || stringPort.endsWith('443');
|
|
91
|
+
const hasServiceLink = serviceLinksMap.has(p.port);
|
|
92
|
+
|
|
93
|
+
if (p?.protocol === 'TCP' && (isDefaultPort || hasServiceLink)) {
|
|
94
|
+
const explicitScheme = hasServiceLink ? serviceLinksMap.get(p.port) : null;
|
|
95
|
+
const scheme = explicitScheme || (isMaybeSecure(p.port, p?.protocol) ? 'https' : 'http');
|
|
96
|
+
|
|
97
|
+
proxyUrl = row.proxyUrl(scheme, p.port);
|
|
79
98
|
}
|
|
80
99
|
|
|
81
100
|
const clusterIpAndPort = proxyUrl ? `<a href="${ proxyUrl }" target="_blank" rel="noopener noreferrer nofollow">${ p?.name ? p.name : `${ parsedClusterIp }${ p.port }` }</a>` : `${ parsedClusterIp }${ p.port }`;
|