@rancher/shell 3.0.12-rc.1 → 3.0.12-rc.3
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/apis/impl/apis.ts +6 -0
- package/apis/index.ts +26 -0
- package/apis/intf/resources-api/cluster-api.ts +18 -0
- package/apis/intf/resources-api/mgmt-api.ts +15 -0
- package/apis/intf/resources-api/resource-base.ts +107 -0
- package/apis/intf/resources-api/resource-constants.ts +147 -0
- package/apis/intf/resources-api/resources-api.ts +143 -0
- package/apis/intf/resources.ts +49 -0
- package/apis/intf/{modal.ts → shell-api/modal.ts} +21 -26
- package/apis/intf/shell-api/proxy.ts +216 -0
- package/apis/intf/{slide-in.ts → shell-api/slide-in.ts} +4 -3
- package/apis/intf/{system.ts → shell-api/system.ts} +4 -1
- package/apis/intf/shell.ts +12 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +550 -0
- package/apis/resources/index.ts +22 -0
- package/apis/resources/resources-api-class.ts +187 -0
- package/apis/shell/__tests__/proxy.test.ts +369 -0
- package/apis/shell/index.ts +8 -1
- package/apis/shell/modal.ts +4 -1
- package/apis/shell/notifications.ts +9 -6
- package/apis/shell/proxy.ts +256 -0
- package/apis/shell/slide-in.ts +4 -1
- package/apis/vue-shim.d.ts +2 -1
- package/assets/data/aws-regions.json +4 -0
- package/assets/fonts/lato/LatoLatin-Black.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Black.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BlackItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Bold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-BoldItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Heavy.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-HeavyItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Italic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Light.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-LightItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Medium.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-MediumItalic.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Regular.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff +0 -0
- package/assets/fonts/lato/LatoLatin-Semibold.woff2 +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff +0 -0
- package/assets/fonts/lato/LatoLatin-SemiboldItalic.woff2 +0 -0
- package/assets/images/providers/entraid-black.svg +4 -0
- package/assets/images/providers/entraid.svg +9 -0
- package/assets/images/vendor/entraid.svg +9 -0
- package/assets/styles/app.scss +0 -1
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/fonts/_fontstack.scss +132 -8
- package/assets/translations/en-us.yaml +41 -22
- package/assets/translations/zh-hans.yaml +4 -8
- package/chart/__tests__/S3.test.ts +10 -3
- package/chart/monitoring/index.vue +10 -1
- package/components/ActionDropdownShell.vue +2 -1
- package/components/CountBox.vue +20 -0
- package/components/CreateDriver.vue +0 -12
- package/components/CruResourceFooter.vue +9 -5
- package/components/DetailText.vue +12 -3
- package/components/ExplorerProjectsNamespaces.vue +1 -1
- package/components/InstallHelmCharts.vue +2 -2
- package/components/LandingPagePreference.vue +14 -5
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +15 -1
- package/components/Resource/Detail/Metadata/index.vue +6 -0
- package/components/Resource/Detail/ResourcePopover/index.vue +12 -1
- package/components/Resource/Detail/SpacedRow.vue +3 -1
- package/components/Resource/Detail/TitleBar/index.vue +10 -11
- package/components/ResourceList/Masthead.vue +12 -8
- package/components/SelectIconGrid.vue +5 -10
- package/components/SingleClusterInfo.vue +1 -0
- package/components/SortableTable/__tests__/sorting.test.ts +126 -0
- package/components/SortableTable/index.vue +6 -9
- package/components/SortableTable/selection.js +23 -5
- package/components/SortableTable/sorting.js +6 -3
- package/components/Wizard.vue +14 -13
- package/components/__tests__/CountBox.test.ts +72 -0
- package/components/__tests__/DetailText.test.ts +113 -0
- package/components/fleet/FleetBundles.vue +100 -12
- package/components/fleet/FleetClusterTargets/index.vue +54 -15
- package/components/fleet/__tests__/FleetClusterTargets.test.ts +149 -115
- package/components/fleet/__tests__/FleetClusters.test.ts +12 -12
- package/components/form/InputWithSelect.vue +18 -10
- package/components/form/KeyValue.vue +17 -1
- package/components/form/LabeledSelect.vue +101 -26
- package/components/form/NameNsDescription.vue +11 -0
- package/components/form/Security.vue +6 -2
- package/components/form/Select.vue +73 -56
- package/components/form/ServiceNameSelect.vue +13 -11
- package/components/form/WorkloadPorts.vue +2 -7
- package/components/form/__tests__/KeyValue.test.ts +66 -0
- package/components/form/__tests__/NodeScheduling.test.ts +9 -0
- package/components/form/__tests__/Security.test.ts +76 -0
- package/components/form/labeled-select-utils/useLabeledSelectPagination.ts +138 -0
- package/components/formatter/Autoscaler.vue +4 -4
- package/components/formatter/ClusterKubeVersion.vue +27 -0
- package/components/formatter/ClusterLink.vue +1 -7
- package/components/formatter/ClusterProvider.vue +6 -10
- package/components/formatter/FleetSummaryGraph.vue +0 -3
- package/components/formatter/MachineSummaryGraph.vue +1 -1
- package/components/formatter/PodsUsage.vue +2 -2
- package/components/formatter/__tests__/Autoscaler.test.ts +19 -22
- package/components/formatter/__tests__/FleetSummaryGraph.test.ts +216 -0
- package/components/formatter/__tests__/PodsUsage.test.ts +6 -10
- package/components/nav/Group.vue +7 -6
- package/components/nav/Header.vue +24 -3
- package/components/nav/NamespaceFilter.vue +2 -2
- package/components/nav/NotificationCenter/Notification.vue +4 -1
- package/components/nav/NotificationCenter/NotificationHeader.vue +20 -8
- package/components/nav/NotificationCenter/__tests__/NotificationHeader.test.ts +80 -0
- package/components/nav/TopLevelMenu.helper.ts +15 -3
- package/components/nav/TopLevelMenu.vue +16 -5
- package/components/nav/Type.vue +8 -7
- package/components/nav/WindowManager/index.vue +2 -1
- package/components/nav/WorkspaceSwitcher.vue +13 -0
- package/components/nav/__tests__/Group.test.ts +67 -0
- package/components/nav/__tests__/Header.test.ts +235 -0
- package/components/nav/__tests__/TopLevelMenu.test.ts +145 -21
- package/components/nav/__tests__/Type.test.ts +20 -3
- package/components/templates/default.vue +34 -4
- package/components/templates/home.vue +30 -25
- package/components/templates/plain.vue +31 -26
- package/components/templates/standalone.vue +17 -0
- package/composables/useFormValidation.ts +93 -0
- package/composables/useLabeledFormElement.ts +10 -2
- package/composables/useLabeledSelect.ts +60 -0
- package/composables/useUserRetentionValidation.ts +1 -49
- package/composables/useVeeValidateField.test.ts +159 -0
- package/composables/useVeeValidateField.ts +67 -0
- package/config/cookies.js +0 -1
- package/config/labels-annotations.js +1 -0
- package/config/pagination-table-headers.js +18 -1
- package/config/product/manager.js +82 -21
- package/config/query-params.js +1 -0
- package/config/router/routes.js +6 -8
- package/config/table-headers.js +20 -1
- package/config/types.js +2 -1
- package/core/__tests__/plugin-products.test.ts +1505 -30
- package/core/plugin-products-base.ts +137 -20
- package/core/plugin-products-helpers.ts +5 -4
- package/core/plugin-products.ts +4 -0
- package/core/plugin-types.ts +129 -4
- package/core/plugin.ts +15 -7
- package/core/productDebugger.js +9 -4
- package/core/types-provisioning.ts +43 -30
- package/core/types.ts +58 -19
- package/detail/__tests__/management.cattle.io.fleetworkspace.test.ts +128 -0
- package/detail/__tests__/pod.test.ts +41 -0
- package/detail/harvesterhci.io.management.cluster.vue +6 -2
- package/detail/management.cattle.io.fleetworkspace.vue +49 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +4 -10
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +9 -0
- package/edit/__tests__/kontainerDriver.test.ts +0 -13
- package/edit/__tests__/nodeDriver.test.ts +5 -11
- package/edit/__tests__/resources.cattle.io.restore.test.ts +9 -0
- package/edit/auditlog.cattle.io.auditpolicy/__tests__/__snapshots__/General.test.ts.snap +6 -0
- package/edit/auth/__tests__/azuread.test.ts +217 -34
- package/edit/auth/__tests__/oidc.test.ts +54 -0
- package/edit/auth/azuread.vue +123 -15
- package/edit/auth/oidc.vue +10 -2
- package/edit/kontainerDriver.vue +1 -2
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +13 -4
- package/edit/networking.k8s.io.ingress/RulePath.vue +8 -4
- package/edit/networking.k8s.io.ingress/index.vue +75 -20
- package/edit/nodeDriver.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentEnv.vue +1 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/AgentEnv.test.ts +25 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/MachinePool.test.ts +104 -0
- package/edit/provisioning.cattle.io.cluster/index.vue +81 -106
- package/edit/provisioning.cattle.io.cluster/rke2.vue +8 -4
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +11 -0
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +37 -4
- package/edit/provisioning.cattle.io.cluster/tabs/registries/__tests__/RegistryConfigs.test.ts +132 -7
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +2 -1
- package/edit/secret/__tests__/ssh.test.ts +5 -6
- package/edit/secret/basic.vue +31 -0
- package/edit/secret/index.vue +68 -17
- package/edit/secret/registry.vue +38 -0
- package/edit/secret/ssh.vue +29 -0
- package/edit/secret/tls.vue +30 -0
- package/edit/service.vue +4 -4
- package/edit/workload/Upgrading.vue +3 -3
- package/edit/workload/__tests__/Upgrading.test.ts +6 -9
- package/edit/workload/mixins/workload.js +2 -1
- package/initialize/App.vue +29 -2
- package/initialize/install-plugins.js +0 -2
- package/list/__tests__/management.cattle.io.feature.test.ts +105 -0
- package/list/catalog.cattle.io.app.vue +25 -5
- package/list/fleet.cattle.io.bundle.vue +7 -104
- package/list/fleet.cattle.io.clusterregistrationtoken.vue +20 -0
- package/list/management.cattle.io.feature.vue +1 -1
- package/list/management.cattle.io.fleetworkspace.vue +8 -0
- package/list/provisioning.cattle.io.cluster.vue +262 -180
- package/list/utils/management.cattle.io.cluster.utils.ts +128 -0
- package/machine-config/amazonec2.vue +1 -0
- package/mixins/__tests__/chart.test.ts +112 -0
- package/mixins/brand.js +2 -1
- package/mixins/chart.js +50 -15
- package/mixins/resource-fetch-api-pagination.js +41 -5
- package/models/__tests__/catalog.cattle.io.app.test.ts +15 -1
- package/models/__tests__/catalog.cattle.io.clusterrepo.test.ts +84 -0
- package/models/__tests__/chart.test.ts +99 -6
- package/models/__tests__/ext.cattle.io.kubeconfig.test.ts +67 -67
- package/models/__tests__/management.cattle.io.cluster.test.ts +1 -1
- package/models/__tests__/management.cattle.io.feature.test.ts +131 -0
- package/models/__tests__/management.cattle.io.node.ts +6 -5
- package/models/__tests__/management.cattle.io.nodepool.ts +5 -4
- package/models/__tests__/monitoring.coreos.com.alertmanagerconfig.test.ts +98 -0
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +32 -11
- package/models/base-cluster.x-k8s.io.js +26 -0
- package/models/catalog.cattle.io.app.js +21 -17
- package/models/catalog.cattle.io.clusterrepo.js +39 -11
- package/models/chart.js +33 -19
- package/models/cluster.js +1 -1
- package/models/cluster.x-k8s.io.machine.js +4 -22
- package/models/cluster.x-k8s.io.machinedeployment.js +2 -20
- package/models/cluster.x-k8s.io.machineset.js +2 -20
- package/models/compliance.cattle.io.clusterscan.js +130 -2
- package/models/ext.cattle.io.kubeconfig.ts +4 -7
- package/models/fleet-application.js +4 -2
- package/models/fleet.cattle.io.bundle.js +1 -1
- package/models/kontainerdriver.js +11 -0
- package/models/management.cattle.io.authconfig.js +5 -1
- package/models/management.cattle.io.cluster.js +402 -78
- package/models/management.cattle.io.feature.js +3 -3
- package/models/management.cattle.io.kontainerdriver.js +1 -26
- package/models/management.cattle.io.node.js +6 -4
- package/models/management.cattle.io.nodepool.js +1 -1
- package/models/monitoring.coreos.com.alertmanagerconfig.js +31 -17
- package/models/networking.k8s.io.ingress.js +12 -4
- package/models/nodedriver.js +7 -0
- package/models/provisioning.cattle.io.cluster.js +47 -330
- package/models/rke.cattle.io.etcdsnapshot.js +1 -2
- package/package.json +20 -37
- package/pages/__tests__/readme.test.ts +49 -0
- package/pages/auth/setup.vue +2 -3
- package/pages/c/_cluster/apps/charts/__tests__/chart.test.ts +265 -0
- package/pages/c/_cluster/apps/charts/__tests__/index.test.ts +55 -0
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +53 -0
- package/pages/c/_cluster/apps/charts/chart.vue +275 -39
- package/pages/c/_cluster/apps/charts/index.vue +2 -2
- package/pages/c/_cluster/apps/charts/install.vue +18 -10
- package/pages/c/_cluster/auth/user.retention/index.vue +55 -22
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +23 -25
- package/pages/c/_cluster/explorer/index.vue +5 -49
- package/pages/c/_cluster/istio/__tests__/istio.index.test.ts +194 -0
- package/pages/c/_cluster/istio/index.vue +21 -6
- package/pages/c/_cluster/manager/drivers/kontainerDriver/index.vue +5 -7
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +40 -2
- package/pages/c/_cluster/uiplugins/__tests__/PluginInfoPanel.test.ts +61 -0
- package/pages/c/_cluster/uiplugins/__tests__/index.test.ts +735 -13
- package/pages/c/_cluster/uiplugins/index.vue +226 -222
- package/pages/diagnostic.vue +13 -17
- package/pages/fail-whale.vue +18 -0
- package/pages/home.vue +77 -260
- package/pages/readme.vue +88 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +88 -0
- package/plugins/dashboard-store/actions.js +40 -18
- package/plugins/dashboard-store/resource-class.js +5 -2
- package/plugins/steve/__tests__/subscribe.spec.ts +6 -3
- package/plugins/steve/steve-pagination-utils.ts +11 -3
- package/plugins/steve/subscribe.js +35 -5
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +211 -1
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +37 -4
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +1 -1
- package/rancher-components/RcButton/RcButton.test.ts +37 -1
- package/rancher-components/RcButton/RcButton.vue +38 -8
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +10 -8
- package/scripts/test-plugins-build.sh +5 -2
- package/server/server-middleware.js +2 -2
- package/static/humans.txt +1 -0
- package/static/robots.txt +34 -0
- package/static/welcome-cow.svg +18 -0
- package/store/__tests__/catalog.test.ts +276 -12
- package/store/__tests__/type-map.test.ts +556 -1
- package/store/action-menu.js +8 -3
- package/store/auth.js +1 -4
- package/store/aws.js +27 -16
- package/store/catalog.js +87 -11
- package/store/digitalocean.js +20 -38
- package/store/index.js +2 -0
- package/store/linode.js +25 -40
- package/store/pnap.js +1 -0
- package/store/type-map.js +111 -29
- package/tsconfig.paths.json +8 -8
- package/types/kube/kube-api.ts +14 -1
- package/types/rancher/steve.api.ts +12 -12
- package/types/resources/settings.d.ts +2 -1
- package/types/shell/index.d.ts +128 -24
- package/types/store/dashboard-store.types.ts +108 -11
- package/types/store/pagination.types.ts +6 -3
- package/utils/__tests__/alertmanagerconfig.test.ts +117 -0
- package/utils/__tests__/async.test.ts +87 -0
- package/utils/__tests__/aws.test.ts +140 -0
- package/utils/__tests__/banners.test.ts +176 -0
- package/utils/__tests__/chart.test.ts +64 -1
- package/utils/__tests__/color.test.ts +226 -0
- package/utils/__tests__/duration.test.ts +140 -0
- package/utils/__tests__/fleet.test.ts +340 -0
- package/utils/__tests__/git.test.ts +270 -0
- package/utils/__tests__/inactivity.test.ts +316 -0
- package/utils/__tests__/ingress.test.ts +553 -0
- package/utils/__tests__/kube.test.ts +68 -0
- package/utils/__tests__/namespace-filter.test.ts +109 -0
- package/utils/__tests__/object.test.ts +77 -0
- package/utils/__tests__/pagination-utils.test.ts +361 -0
- package/utils/__tests__/parse-externalid.test.ts +137 -0
- package/utils/__tests__/perf-setting.utils.test.ts +98 -0
- package/utils/__tests__/poller-sequential.test.ts +177 -0
- package/utils/__tests__/poller.test.ts +170 -0
- package/utils/__tests__/promise.test.ts +346 -0
- package/utils/__tests__/settings.test.ts +140 -0
- package/utils/__tests__/sort-utils.test.ts +301 -0
- package/utils/__tests__/string-utils.test.ts +798 -0
- package/utils/__tests__/string.test.ts +23 -1
- package/utils/__tests__/style.test.ts +154 -0
- package/utils/__tests__/svg-filter.test.ts +184 -0
- package/utils/__tests__/time.test.ts +14 -1
- package/utils/__tests__/units.test.ts +417 -0
- package/utils/__tests__/url.test.ts +246 -0
- package/utils/__tests__/versions.test.ts +128 -0
- package/utils/__tests__/xccdf.test.ts +391 -0
- package/utils/chart.js +36 -0
- package/utils/fleet.ts +13 -3
- package/utils/gatekeeper/__tests__/util.test.ts +174 -0
- package/utils/gc/__tests__/gc-interval.test.ts +119 -0
- package/utils/gc/__tests__/gc-root-store.test.ts +225 -0
- package/utils/gc/__tests__/gc-route-changed.test.ts +96 -0
- package/utils/gc/__tests__/gc.test.ts +487 -0
- package/utils/ingress.ts +9 -1
- package/utils/object.js +33 -2
- package/utils/pagination-utils.ts +2 -1
- package/utils/string.js +25 -2
- package/utils/time.ts +5 -0
- package/utils/uiplugins.ts +5 -5
- package/utils/validators/__tests__/cluster-name.test.ts +110 -0
- package/utils/validators/__tests__/cron-schedule.test.ts +79 -0
- package/utils/validators/__tests__/index.test.ts +481 -0
- package/utils/validators/__tests__/kubernetes-name.test.ts +163 -0
- package/utils/validators/__tests__/misc-validators.test.ts +246 -0
- package/utils/validators/__tests__/pod-affinity.test.ts +382 -0
- package/utils/validators/__tests__/prometheusrule.test.ts +211 -0
- package/utils/validators/__tests__/role-template.test.ts +149 -0
- package/utils/validators/__tests__/service.test.ts +283 -0
- package/utils/validators/__tests__/setting.test.js +32 -0
- package/utils/validators/formRules/__tests__/index.test.ts +50 -0
- package/utils/validators/formRules/index.ts +5 -5
- package/utils/validators/machine-pool.ts +1 -1
- package/utils/validators/setting.js +18 -3
- package/utils/xccdf.ts +418 -0
- package/vue.config.js +0 -9
- package/assets/fonts/lato/lato-v17-latin-700.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-700.woff2 +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff +0 -0
- package/assets/fonts/lato/lato-v17-latin-regular.woff2 +0 -0
- package/assets/images/providers/azuread-black.svg +0 -22
- package/assets/images/providers/azuread.svg +0 -25
- package/assets/images/vendor/azuread.svg +0 -18
- package/assets/styles/fonts/_dots.scss +0 -18
- package/components/EmberPage.vue +0 -622
- package/components/EmberPageView.vue +0 -39
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +0 -116
- package/mixins/labeled-form-element.ts +0 -225
- package/pages/c/_cluster/explorer/tools/pages/_page.vue +0 -28
- package/pages/c/_cluster/manager/pages/_page.vue +0 -22
- package/pages/c/_cluster/mcapps/pages/_page.vue +0 -22
- package/plugins/ember-cookie.js +0 -17
- package/utils/ember-page.js +0 -30
|
@@ -660,12 +660,8 @@ export default {
|
|
|
660
660
|
return getters.all(type);
|
|
661
661
|
},
|
|
662
662
|
|
|
663
|
-
// opt:
|
|
664
|
-
//
|
|
665
|
-
// limit: Number of records to return per page (default: 1000)
|
|
666
|
-
// sortBy: Sort by field
|
|
667
|
-
// sortOrder: asc or desc
|
|
668
|
-
// url: Use this specific URL instead of looking up the URL for the type/id. This should only be used for bootstrapping schemas on startup.
|
|
663
|
+
// opt: @ActionFindArgs
|
|
664
|
+
// @returns @ActionFindResponse
|
|
669
665
|
// @TODO depaginate: If the response is paginated, retrieve all the pages. (default: true)
|
|
670
666
|
async find(ctx, { type, id, opt }) {
|
|
671
667
|
if (!id) {
|
|
@@ -697,11 +693,23 @@ export default {
|
|
|
697
693
|
}
|
|
698
694
|
}
|
|
699
695
|
|
|
696
|
+
const havePage = getters.havePage(type);
|
|
697
|
+
|
|
700
698
|
opt = opt || {};
|
|
701
699
|
opt.url = getters.urlFor(type, id, opt);
|
|
702
700
|
|
|
703
701
|
const res = await dispatch('request', { opt, type });
|
|
704
702
|
|
|
703
|
+
if (!havePage && getters.havePage(type)) {
|
|
704
|
+
// There may be a super edge case where list --> detail (whilst loading) --> list navigation causes the list's rows to disappear
|
|
705
|
+
// Somehow the `findPage` from the list page returns before the `find`. The `find` then clears the page state in the cache.
|
|
706
|
+
// If this has happened silently return (we don't care about result)
|
|
707
|
+
// https://github.com/rancher/dashboard/issues/17524
|
|
708
|
+
console.warn(`Prevented \`find\` action from polluting cache for type "${ type }" (currently represents a page).`); // eslint-disable-line no-console
|
|
709
|
+
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
705
713
|
if (!opt.transient) {
|
|
706
714
|
await dispatch('load', { data: res, invalidatePageCache: opt.invalidatePageCache });
|
|
707
715
|
}
|
|
@@ -830,19 +838,33 @@ export default {
|
|
|
830
838
|
/**
|
|
831
839
|
* Remove all cached entries for a resource and stop watches
|
|
832
840
|
*/
|
|
833
|
-
forgetType({ commit, dispatch, state },
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
}
|
|
841
|
+
forgetType({ commit, dispatch, state }, payload) {
|
|
842
|
+
let type = payload;
|
|
843
|
+
let config = {};
|
|
844
|
+
|
|
845
|
+
if ( typeof payload === 'object' && payload !== null && payload.type ) {
|
|
846
|
+
type = payload.type;
|
|
847
|
+
config = payload;
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
const { compareWatches, unwatch = true, forget = true } = config;
|
|
843
851
|
|
|
844
|
-
|
|
845
|
-
|
|
852
|
+
if (unwatch) {
|
|
853
|
+
// Stop all known watches
|
|
854
|
+
state.started
|
|
855
|
+
.filter((entry) => compareWatches ? compareWatches(entry) : entry.type === type)
|
|
856
|
+
.forEach((entry) => dispatch('unwatch', entry));
|
|
857
|
+
|
|
858
|
+
// Stop all known back-off watch processes for this type
|
|
859
|
+
dispatch('resetWatchBackOff', {
|
|
860
|
+
type, compareWatches, resetStarted: false
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (forget) {
|
|
865
|
+
// Remove entries from store
|
|
866
|
+
commit('forgetType', type);
|
|
867
|
+
}
|
|
846
868
|
},
|
|
847
869
|
|
|
848
870
|
promptRemove({ commit, state }, resources ) {
|
|
@@ -946,12 +946,15 @@ export default class Resource {
|
|
|
946
946
|
// where mostly likely extension CRD model is extending from resource-class
|
|
947
947
|
const isResourceDetailDrawerCompatibleWithRancherSystem = semver.satisfies(parsedRancherVersion, '>= 2.13.0');
|
|
948
948
|
|
|
949
|
+
// If the resource can't show an edit or a yaml we don't want to show the configuration drawer
|
|
950
|
+
const showConfigEnabled = isResourceDetailDrawerCompatibleWithRancherSystem && this.disableResourceDetailDrawer !== true && (this.canCustomEdit || this.canYaml);
|
|
951
|
+
|
|
949
952
|
const all = [
|
|
950
953
|
{
|
|
951
954
|
action: 'showConfiguration',
|
|
952
955
|
label: this.t('action.showConfiguration'),
|
|
953
956
|
icon: 'icon icon-document',
|
|
954
|
-
enabled:
|
|
957
|
+
enabled: showConfigEnabled,
|
|
955
958
|
},
|
|
956
959
|
{ divider: true },
|
|
957
960
|
{
|
|
@@ -964,7 +967,7 @@ export default class Resource {
|
|
|
964
967
|
action: this.canEditYaml ? 'goToEditYaml' : 'goToViewYaml',
|
|
965
968
|
label: this.t(this.canEditYaml ? 'action.editYaml' : 'action.viewYaml'),
|
|
966
969
|
icon: 'icon icon-file',
|
|
967
|
-
enabled: this.canYaml,
|
|
970
|
+
enabled: this.canYaml && (this.canEditYaml || !showConfigEnabled), // Hide "View YAML" when "Show Configuration" is available since it already includes YAML viewing
|
|
968
971
|
},
|
|
969
972
|
{
|
|
970
973
|
action: (this.canCustomEdit ? 'goToClone' : 'cloneYaml'),
|
|
@@ -14,7 +14,8 @@ describe('steve: subscribe', () => {
|
|
|
14
14
|
schemaFor: () => null,
|
|
15
15
|
inError: () => false,
|
|
16
16
|
watchStarted: () => false,
|
|
17
|
-
listenerManager: state.listenerManager
|
|
17
|
+
listenerManager: state.listenerManager,
|
|
18
|
+
typeRegistered: () => true,
|
|
18
19
|
};
|
|
19
20
|
const rootGetters = {
|
|
20
21
|
'type-map/isSpoofed': () => false,
|
|
@@ -343,7 +344,7 @@ describe('steve: subscribe', () => {
|
|
|
343
344
|
|
|
344
345
|
// call watch
|
|
345
346
|
actions.watch({
|
|
346
|
-
state, dispatch, getters, rootGetters
|
|
347
|
+
state, dispatch, getters, rootGetters, commit
|
|
347
348
|
}, {
|
|
348
349
|
...obj,
|
|
349
350
|
revision,
|
|
@@ -488,6 +489,7 @@ describe('steve: subscribe', () => {
|
|
|
488
489
|
const state = {
|
|
489
490
|
started: [],
|
|
490
491
|
inError: {},
|
|
492
|
+
queue: [],
|
|
491
493
|
listenerManager: new SteveWatchEventListenerManager()
|
|
492
494
|
};
|
|
493
495
|
const _getters = {
|
|
@@ -498,7 +500,8 @@ describe('steve: subscribe', () => {
|
|
|
498
500
|
watchStarted: (...args) => getters.watchStarted(state)(...args),
|
|
499
501
|
backOffId: (...args) => getters.backOffId()(...args),
|
|
500
502
|
canBackoff: () => true,
|
|
501
|
-
listenerManager: state.listenerManager
|
|
503
|
+
listenerManager: state.listenerManager,
|
|
504
|
+
typeRegistered: () => true,
|
|
502
505
|
};
|
|
503
506
|
const commit = (type, ...args) => mutations[type](state, ...args);
|
|
504
507
|
|
|
@@ -258,6 +258,11 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
258
258
|
{ field: 'spec.displayName' },
|
|
259
259
|
{ field: `status.provider` },
|
|
260
260
|
{ field: `status.connected` },
|
|
261
|
+
{ field: `status.info.machineProvider` },
|
|
262
|
+
{ field: `status.driver` },
|
|
263
|
+
{ field: `status.provider` },
|
|
264
|
+
{ field: `status.info.kubernetesVersion` },
|
|
265
|
+
{ field: `spec.fleetWorkspaceName` },
|
|
261
266
|
],
|
|
262
267
|
[SECRET]: [
|
|
263
268
|
{ field: `metadata.annotations[${ UI_PROJECT_SECRET_COPY }]` },
|
|
@@ -265,7 +270,10 @@ class StevePaginationUtils extends NamespaceProjectFilters {
|
|
|
265
270
|
[NAMESPACE]: [
|
|
266
271
|
],
|
|
267
272
|
[CAPI.MACHINE]: [
|
|
268
|
-
{ field: 'spec.clusterName' }
|
|
273
|
+
{ field: 'spec.clusterName' },
|
|
274
|
+
],
|
|
275
|
+
[CAPI.MACHINE_DEPLOYMENT]: [
|
|
276
|
+
{ field: 'spec.clusterName' },
|
|
269
277
|
],
|
|
270
278
|
[EVENT]: [
|
|
271
279
|
{ field: '_type' },
|
|
@@ -770,8 +778,8 @@ export const PAGINATION_SETTINGS_STORE_DEFAULTS: PaginationSettingsStores = {
|
|
|
770
778
|
enableAll: false,
|
|
771
779
|
enableSome: {
|
|
772
780
|
enabled: [
|
|
773
|
-
{ resource: CAPI.RANCHER_CLUSTER, context: ['side-bar'] },
|
|
774
|
-
{ resource: MANAGEMENT.CLUSTER, context: ['side-bar'] },
|
|
781
|
+
{ resource: CAPI.RANCHER_CLUSTER, context: ['side-bar', 'home', 'cluster-management'] },
|
|
782
|
+
{ resource: MANAGEMENT.CLUSTER, context: ['side-bar', 'home', 'cluster-management'] },
|
|
775
783
|
{ resource: CATALOG.APP, context: ['branding'] },
|
|
776
784
|
SECRET
|
|
777
785
|
],
|
|
@@ -469,8 +469,10 @@ const sharedActions = {
|
|
|
469
469
|
const worker = (this.$workers || {})[getters.storeName];
|
|
470
470
|
|
|
471
471
|
if (worker) {
|
|
472
|
+
const storeName = getters.storeName;
|
|
473
|
+
|
|
472
474
|
worker.postMessage({ destroyWorker: true }); // we're only passing the boolean here because the key needs to be something truthy to ensure it's passed on the object.
|
|
473
|
-
cleanupTasks.push(waitFor(() => !this.$workers[
|
|
475
|
+
cleanupTasks.push(waitFor(() => !this.$workers?.[storeName], 'Worker to be destroyed', 30000, 10, true));
|
|
474
476
|
}
|
|
475
477
|
|
|
476
478
|
if ( socket ) {
|
|
@@ -553,17 +555,32 @@ const sharedActions = {
|
|
|
553
555
|
* @param {STEVE_WATCH_PARAMS} params
|
|
554
556
|
*/
|
|
555
557
|
watch({
|
|
556
|
-
state, dispatch, getters, rootGetters
|
|
558
|
+
state, dispatch, getters, rootGetters, commit
|
|
557
559
|
}, params) {
|
|
558
560
|
state.debugSocket && console.info(`Watch Request [${ getters.storeName }]`, JSON.stringify(params)); // eslint-disable-line no-console
|
|
559
561
|
let {
|
|
560
562
|
// eslint-disable-next-line prefer-const
|
|
561
|
-
type, selector, id, revision, namespace, stop, force, mode, standardWatch = true
|
|
563
|
+
type, selector, id, revision, namespace, stop, force, mode, standardWatch = true, registerType = false
|
|
562
564
|
} = params;
|
|
563
565
|
|
|
564
566
|
namespace = acceptOrRejectSocketMessage.subscribeNamespace(namespace);
|
|
565
567
|
type = getters.normalizeType(type);
|
|
566
568
|
|
|
569
|
+
if ( !getters.typeRegistered(type) ) {
|
|
570
|
+
if (registerType) {
|
|
571
|
+
commit('registerType', type);
|
|
572
|
+
} else if (mode !== STEVE_WATCH_MODE.RESOURCE_CHANGES) {
|
|
573
|
+
// - If we continue and open up a watch whenever we receive a `resource.` notification we go to queueChanges (bar resource.changes mode).
|
|
574
|
+
// - queueChanges ignores any change that's for a type that hasn't been registered
|
|
575
|
+
// - So here we're just exiting early, avoiding the watch --> queueChanges --> ignore loop
|
|
576
|
+
//
|
|
577
|
+
// Interestingly this is hit quite a few times (on cluster create screens there's token, cluster, project, projectRoleTemplateBinding, etc)
|
|
578
|
+
state.debugSocket && console.info('Will not Watch (type is not registered)', JSON.stringify(params)); // eslint-disable-line no-console
|
|
579
|
+
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
567
584
|
if (rootGetters['type-map/isSpoofed'](type)) {
|
|
568
585
|
state.debugSocket && console.info('Will not Watch (type is spoofed)', JSON.stringify(params)); // eslint-disable-line no-console
|
|
569
586
|
|
|
@@ -620,6 +637,9 @@ const sharedActions = {
|
|
|
620
637
|
if (debounceMs) {
|
|
621
638
|
msg.debounceMs = debounceMs;
|
|
622
639
|
}
|
|
640
|
+
|
|
641
|
+
// Anything in the queue will pollute the result set, so clear (and print to console so we know it's working)
|
|
642
|
+
commit('clearFromQueue', { type, log: true });
|
|
623
643
|
}
|
|
624
644
|
}
|
|
625
645
|
|
|
@@ -1556,10 +1576,20 @@ const defaultMutations = {
|
|
|
1556
1576
|
state.socketListenerManager = new SteveWatchEventListenerManager(state.config.namespace);
|
|
1557
1577
|
},
|
|
1558
1578
|
|
|
1559
|
-
clearFromQueue(state,
|
|
1579
|
+
clearFromQueue(state, args) {
|
|
1580
|
+
const safeArgs = typeof args === 'object' ? args : { type: args };
|
|
1581
|
+
const { type, log } = safeArgs;
|
|
1582
|
+
|
|
1560
1583
|
// Remove anything in the queue that is a resource update for the given type
|
|
1561
1584
|
state.queue = state.queue.filter((item) => {
|
|
1562
|
-
|
|
1585
|
+
const keep = item.body?.type !== type;
|
|
1586
|
+
|
|
1587
|
+
if (!keep && log) {
|
|
1588
|
+
// eslint-disable-next-line no-console
|
|
1589
|
+
console.info(`Clearing queued item of type \`${ type }\` from queue`, item);
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
return keep;
|
|
1563
1593
|
});
|
|
1564
1594
|
},
|
|
1565
1595
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineComponent, nextTick, provide, ref } from 'vue';
|
|
2
|
+
import { mount, flushPromises } from '@vue/test-utils';
|
|
3
|
+
import { useForm } from 'vee-validate';
|
|
2
4
|
import { LabeledInput } from './index';
|
|
3
5
|
|
|
4
6
|
describe('component: LabeledInput', () => {
|
|
@@ -105,4 +107,212 @@ describe('component: LabeledInput', () => {
|
|
|
105
107
|
expect(mainInput.attributes('aria-label')).toBeUndefined();
|
|
106
108
|
expect(wrapper.find('label').text()).toBe(label);
|
|
107
109
|
});
|
|
110
|
+
|
|
111
|
+
describe('vee-validate integration', () => {
|
|
112
|
+
const i18nMock = { $store: { getters: { 'i18n/t': jest.fn() } } };
|
|
113
|
+
|
|
114
|
+
it('without name prop: existing rules-based validation message is shown after blur', async() => {
|
|
115
|
+
const errorMessage = 'This field cannot be empty';
|
|
116
|
+
const notEmptyRule = (v: string) => (!v ? errorMessage : undefined);
|
|
117
|
+
|
|
118
|
+
const wrapper = mount(LabeledInput, {
|
|
119
|
+
propsData: {
|
|
120
|
+
rules: [notEmptyRule],
|
|
121
|
+
value: '',
|
|
122
|
+
},
|
|
123
|
+
mocks: i18nMock
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await wrapper.find('input').trigger('blur');
|
|
127
|
+
await nextTick();
|
|
128
|
+
|
|
129
|
+
expect(wrapper.vm.validationMessage).toBe(errorMessage);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('without name prop: error CSS class is not applied automatically', async() => {
|
|
133
|
+
const notEmptyRule = (v: string) => (!v ? 'Error' : undefined);
|
|
134
|
+
|
|
135
|
+
const wrapper = mount(LabeledInput, {
|
|
136
|
+
propsData: {
|
|
137
|
+
rules: [notEmptyRule],
|
|
138
|
+
value: '',
|
|
139
|
+
},
|
|
140
|
+
mocks: i18nMock
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await wrapper.find('input').trigger('blur');
|
|
144
|
+
await nextTick();
|
|
145
|
+
|
|
146
|
+
expect(wrapper.find('.labeled-input').classes()).not.toContain('error');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('with name prop: name attribute is set on the input element', () => {
|
|
150
|
+
const wrapper = mount(LabeledInput, {
|
|
151
|
+
propsData: { name: 'myField' },
|
|
152
|
+
mocks: i18nMock
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(wrapper.find('input').attributes('name')).toStrictEqual('myField');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('with name prop: existing rules run through vee-validate and show error message after blur', async() => {
|
|
159
|
+
const errorMessage = 'Field cannot be empty';
|
|
160
|
+
const notEmptyRule = (v: string) => (!v ? errorMessage : undefined);
|
|
161
|
+
|
|
162
|
+
const wrapper = mount(LabeledInput, {
|
|
163
|
+
propsData: {
|
|
164
|
+
name: 'testField',
|
|
165
|
+
rules: [notEmptyRule],
|
|
166
|
+
value: '',
|
|
167
|
+
},
|
|
168
|
+
mocks: i18nMock
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await wrapper.find('input').trigger('blur');
|
|
172
|
+
await flushPromises();
|
|
173
|
+
|
|
174
|
+
expect(wrapper.vm.validationMessage).toStrictEqual(errorMessage);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('with name prop: no error class when validation passes', async() => {
|
|
178
|
+
const notEmptyRule = (v: string) => (!v ? 'Error' : undefined);
|
|
179
|
+
|
|
180
|
+
const wrapper = mount(LabeledInput, {
|
|
181
|
+
propsData: {
|
|
182
|
+
name: 'testField',
|
|
183
|
+
rules: [notEmptyRule],
|
|
184
|
+
value: 'valid value',
|
|
185
|
+
},
|
|
186
|
+
mocks: i18nMock
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await wrapper.find('input').trigger('blur');
|
|
190
|
+
await flushPromises();
|
|
191
|
+
|
|
192
|
+
expect(wrapper.find('.labeled-input').classes()).not.toContain('error');
|
|
193
|
+
expect(wrapper.vm.validationMessage).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('with name prop: form-level validation schema error is shown when the form validates', async() => {
|
|
197
|
+
const errorMessage = 'Username is required';
|
|
198
|
+
const showAllErrors = ref(false);
|
|
199
|
+
let triggerFormValidation!: () => Promise<unknown>;
|
|
200
|
+
|
|
201
|
+
const TestWrapper = defineComponent({
|
|
202
|
+
components: { LabeledInput },
|
|
203
|
+
setup() {
|
|
204
|
+
provide('vee-show-all-errors', showAllErrors);
|
|
205
|
+
|
|
206
|
+
const { validate } = useForm({
|
|
207
|
+
validationSchema: { username: (v: string) => (!v ? errorMessage : true) },
|
|
208
|
+
initialValues: { username: '' },
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
triggerFormValidation = async() => {
|
|
212
|
+
await validate();
|
|
213
|
+
showAllErrors.value = true;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return {};
|
|
217
|
+
},
|
|
218
|
+
template: '<LabeledInput name="username" value="" />',
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const wrapper = mount(TestWrapper, { global: { mocks: { $store: { getters: { 'i18n/t': jest.fn() } } } } });
|
|
222
|
+
|
|
223
|
+
await triggerFormValidation();
|
|
224
|
+
await flushPromises();
|
|
225
|
+
|
|
226
|
+
const labeledInput = wrapper.findComponent(LabeledInput);
|
|
227
|
+
|
|
228
|
+
expect(labeledInput.vm.validationMessage).toStrictEqual(errorMessage);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('without name prop: error clears when a previously invalid value becomes valid', async() => {
|
|
232
|
+
const errorMessage = 'This field cannot be empty';
|
|
233
|
+
const notEmptyRule = (v: string) => (!v ? errorMessage : undefined);
|
|
234
|
+
|
|
235
|
+
const wrapper = mount(LabeledInput, {
|
|
236
|
+
propsData: {
|
|
237
|
+
rules: [notEmptyRule],
|
|
238
|
+
value: '',
|
|
239
|
+
},
|
|
240
|
+
mocks: i18nMock
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await wrapper.find('input').trigger('blur');
|
|
244
|
+
await nextTick();
|
|
245
|
+
|
|
246
|
+
expect(wrapper.vm.validationMessage).toBe(errorMessage);
|
|
247
|
+
|
|
248
|
+
await wrapper.setProps({ value: 'valid value' });
|
|
249
|
+
await nextTick();
|
|
250
|
+
|
|
251
|
+
expect(wrapper.vm.validationMessage).toBeUndefined();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it('with name prop: error clears when a previously invalid value becomes valid', async() => {
|
|
255
|
+
const errorMessage = 'Field cannot be empty';
|
|
256
|
+
const notEmptyRule = (v: string) => (!v ? errorMessage : undefined);
|
|
257
|
+
|
|
258
|
+
const wrapper = mount(LabeledInput, {
|
|
259
|
+
propsData: {
|
|
260
|
+
name: 'testField',
|
|
261
|
+
rules: [notEmptyRule],
|
|
262
|
+
value: '',
|
|
263
|
+
},
|
|
264
|
+
mocks: i18nMock
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
await wrapper.find('input').trigger('blur');
|
|
268
|
+
await flushPromises();
|
|
269
|
+
|
|
270
|
+
expect(wrapper.vm.validationMessage).toStrictEqual(errorMessage);
|
|
271
|
+
|
|
272
|
+
await wrapper.setProps({ value: 'valid value' });
|
|
273
|
+
await flushPromises();
|
|
274
|
+
|
|
275
|
+
expect(wrapper.vm.validationMessage).toBeUndefined();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe('with both name and rules provided', () => {
|
|
279
|
+
it('shows the error message exactly once when invalid (not duplicated across both validation paths)', async() => {
|
|
280
|
+
const errorMessage = 'Field cannot be empty';
|
|
281
|
+
const notEmptyRule = (v: string) => (!v ? errorMessage : undefined);
|
|
282
|
+
|
|
283
|
+
const wrapper = mount(LabeledInput, {
|
|
284
|
+
propsData: {
|
|
285
|
+
name: 'testField',
|
|
286
|
+
rules: [notEmptyRule],
|
|
287
|
+
value: '',
|
|
288
|
+
},
|
|
289
|
+
mocks: i18nMock
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await wrapper.find('input').trigger('blur');
|
|
293
|
+
await flushPromises();
|
|
294
|
+
|
|
295
|
+
expect(wrapper.vm.validationMessage).toStrictEqual(errorMessage);
|
|
296
|
+
expect(wrapper.vm.validationMessage).not.toContain(`${ errorMessage }, ${ errorMessage }`);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('shows no error when the value satisfies the rules', async() => {
|
|
300
|
+
const notEmptyRule = (v: string) => (!v ? 'Field cannot be empty' : undefined);
|
|
301
|
+
|
|
302
|
+
const wrapper = mount(LabeledInput, {
|
|
303
|
+
propsData: {
|
|
304
|
+
name: 'testField',
|
|
305
|
+
rules: [notEmptyRule],
|
|
306
|
+
value: 'valid value',
|
|
307
|
+
},
|
|
308
|
+
mocks: i18nMock
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
await wrapper.find('input').trigger('blur');
|
|
312
|
+
await flushPromises();
|
|
313
|
+
|
|
314
|
+
expect(wrapper.vm.validationMessage).toBeUndefined();
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
});
|
|
108
318
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { defineComponent, inject } from 'vue';
|
|
2
|
+
import { defineComponent, inject, computed, toRef } from 'vue';
|
|
3
3
|
import TextAreaAutoGrow from '@components/Form/TextArea/TextAreaAutoGrow.vue';
|
|
4
4
|
import LabeledTooltip from '@components/LabeledTooltip/LabeledTooltip.vue';
|
|
5
5
|
import { escapeHtml, generateRandomAlphaString } from '@shell/utils/string';
|
|
@@ -8,6 +8,7 @@ import { isValidCron } from 'cron-validator';
|
|
|
8
8
|
import { debounce } from 'lodash';
|
|
9
9
|
import { useLabeledFormElement, labeledFormElementProps } from '@shell/composables/useLabeledFormElement';
|
|
10
10
|
import { useCompactInput } from '@shell/composables/useCompactInput';
|
|
11
|
+
import { useVeeValidateField } from '@shell/composables/useVeeValidateField';
|
|
11
12
|
|
|
12
13
|
interface NonReactiveProps {
|
|
13
14
|
onInput: (event: Event) => void | ((event: Event) => void);
|
|
@@ -114,6 +115,15 @@ export default defineComponent({
|
|
|
114
115
|
ariaLabel: {
|
|
115
116
|
type: String,
|
|
116
117
|
default: ''
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The field name used for vee-validate integration. When provided, the
|
|
122
|
+
* component registers with a parent vee-validate form context
|
|
123
|
+
*/
|
|
124
|
+
name: {
|
|
125
|
+
type: String,
|
|
126
|
+
default: null
|
|
117
127
|
}
|
|
118
128
|
},
|
|
119
129
|
|
|
@@ -132,15 +142,27 @@ export default defineComponent({
|
|
|
132
142
|
|
|
133
143
|
const onInput = inject('onInput', provideProps.onInput);
|
|
134
144
|
|
|
145
|
+
const { effectiveValidationMessage, veeHandleBlur, veeValidate } = useVeeValidateField({
|
|
146
|
+
name: toRef(props, 'name'),
|
|
147
|
+
rules: toRef(props, 'rules'),
|
|
148
|
+
value: toRef(props, 'value'),
|
|
149
|
+
validationMessage,
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const effectiveStatus = computed(() => props.status);
|
|
153
|
+
|
|
135
154
|
return {
|
|
136
155
|
focused,
|
|
137
156
|
onFocusLabeled,
|
|
138
157
|
onBlurLabeled,
|
|
139
158
|
onInput,
|
|
140
159
|
isDisabled,
|
|
141
|
-
validationMessage,
|
|
160
|
+
validationMessage: effectiveValidationMessage,
|
|
142
161
|
requiredField,
|
|
143
162
|
isCompact,
|
|
163
|
+
veeHandleBlur,
|
|
164
|
+
veeValidate,
|
|
165
|
+
effectiveStatus,
|
|
144
166
|
};
|
|
145
167
|
},
|
|
146
168
|
|
|
@@ -339,6 +361,11 @@ export default defineComponent({
|
|
|
339
361
|
onBlur(event: string | FocusEvent): void {
|
|
340
362
|
this.$emit('blur', event);
|
|
341
363
|
this.onBlurLabeled();
|
|
364
|
+
// Mark the field as touched in vee-validate without relying on its
|
|
365
|
+
// 'validated-only' guard, then run validation unconditionally so
|
|
366
|
+
// errors surface on the first blur (matching useLabeledFormElement behavior).
|
|
367
|
+
this.veeHandleBlur(event instanceof FocusEvent ? event : undefined, false);
|
|
368
|
+
this.veeValidate();
|
|
342
369
|
},
|
|
343
370
|
|
|
344
371
|
escapeHtml
|
|
@@ -353,7 +380,7 @@ export default defineComponent({
|
|
|
353
380
|
focused,
|
|
354
381
|
[mode]: true,
|
|
355
382
|
disabled: isDisabled,
|
|
356
|
-
[
|
|
383
|
+
[effectiveStatus]: effectiveStatus,
|
|
357
384
|
suffix: hasSuffix,
|
|
358
385
|
'has-clean-tooltip': hasTooltip,
|
|
359
386
|
'compact-input': isCompact,
|
|
@@ -389,13 +416,14 @@ export default defineComponent({
|
|
|
389
416
|
ref="value"
|
|
390
417
|
v-bind="$attrs"
|
|
391
418
|
v-stripped-aria-label="!hasLabel && ariaLabel ? ariaLabel : undefined"
|
|
419
|
+
:name="name || undefined"
|
|
392
420
|
:maxlength="_maxlength"
|
|
393
421
|
:disabled="isDisabled"
|
|
394
422
|
:aria-disabled="isDisabled"
|
|
395
423
|
:value="value || ''"
|
|
396
424
|
:placeholder="_placeholder"
|
|
397
425
|
autocapitalize="off"
|
|
398
|
-
:class="{
|
|
426
|
+
:class="{ 'multiline-password': type === 'multiline-password' }"
|
|
399
427
|
:aria-describedby="ariaDescribedBy"
|
|
400
428
|
:aria-required="requiredField"
|
|
401
429
|
@update:value="onInput"
|
|
@@ -410,6 +438,7 @@ export default defineComponent({
|
|
|
410
438
|
:role="type === 'number' ? undefined : 'textbox'"
|
|
411
439
|
:class="{ 'no-label': !hasLabel }"
|
|
412
440
|
v-bind="$attrs"
|
|
441
|
+
:name="name || undefined"
|
|
413
442
|
:maxlength="_maxlength"
|
|
414
443
|
:disabled="isDisabled"
|
|
415
444
|
:aria-disabled="isDisabled"
|
|
@@ -464,6 +493,10 @@ export default defineComponent({
|
|
|
464
493
|
</div>
|
|
465
494
|
</template>
|
|
466
495
|
<style scoped lang="scss">
|
|
496
|
+
.multiline-password:not(:focus) {
|
|
497
|
+
-webkit-text-security: disc;
|
|
498
|
+
}
|
|
499
|
+
|
|
467
500
|
.labeled-input.view {
|
|
468
501
|
input {
|
|
469
502
|
text-overflow: ellipsis;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mount } from '@vue/test-utils';
|
|
1
|
+
import { mount, RouterLinkStub } from '@vue/test-utils';
|
|
2
2
|
import RcButton from './RcButton.vue';
|
|
3
3
|
|
|
4
4
|
describe('rcButton.vue', () => {
|
|
@@ -138,4 +138,40 @@ describe('rcButton.vue', () => {
|
|
|
138
138
|
expect(button.classes()).toContain('variant-ghost');
|
|
139
139
|
});
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
describe('to prop', () => {
|
|
143
|
+
it('renders as a <button> when no "to" prop is provided', () => {
|
|
144
|
+
const wrapper = mount(RcButton);
|
|
145
|
+
|
|
146
|
+
expect(wrapper.find('button').exists()).toBe(true);
|
|
147
|
+
expect(wrapper.findComponent(RouterLinkStub).exists()).toBe(false);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('renders as a RouterLink when "to" prop is provided', () => {
|
|
151
|
+
const to = { name: 'some-route' };
|
|
152
|
+
const wrapper = mount(RcButton, {
|
|
153
|
+
props: { to },
|
|
154
|
+
global: { stubs: { RouterLink: RouterLinkStub } },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const link = wrapper.findComponent(RouterLinkStub);
|
|
158
|
+
|
|
159
|
+
expect(link.exists()).toBe(true);
|
|
160
|
+
expect(wrapper.find('button').exists()).toBe(false);
|
|
161
|
+
expect(link.props('to')).toStrictEqual(to);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it('applies button classes when rendered as a RouterLink', () => {
|
|
165
|
+
const wrapper = mount(RcButton, {
|
|
166
|
+
props: { to: '/foo', variant: 'secondary' },
|
|
167
|
+
global: { stubs: { RouterLink: RouterLinkStub } },
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const link = wrapper.findComponent(RouterLinkStub);
|
|
171
|
+
|
|
172
|
+
expect(link.classes()).toContain('rc-button');
|
|
173
|
+
expect(link.classes()).toContain('btn');
|
|
174
|
+
expect(link.classes()).toContain('variant-secondary');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
141
177
|
});
|