@rancher/shell 3.0.5-rc.6 → 3.0.5-rc.8
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/brand/classic/metadata.json +3 -0
- package/assets/styles/app.scss +1 -0
- package/assets/styles/base/_color.scss +16 -0
- package/assets/styles/base/_helpers.scss +10 -0
- package/assets/styles/base/_variables.scss +18 -12
- package/assets/styles/fonts/_icons.scss +1 -32
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +262 -258
- package/assets/styles/themes/_light.scss +538 -509
- package/assets/styles/themes/_modern.scss +914 -0
- package/assets/translations/en-us.yaml +110 -29
- package/chart/__tests__/S3.test.ts +2 -1
- package/cloud-credential/generic.vue +18 -10
- package/cloud-credential/harvester.vue +1 -9
- package/components/AdvancedSection.vue +8 -0
- package/components/ChartReadme.vue +17 -7
- package/components/CodeMirror.vue +1 -1
- package/components/Drawer/Chrome.vue +0 -1
- package/components/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +27 -28
- package/components/Drawer/ResourceDetailDrawer/composables.ts +4 -24
- package/components/Drawer/ResourceDetailDrawer/index.vue +18 -4
- package/components/InstallHelmCharts.vue +656 -0
- package/components/LazyImage.vue +60 -4
- package/components/Loading.vue +1 -1
- package/components/LocaleSelector.vue +7 -2
- package/components/Markdown.vue +4 -0
- package/components/PaginatedResourceTable.vue +46 -1
- package/components/PromptRestore.vue +22 -44
- package/components/Resource/Detail/Masthead/composable.ts +16 -0
- package/components/Resource/Detail/Masthead/index.vue +37 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/composable.ts +10 -2
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +26 -7
- package/components/Resource/Detail/Metadata/IdentifyingInformation/index.vue +8 -1
- package/components/Resource/Detail/Metadata/KeyValue.vue +12 -10
- package/components/Resource/Detail/Metadata/Rectangle.vue +3 -1
- package/components/Resource/Detail/Metadata/__tests__/composables.test.ts +10 -17
- package/components/Resource/Detail/Metadata/composables.ts +9 -7
- package/components/Resource/Detail/Metadata/index.vue +17 -2
- package/components/Resource/Detail/Page.vue +35 -21
- package/components/Resource/Detail/SpacedRow.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/composables.test.ts +8 -9
- package/components/Resource/Detail/TitleBar/composables.ts +5 -5
- package/components/Resource/Detail/TitleBar/index.vue +12 -3
- package/components/ResourceDetail/Masthead/legacy.vue +1 -1
- package/components/ResourceDetail/index.vue +569 -72
- package/components/ResourceList/index.vue +1 -0
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +1 -1
- package/components/RichTranslation.vue +106 -0
- package/components/SlideInPanelManager.vue +13 -10
- package/components/SortableTable/index.vue +5 -5
- package/components/SortableTable/selection.js +0 -1
- package/components/Tabbed/index.vue +35 -4
- package/components/__tests__/LazyImage.spec.ts +121 -0
- package/components/__tests__/PromptRestore.test.ts +1 -65
- package/components/__tests__/RichTranslation.test.ts +115 -0
- package/components/fleet/FleetStatus.vue +4 -0
- package/components/fleet/dashboard/ResourcePanel.vue +2 -1
- package/components/form/ClusterAppearance.vue +5 -0
- package/components/form/FileImageSelector.vue +1 -1
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/NameNsDescription.vue +1 -0
- package/components/form/Networking.vue +24 -19
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +22 -8
- package/components/form/ResourceTabs/index.vue +20 -0
- package/components/form/SecretSelector.vue +9 -0
- package/components/form/SelectOrCreateAuthSecret.vue +6 -3
- package/components/form/__tests__/Networking.test.ts +116 -0
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
- package/components/formatter/FleetApplicationSource.vue +25 -17
- package/components/formatter/PodImages.vue +1 -1
- package/components/formatter/__tests__/LiveDate.test.ts +10 -2
- package/components/google/AccountAccess.vue +44 -46
- package/components/nav/Favorite.vue +4 -0
- package/components/nav/Group.vue +4 -1
- package/components/nav/NotificationCenter/Notification.vue +1 -27
- package/components/nav/WindowManager/index.vue +3 -3
- package/composables/resources.ts +2 -2
- package/config/labels-annotations.js +3 -2
- package/config/pagination-table-headers.js +8 -1
- package/config/product/explorer.js +27 -2
- package/config/product/manager.js +0 -1
- package/config/query-params.js +10 -0
- package/config/router/routes.js +21 -1
- package/config/system-namespaces.js +1 -1
- package/config/table-headers.js +30 -1
- package/config/types.js +1 -1
- package/config/version.js +1 -1
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +11 -0
- package/detail/__tests__/workload.test.ts +164 -0
- package/detail/configmap.vue +33 -75
- package/detail/projectsecret.vue +11 -0
- package/detail/provisioning.cattle.io.cluster.vue +351 -369
- package/detail/secret.vue +49 -308
- package/detail/workload/index.vue +38 -21
- package/dialog/InstallExtensionDialog.vue +8 -5
- package/dialog/RotateEncryptionKeyDialog.vue +10 -30
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/edit/auth/ldap/__tests__/config.test.ts +14 -0
- package/edit/auth/ldap/config.vue +24 -0
- package/edit/compliance.cattle.io.clusterscan.vue +1 -1
- package/edit/configmap.vue +4 -1
- package/edit/fleet.cattle.io.gitrepo.vue +5 -6
- package/edit/fleet.cattle.io.helmop.vue +78 -56
- package/edit/logging.banzaicloud.io.output/index.vue +1 -1
- package/edit/logging.banzaicloud.io.output/providers/awsElasticsearch.vue +5 -6
- package/edit/networking.k8s.io.ingress/Certificate.vue +20 -22
- package/edit/networking.k8s.io.ingress/DefaultBackend.vue +8 -3
- package/edit/networking.k8s.io.ingress/Rule.vue +2 -5
- package/edit/networking.k8s.io.ingress/RulePath.vue +17 -11
- package/edit/networking.k8s.io.ingress/__tests__/Certificate.test.ts +165 -0
- package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +11 -10
- package/edit/networking.k8s.io.networkpolicy/PolicyRules.vue +1 -3
- package/edit/networking.k8s.io.networkpolicy/index.vue +17 -17
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +3 -2
- package/edit/provisioning.cattle.io.cluster/rke2.vue +123 -61
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
- package/edit/provisioning.cattle.io.cluster/tabs/Basics.vue +22 -13
- package/edit/provisioning.cattle.io.cluster/tabs/DirectoryConfig.vue +10 -12
- package/edit/provisioning.cattle.io.cluster/tabs/MachinePool.vue +39 -38
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/S3Config.vue +41 -19
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +16 -3
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryConfigs.vue +32 -33
- package/edit/provisioning.cattle.io.cluster/tabs/registries/RegistryMirrors.vue +9 -10
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +1 -3
- package/edit/provisioning.cattle.io.cluster/tabs/upgrade/DrainOptions.vue +16 -9
- package/edit/secret/basic.vue +1 -0
- package/edit/secret/index.vue +126 -15
- package/edit/workload/index.vue +5 -14
- package/list/projectsecret.vue +345 -0
- package/list/provisioning.cattle.io.cluster.vue +1 -69
- package/list/secret.vue +109 -0
- package/machine-config/__tests__/vmwarevsphere.test.ts +5 -7
- package/machine-config/google.vue +9 -1
- package/machine-config/vmwarevsphere.vue +7 -17
- package/mixins/__tests__/brand.spec.ts +2 -2
- package/mixins/chart.js +0 -2
- package/mixins/create-edit-view/impl.js +10 -1
- package/mixins/resource-fetch-api-pagination.js +11 -12
- package/mixins/resource-fetch.js +3 -1
- package/models/__tests__/chart.test.ts +111 -80
- package/models/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- package/models/__tests__/node.test.ts +7 -63
- package/models/catalog.cattle.io.app.js +1 -1
- package/models/catalog.cattle.io.operation.js +1 -1
- package/models/chart.js +36 -20
- package/models/cloudcredential.js +2 -163
- package/models/cluster/node.js +7 -7
- package/models/cluster.x-k8s.io.machine.js +3 -3
- package/models/cluster.x-k8s.io.machinedeployment.js +11 -2
- package/models/compliance.cattle.io.clusterscan.js +2 -2
- package/models/configmap.js +4 -0
- package/models/constraints.gatekeeper.sh.constraint.js +1 -1
- package/models/fleet-application.js +0 -17
- package/models/fleet.cattle.io.cluster.js +2 -2
- package/models/fleet.cattle.io.gitrepo.js +15 -1
- package/models/fleet.cattle.io.helmop.js +26 -22
- package/models/management.cattle.io.setting.js +4 -0
- package/models/persistentvolumeclaim.js +1 -1
- package/models/pod.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +39 -67
- package/models/rke.cattle.io.etcdsnapshot.js +1 -1
- package/models/secret.js +161 -2
- package/models/storage.k8s.io.storageclass.js +2 -2
- package/models/workload.js +3 -3
- package/package.json +11 -10
- package/pages/c/_cluster/apps/charts/AppChartCardFooter.vue +1 -0
- package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +4 -1
- package/pages/c/_cluster/apps/charts/__tests__/AppChartCardFooter.spec.js +41 -0
- package/pages/c/_cluster/apps/charts/chart.vue +422 -174
- package/pages/c/_cluster/apps/charts/index.vue +46 -35
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/projectsecret.vue +24 -0
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
- package/pages/c/_cluster/fleet/index.vue +103 -45
- package/pages/c/_cluster/manager/cloudCredential/index.vue +2 -59
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +10 -3
- package/pages/c/_cluster/uiplugins/index.vue +36 -25
- package/plugins/dashboard-store/__tests__/normalize.test.ts +223 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +191 -0
- package/plugins/dashboard-store/__tests__/utils/normalize-usecases.ts +1526 -0
- package/plugins/dashboard-store/actions.js +42 -22
- package/plugins/dashboard-store/normalize.js +29 -17
- package/plugins/dashboard-store/resource-class.js +83 -17
- package/plugins/steve/__tests__/getters.test.ts +1 -1
- package/plugins/steve/__tests__/subscribe.spec.ts +259 -1
- package/plugins/steve/getters.js +8 -2
- package/plugins/steve/resourceWatcher.js +10 -3
- package/plugins/steve/steve-pagination-utils.ts +14 -3
- package/plugins/steve/subscribe.js +192 -19
- package/plugins/steve/worker/web-worker.advanced.js +2 -0
- package/rancher-components/Card/Card.vue +0 -18
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.test.ts +15 -0
- package/rancher-components/Pill/RcStatusBadge/RcStatusBadge.vue +65 -0
- package/rancher-components/Pill/RcStatusBadge/index.ts +2 -0
- package/rancher-components/Pill/RcStatusBadge/types.ts +5 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.test.ts +33 -0
- package/rancher-components/Pill/RcStatusIndicator/RcStatusIndicator.vue +75 -0
- package/rancher-components/Pill/RcStatusIndicator/index.ts +2 -0
- package/rancher-components/Pill/RcStatusIndicator/types.ts +7 -0
- package/rancher-components/Pill/types.ts +2 -0
- package/rancher-components/RcButton/RcButton.vue +1 -1
- package/rancher-components/RcDropdown/RcDropdown.test.ts +98 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +5 -0
- package/rancher-components/RcDropdown/RcDropdownItem.vue +7 -1
- package/rancher-components/RcDropdown/RcDropdownItemCheckbox.vue +2 -1
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +2 -1
- package/rancher-components/RcDropdown/useDropdownContext.ts +21 -0
- package/rancher-components/RcDropdown/useDropdownItem.ts +30 -1
- package/rancher-components/RcItemCard/RcItemCard.test.ts +20 -0
- package/rancher-components/RcItemCard/RcItemCard.vue +40 -6
- package/store/__tests__/catalog.test.ts +93 -1
- package/store/aws.js +19 -8
- package/store/catalog.js +8 -3
- package/types/kube/kube-api.ts +12 -0
- package/types/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +643 -585
- package/types/store/pagination.types.ts +16 -6
- package/types/uiplugins.ts +73 -0
- package/utils/__tests__/back-off.test.ts +354 -0
- package/utils/__tests__/create-yaml.test.ts +235 -0
- package/utils/__tests__/kontainer.test.ts +19 -0
- package/utils/__tests__/uiplugins.test.ts +84 -0
- package/utils/back-off.ts +176 -0
- package/utils/create-yaml.js +103 -9
- package/utils/dynamic-importer.js +8 -0
- package/utils/kontainer.ts +3 -5
- package/utils/pagination-utils.ts +18 -0
- package/utils/style.ts +3 -0
- package/utils/uiplugins.ts +29 -2
- package/utils/validators/__tests__/setting.test.js +92 -0
- package/utils/validators/formRules/__tests__/index.test.ts +88 -7
- package/utils/validators/formRules/index.ts +83 -8
- package/utils/validators/setting.js +17 -0
- package/cloud-credential/__tests__/harvester.test.ts +0 -18
- package/components/ResourceDetail/__tests__/index.test.ts +0 -135
- package/components/ResourceDetail/legacy.vue +0 -562
- package/components/formatter/CloudCredExpired.vue +0 -69
- package/models/etcdbackup.js +0 -45
- package/pages/explorer/resource/detail/configmap.vue +0 -42
- package/pages/explorer/resource/detail/secret.vue +0 -50
- package/utils/aws.js +0 -0
|
@@ -3,8 +3,57 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Covers three use cases
|
|
5
5
|
* 1) Handles subscription within this file
|
|
6
|
-
* 2) Handles `cluster` subscriptions for some basic types in a web worker (SETTING.UI_PERFORMANCE advancedWorker = false)
|
|
6
|
+
* 2) Handles `cluster` subscriptions for some basic types in a web worker (SETTING.UI_PERFORMANCE advancedWorker = false) (is this true??)
|
|
7
7
|
* 2) Handles `cluster` subscriptions and optimisations in an advanced worker (SETTING.UI_PERFORMANCE advancedWorker = true)
|
|
8
|
+
*
|
|
9
|
+
* Very roughly this does...
|
|
10
|
+
*
|
|
11
|
+
* 1. _Subscribes_ to a web socket (v1, v3, v1 cluster)
|
|
12
|
+
* 2. UI --> Rancher: Sends a _watch_ message for a specific resource type (which can have qualifying filters)
|
|
13
|
+
* 3. Rancher --> UI: Rancher can send a number of messages back
|
|
14
|
+
* - `resource.start` - watch has started
|
|
15
|
+
* - `resource.error` - watch has errored, usually a result of bad data in the resource.start message
|
|
16
|
+
* - `resource.change` - a resource has changed, this is it's new value
|
|
17
|
+
* - `resource.changes` - if in this mode, no resource.change events are sent, instead one debounced message is sent without any resource data
|
|
18
|
+
* - `resource.stop` - either we have requested the watch stops, or there has been a resource.error
|
|
19
|
+
* 4. UI --> Rancher: Sends an _unwatch_ request for a matching _watch_ request
|
|
20
|
+
*
|
|
21
|
+
* Below are some VERY brief steps for common flows. Some will link together
|
|
22
|
+
*
|
|
23
|
+
* Successfully flow - watch
|
|
24
|
+
* 1. UI --> Rancher: _watch_ request
|
|
25
|
+
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
26
|
+
* 3. Rancher --> UI: `resource.change` (contains data). UI caches data
|
|
27
|
+
*
|
|
28
|
+
* Successful flow - watch - new mode
|
|
29
|
+
* 1. UI --> Rancher: _watch_ request
|
|
30
|
+
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
31
|
+
* 3. Rancher --> UI: `resource.changes` (contains no data). UI makes a HTTP request to fetch data
|
|
32
|
+
*
|
|
33
|
+
* Successful flow - unwatch
|
|
34
|
+
* 1. UI --> Rancher: _unwatch_ request
|
|
35
|
+
* 2. Rancher --> UI: `resource.stop`. UI sets watch as stopped
|
|
36
|
+
*
|
|
37
|
+
* Successful flow - resource.stop received
|
|
38
|
+
* 1. Rancher --> UI: `resource.stop`. UI sets watch as stopped
|
|
39
|
+
* 2. UI --> Rancher: _watch_ request
|
|
40
|
+
*
|
|
41
|
+
* Successful flow - socket disconnected
|
|
42
|
+
* 1. Socket closes|disconnects (not sure which)
|
|
43
|
+
* 2. UI: reopens socket
|
|
44
|
+
* 3. UI --> Rancher: _watch_ request (for every started watch)
|
|
45
|
+
*
|
|
46
|
+
* Error Flow
|
|
47
|
+
* 1. UI --> Rancher: _watch_ request
|
|
48
|
+
* 2. Rancher --> UI: `resource.start`. UI sets watch as started
|
|
49
|
+
* 3. Rancher --> UI: `resource.error`. UI sets watch as errored.
|
|
50
|
+
* a) UI: in the event of 'too old' the UI will make a http request to fetch a new revision and re-watch with it. This process is delayed on each call
|
|
51
|
+
* 4. Rancher --> UI: `resource.stop`. UI sets watch as stop (note the resource.stop flow above is avoided given error state)
|
|
52
|
+
*
|
|
53
|
+
* Additionally
|
|
54
|
+
* - if we receive resource.stop, unless the watch is in error, we immediately send back a watch event
|
|
55
|
+
* - if the web socket is disconnected (for steve based sockets it happens every 30 mins, or when there are permission changes)
|
|
56
|
+
* the ui will re-connect it and re-watch all previous watches using a best effort revision
|
|
8
57
|
*/
|
|
9
58
|
|
|
10
59
|
import { addObject, clear, removeObject } from '@shell/utils/array';
|
|
@@ -37,6 +86,7 @@ import { BLANK_CLUSTER, STORE } from '@shell/store/store-types.js';
|
|
|
37
86
|
import { _MERGE } from '@shell/plugins/dashboard-store/actions';
|
|
38
87
|
import { STEVE_WATCH_EVENT, STEVE_WATCH_MODE } from '@shell/types/store/subscribe.types';
|
|
39
88
|
import paginationUtils from '@shell/utils/pagination-utils';
|
|
89
|
+
import backOff from '@shell/utils/back-off';
|
|
40
90
|
|
|
41
91
|
// minimum length of time a disconnect notification is shown
|
|
42
92
|
const MINIMUM_TIME_NOTIFIED = 3000;
|
|
@@ -88,7 +138,7 @@ export async function createWorker(store, ctx) {
|
|
|
88
138
|
postMessage: (msg) => {
|
|
89
139
|
if (Object.keys(msg)?.[0] === 'destroyWorker') {
|
|
90
140
|
// The worker has been destroyed before it's been set up. Flag this so we stop waiting for mgmt settings and then can destroy worker.
|
|
91
|
-
// This can
|
|
141
|
+
// This can occur when the user is redirected to the log in page
|
|
92
142
|
// - workers created (but waiting)
|
|
93
143
|
// - logout is called
|
|
94
144
|
// - <store>/unsubscribe is dispatched
|
|
@@ -269,6 +319,27 @@ function growlsDisabled(rootGetters) {
|
|
|
269
319
|
*/
|
|
270
320
|
const listeners = { [STEVE_WATCH_EVENT.CHANGES]: [] };
|
|
271
321
|
|
|
322
|
+
/**
|
|
323
|
+
* Given a started or error entry, is it compatible with the given change in mode?
|
|
324
|
+
*/
|
|
325
|
+
const shouldUnwatchIncompatible = (messageMeta, mode) => {
|
|
326
|
+
if (messageMeta.mode === STEVE_WATCH_EVENT.CHANGES) {
|
|
327
|
+
return mode !== STEVE_WATCH_EVENT.CHANGES;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return mode === STEVE_WATCH_EVENT.CHANGES;
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* clear the provided error, but also ensure any backoff request associated with it is cleared as well
|
|
335
|
+
*/
|
|
336
|
+
const clearInError = ({ getters, commit }, error) => {
|
|
337
|
+
// for this watch ... get the specific prefix we care about ... reset back-offs related to it
|
|
338
|
+
backOff.resetPrefix(getters.backOffId(error.obj, ''));
|
|
339
|
+
// Clear out stale error state (next time around we can try again with a new revision that was just fetched)
|
|
340
|
+
commit('clearInError', error.obj);
|
|
341
|
+
};
|
|
342
|
+
|
|
272
343
|
/**
|
|
273
344
|
* Actions that cover all cases (see file description)
|
|
274
345
|
*/
|
|
@@ -347,7 +418,9 @@ const sharedActions = {
|
|
|
347
418
|
}
|
|
348
419
|
},
|
|
349
420
|
|
|
350
|
-
unsubscribe({
|
|
421
|
+
async unsubscribe({
|
|
422
|
+
commit, getters, state, dispatch
|
|
423
|
+
}) {
|
|
351
424
|
const socket = state.socket;
|
|
352
425
|
|
|
353
426
|
commit('setWantSocket', false);
|
|
@@ -364,6 +437,8 @@ const sharedActions = {
|
|
|
364
437
|
cleanupTasks.push(socket.disconnect());
|
|
365
438
|
}
|
|
366
439
|
|
|
440
|
+
await dispatch('resetWatchBackOff');
|
|
441
|
+
|
|
367
442
|
return Promise.all(cleanupTasks);
|
|
368
443
|
},
|
|
369
444
|
|
|
@@ -574,6 +649,8 @@ const sharedActions = {
|
|
|
574
649
|
// Make sure anything in the pending queue for the type is removed, since we've now removed the type
|
|
575
650
|
commit('clearFromQueue', type);
|
|
576
651
|
}
|
|
652
|
+
// Ensure anything pinging in the background is stopped
|
|
653
|
+
backOff.resetPrefix(getters.backOffId(obj));
|
|
577
654
|
};
|
|
578
655
|
|
|
579
656
|
if (isAdvancedWorker(ctx)) {
|
|
@@ -591,19 +668,63 @@ const sharedActions = {
|
|
|
591
668
|
/**
|
|
592
669
|
* Unwatch watches that are incompatible with the new type
|
|
593
670
|
*/
|
|
594
|
-
unwatchIncompatible({
|
|
671
|
+
unwatchIncompatible({
|
|
672
|
+
state, dispatch, getters, commit
|
|
673
|
+
}, messageMeta) {
|
|
674
|
+
// Step 1 - Clear incompatible watches that have STARTED
|
|
595
675
|
const watchesOfType = getters.watchesOfType(messageMeta.type);
|
|
596
|
-
let unwatch = [];
|
|
597
676
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
677
|
+
watchesOfType
|
|
678
|
+
.filter((entry) => shouldUnwatchIncompatible(messageMeta, entry.mode))
|
|
679
|
+
.forEach((entry) => {
|
|
680
|
+
dispatch('unwatch', entry);
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Step 2 - Clear inError state for incompatible watches (these won't appear in watchesOfType / state.started)
|
|
684
|
+
// (important for the backoff case... for example backoff request to find would overwrite findPage res if executed after nav from detail to list)
|
|
685
|
+
const inErrorOfType = Object.values(state.inError || {})
|
|
686
|
+
.filter((error) => error.obj.type === messageMeta.type);
|
|
687
|
+
|
|
688
|
+
inErrorOfType
|
|
689
|
+
.filter((error) => shouldUnwatchIncompatible(messageMeta, error.obj.mode))
|
|
690
|
+
.forEach((error) => clearInError({ getters, commit }, error));
|
|
691
|
+
},
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Ensure there's no back-off process waiting to run for
|
|
695
|
+
* - resource.changes fetchResources
|
|
696
|
+
* - resource.error resyncWatches
|
|
697
|
+
*/
|
|
698
|
+
resetWatchBackOff({ state, getters, commit }, {
|
|
699
|
+
type, compareWatches, resetInError = true, resetStarted = true
|
|
700
|
+
} = { resetInError: true, resetStarted: true }) {
|
|
701
|
+
// Step 1 - Reset back-offs related to watches that have STARTED
|
|
702
|
+
if (resetStarted && state.started?.length) {
|
|
703
|
+
let entries = state.started;
|
|
704
|
+
|
|
705
|
+
if (type) { // Filter out ones for types we're no interested in
|
|
706
|
+
entries = entries
|
|
707
|
+
.filter((obj) => compareWatches ? compareWatches(obj) : obj.type === type);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
entries.forEach((obj) => backOff.resetPrefix(getters.backOffId(obj, '')));
|
|
604
711
|
}
|
|
605
712
|
|
|
606
|
-
|
|
713
|
+
// Step 2 - Reset back-offs related to watches that are in error (and may not be started)
|
|
714
|
+
if (resetInError && state.inError) {
|
|
715
|
+
// (it would be nicer if we could store backOff state in `state.started`,
|
|
716
|
+
// however resource.stop clears `started` and we need the settings to persist over start-->error-->stop-->start cycles
|
|
717
|
+
let entries = Object.values(state.inError || {});
|
|
718
|
+
|
|
719
|
+
if (type) { // Filter out ones for types we're no interested in
|
|
720
|
+
entries = entries
|
|
721
|
+
.filter((error) => compareWatches ? compareWatches(error.obj) : error.obj.type === type);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
entries
|
|
725
|
+
.filter((error) => error.reason === REVISION_TOO_OLD) // Filter out ones for reasons we're not interested in
|
|
726
|
+
.forEach((error) => clearInError({ getters, commit }, error));
|
|
727
|
+
}
|
|
607
728
|
},
|
|
608
729
|
|
|
609
730
|
'ws.ping'({ getters, dispatch }, msg) {
|
|
@@ -863,15 +984,20 @@ const defaultActions = {
|
|
|
863
984
|
}
|
|
864
985
|
},
|
|
865
986
|
|
|
866
|
-
closed({ state, getters }) {
|
|
987
|
+
async closed({ state, getters, dispatch }) {
|
|
867
988
|
state.debugSocket && console.info(`WebSocket Closed [${ getters.storeName }]`); // eslint-disable-line no-console
|
|
989
|
+
|
|
990
|
+
await dispatch('resetWatchBackOff');
|
|
868
991
|
clearTimeout(state.queueTimer);
|
|
869
992
|
state.queueTimer = null;
|
|
870
993
|
},
|
|
871
994
|
|
|
872
|
-
error({
|
|
995
|
+
async error({
|
|
873
996
|
getters, state, dispatch, rootGetters
|
|
874
997
|
}, e) {
|
|
998
|
+
state.debugSocket && console.info(`WebSocket Error [${ getters.storeName }]`); // eslint-disable-line no-console
|
|
999
|
+
|
|
1000
|
+
await dispatch('resetWatchBackOff');
|
|
875
1001
|
clearTimeout(state.queueTimer);
|
|
876
1002
|
state.queueTimer = null;
|
|
877
1003
|
|
|
@@ -986,7 +1112,23 @@ const defaultActions = {
|
|
|
986
1112
|
// 1) blocks attempts by resource.stop to resub (as type is in error)
|
|
987
1113
|
// 2) will be cleared when resyncWatch --> watch (with force) --> resource.start completes
|
|
988
1114
|
commit('setInError', { msg, reason: REVISION_TOO_OLD });
|
|
989
|
-
|
|
1115
|
+
|
|
1116
|
+
// See Scenario 1 from https://github.com/rancher/dashboard/issues/14974
|
|
1117
|
+
// The watch that results from resyncWatch will fail and end up here if the revision isn't (yet) known
|
|
1118
|
+
// So re-retry resyncWatch until it does OR
|
|
1119
|
+
// - we're already re-retrying
|
|
1120
|
+
// - early exist from `execute`
|
|
1121
|
+
// - we give up (exceed max retries)
|
|
1122
|
+
// - early exist from `execute`
|
|
1123
|
+
// - we need to stop (socket is disconnected or closed, type is 'forgotten', watch is unwatched)
|
|
1124
|
+
// - `reset` called asynchronously
|
|
1125
|
+
// - Note - we won't need to clear the id outside of the above scenarios because `too old` only occurs on fresh watches (covered by above scenarios)
|
|
1126
|
+
backOff.execute({
|
|
1127
|
+
id: getters.backOffId(msg, REVISION_TOO_OLD),
|
|
1128
|
+
description: `Invalid watch revision, re-syncing`,
|
|
1129
|
+
canFn: () => getters.canBackoff(this.$socket),
|
|
1130
|
+
delayedFn: () => dispatch('resyncWatch', msg),
|
|
1131
|
+
});
|
|
990
1132
|
} else if ( err.includes('the server does not allow this method on the requested resource')) {
|
|
991
1133
|
commit('setInError', { msg, reason: NO_PERMS });
|
|
992
1134
|
}
|
|
@@ -1170,21 +1312,33 @@ const defaultMutations = {
|
|
|
1170
1312
|
setInError(state, { msg, reason }) {
|
|
1171
1313
|
const key = keyForSubscribe(msg);
|
|
1172
1314
|
|
|
1173
|
-
|
|
1315
|
+
const { data, resourceType, ...obj } = msg;
|
|
1316
|
+
|
|
1317
|
+
obj.type = msg.resourceType || msg.type;
|
|
1318
|
+
|
|
1319
|
+
state.inError[key] = { obj, reason };
|
|
1174
1320
|
},
|
|
1175
1321
|
|
|
1176
1322
|
clearInError(state, msg) {
|
|
1323
|
+
// Callers of this should consider using local clearInError instead
|
|
1324
|
+
|
|
1177
1325
|
const key = keyForSubscribe(msg);
|
|
1178
1326
|
|
|
1179
1327
|
delete state.inError[key];
|
|
1180
1328
|
},
|
|
1181
1329
|
|
|
1330
|
+
/**
|
|
1331
|
+
* Clear out socket state
|
|
1332
|
+
*/
|
|
1182
1333
|
resetSubscriptions(state) {
|
|
1183
|
-
// Clear out socket state. This is only ever called from reset... which is always called after we `disconnect` above.
|
|
1184
|
-
// This could probably be folded in to there
|
|
1185
1334
|
clear(state.started);
|
|
1186
1335
|
clear(state.pendingFrames);
|
|
1187
1336
|
clear(state.queue);
|
|
1337
|
+
// Note - we clear async operations here (like queueTimer) and we should also do so for backoff requests via
|
|
1338
|
+
// resetWatchBackOff, however can't because this is a mutation and it's an action
|
|
1339
|
+
// We shouldn't need to though given resetSubscription is called from store reset, which includes forgetType
|
|
1340
|
+
// on everything in the store, which resets backoff requests.
|
|
1341
|
+
// Additionally this is probably called on a cluster store, so we also call resetWatchBackOff when the socket disconnects
|
|
1188
1342
|
clearTimeout(state.queueTimer);
|
|
1189
1343
|
state.deferredRequests = {};
|
|
1190
1344
|
state.queueTimer = null;
|
|
@@ -1202,8 +1356,27 @@ const defaultMutations = {
|
|
|
1202
1356
|
* Getters that cover cases 1 & 2 (see file description)
|
|
1203
1357
|
*/
|
|
1204
1358
|
const defaultGetters = {
|
|
1359
|
+
/**
|
|
1360
|
+
* Get a unique id that can be used to track a process that can be backed-off
|
|
1361
|
+
*
|
|
1362
|
+
* @param obj - the usual id/namespace/selector, etc,
|
|
1363
|
+
* @param postFix - something else to uniquely id this back-off
|
|
1364
|
+
*/
|
|
1365
|
+
backOffId: () => (obj, postFix) => {
|
|
1366
|
+
return `${ keyForSubscribe(obj) }${ postFix ? `:${ postFix }` : '' }`;
|
|
1367
|
+
},
|
|
1368
|
+
|
|
1369
|
+
/**
|
|
1370
|
+
* Can the back off process run?
|
|
1371
|
+
*
|
|
1372
|
+
* If we're not connected no.
|
|
1373
|
+
*/
|
|
1374
|
+
canBackoff: () => ($socket) => {
|
|
1375
|
+
return $socket.state === EVENT_CONNECTED;
|
|
1376
|
+
},
|
|
1377
|
+
|
|
1205
1378
|
inError: (state) => (obj) => {
|
|
1206
|
-
return state.inError[keyForSubscribe(obj)];
|
|
1379
|
+
return state.inError[keyForSubscribe(obj)]?.reason;
|
|
1207
1380
|
},
|
|
1208
1381
|
|
|
1209
1382
|
watchesOfType: (state) => (type) => {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* relocates cluster resource sockets off the UI thread and into a webworker
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
// Status of this is TBD - https://github.com/rancher/dashboard/issues/15111
|
|
7
|
+
|
|
6
8
|
import { SCHEMA, COUNT } from '@shell/config/types';
|
|
7
9
|
import ResourceWatcher, { watchKeyFromMessage } from '@shell/plugins/steve/resourceWatcher';
|
|
8
10
|
import ResourceCache from '@shell/plugins/steve/caches/resourceCache';
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent, PropType } from 'vue';
|
|
3
|
-
import { useBasicSetupFocusTrap } from '@shell/composables/focusTrap';
|
|
4
3
|
|
|
5
4
|
export default defineComponent({
|
|
6
5
|
|
|
@@ -51,23 +50,6 @@ export default defineComponent({
|
|
|
51
50
|
sticky: {
|
|
52
51
|
type: Boolean,
|
|
53
52
|
default: false,
|
|
54
|
-
},
|
|
55
|
-
triggerFocusTrap: {
|
|
56
|
-
type: Boolean,
|
|
57
|
-
default: false,
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
setup(props) {
|
|
61
|
-
if (props.triggerFocusTrap) {
|
|
62
|
-
useBasicSetupFocusTrap('#focus-trap-card-container-element', {
|
|
63
|
-
// needs to be false because of import YAML modal from header
|
|
64
|
-
// where the YAML editor itself is a focus trap
|
|
65
|
-
// and we can't have it superseed the "escape key" to blur that UI element
|
|
66
|
-
// In this case the focus trap moves the focus out of the modal
|
|
67
|
-
// correctly once it closes because of the "onBeforeUnmount" trigger
|
|
68
|
-
escapeDeactivates: false,
|
|
69
|
-
allowOutsideClick: true,
|
|
70
|
-
});
|
|
71
53
|
}
|
|
72
54
|
}
|
|
73
55
|
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RcStatusBadge from './index';
|
|
3
|
+
import { Status } from '@components/Pill/types';
|
|
4
|
+
|
|
5
|
+
describe('component: RcStatusBadge', () => {
|
|
6
|
+
const statuses: Status[] = ['info', 'success', 'warning', 'error', 'unknown', 'none'];
|
|
7
|
+
|
|
8
|
+
it.each(statuses)('should apply correct classes for shape and status "%s"', (status) => {
|
|
9
|
+
const wrapper = mount(RcStatusBadge, { props: { status } });
|
|
10
|
+
|
|
11
|
+
const shapeEl = wrapper.find('.rc-status-badge');
|
|
12
|
+
|
|
13
|
+
expect(shapeEl.classes()).toContain(status);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { RcStatusBadgeProps } from '@components/Pill/RcStatusBadge/types';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<RcStatusBadgeProps>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div
|
|
9
|
+
class="rc-status-badge"
|
|
10
|
+
:class="{[props.status]: true}"
|
|
11
|
+
>
|
|
12
|
+
<slot name="default" />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style lang="scss" scoped>
|
|
17
|
+
.rc-status-badge {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
padding: 1px 7px;
|
|
22
|
+
|
|
23
|
+
border: 1px solid transparent;
|
|
24
|
+
border-radius: 30px;
|
|
25
|
+
|
|
26
|
+
font-family: Lato;
|
|
27
|
+
font-size: 12px;
|
|
28
|
+
line-height: 19px;
|
|
29
|
+
|
|
30
|
+
&.info {
|
|
31
|
+
background-color: var(--rc-info-secondary);
|
|
32
|
+
border-color: var(--rc-info-secondary);
|
|
33
|
+
color: var(--rc-info);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&.success {
|
|
37
|
+
background-color: var(--rc-success-secondary);
|
|
38
|
+
border-color: var(--rc-success-secondary);
|
|
39
|
+
color: var(--rc-success);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
&.warning {
|
|
43
|
+
background-color: var(--rc-warning);
|
|
44
|
+
border-color: var(--rc-warning);
|
|
45
|
+
color: var(--rc-warning-secondary);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.error {
|
|
49
|
+
background-color: var(--rc-error);
|
|
50
|
+
border-color: var(--rc-error);
|
|
51
|
+
color: var(--rc-error-secondary);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&.unknown {
|
|
55
|
+
background-color: var(--rc-unknown);
|
|
56
|
+
border-color: var(--rc-unknown);
|
|
57
|
+
color: var(--rc-unknown-secondary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&.none {
|
|
61
|
+
border-color: var(--rc-none);
|
|
62
|
+
color: var(--rc-none-secondary);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
</style>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RcStatusIndicator, { Shape } from './index';
|
|
3
|
+
import { Status } from '@components/Pill/types';
|
|
4
|
+
|
|
5
|
+
describe('component: RcStatusIndicator', () => {
|
|
6
|
+
const shapes: Shape[] = ['disc', 'horizontal-bar', 'vertical-bar'];
|
|
7
|
+
const statuses: Status[] = ['info', 'success', 'warning', 'error', 'unknown', 'none'];
|
|
8
|
+
|
|
9
|
+
const combinations: {shape: Shape, status: Status}[] = [];
|
|
10
|
+
|
|
11
|
+
shapes.forEach((shape) => {
|
|
12
|
+
statuses.forEach((status) => {
|
|
13
|
+
combinations.push({
|
|
14
|
+
shape,
|
|
15
|
+
status
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it.each(combinations)('should apply correct classes for shape "$shape" and status "$status"', ({ shape, status }) => {
|
|
21
|
+
const wrapper = mount(RcStatusIndicator, {
|
|
22
|
+
props: {
|
|
23
|
+
shape,
|
|
24
|
+
status,
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const shapeEl = wrapper.find('.shape');
|
|
29
|
+
|
|
30
|
+
expect(shapeEl.classes()).toContain(shape);
|
|
31
|
+
expect(shapeEl.classes()).toContain(status);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { RcStatusIndicatorProps } from '@components/Pill/RcStatusIndicator/types';
|
|
3
|
+
|
|
4
|
+
const props = defineProps<RcStatusIndicatorProps>();
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<div class="rc-status-indicator">
|
|
9
|
+
<div
|
|
10
|
+
class="shape"
|
|
11
|
+
:class="{[props.shape]: true, [props.status]: true}"
|
|
12
|
+
/>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<style lang="scss" scoped>
|
|
17
|
+
.rc-status-indicator {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
height: 21px;
|
|
22
|
+
|
|
23
|
+
.shape {
|
|
24
|
+
display: inline-block;
|
|
25
|
+
border: 1px solid transparent;
|
|
26
|
+
|
|
27
|
+
&.disc {
|
|
28
|
+
width: 6px;
|
|
29
|
+
height: 6px;
|
|
30
|
+
border-radius: 50%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&.horizontal-bar {
|
|
34
|
+
width: 16px;
|
|
35
|
+
height: 4px;
|
|
36
|
+
border-radius: 2px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
&.vertical-bar {
|
|
40
|
+
width: 4px;
|
|
41
|
+
height: 16px;
|
|
42
|
+
border-radius: 2px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
&.info {
|
|
46
|
+
background-color: var(--rc-info);
|
|
47
|
+
border-color: var(--rc-info);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.success {
|
|
51
|
+
background-color: var(--rc-success);
|
|
52
|
+
border-color: var(--rc-success);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
&.warning {
|
|
56
|
+
background-color: var(--rc-warning);
|
|
57
|
+
border-color: var(--rc-warning);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&.error {
|
|
61
|
+
background-color: var(--rc-error);
|
|
62
|
+
border-color: var(--rc-error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
&.unknown {
|
|
66
|
+
background-color: var(--rc-unknown);
|
|
67
|
+
border-color: var(--rc-unknown);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&.none {
|
|
71
|
+
border-color: var(--rc-none);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
</style>
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* <rc-button primary @click="doAction">Perform an Action</rc-button>
|
|
9
9
|
*/
|
|
10
|
-
import { computed, ref
|
|
10
|
+
import { computed, ref } from 'vue';
|
|
11
11
|
import { ButtonRoleProps, ButtonSizeProps } from './types';
|
|
12
12
|
|
|
13
13
|
const buttonRoles: { role: keyof ButtonRoleProps, className: string }[] = [
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import { defineComponent } from 'vue';
|
|
3
|
+
import { RcDropdown } from '@components/RcDropdown';
|
|
4
|
+
|
|
5
|
+
const vDropdownMock = defineComponent({
|
|
6
|
+
template: `
|
|
7
|
+
<div class="popper">
|
|
8
|
+
<slot name="popper" />
|
|
9
|
+
</div>
|
|
10
|
+
`,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe('component: RcDropdown.vue', () => {
|
|
14
|
+
it('should not change the height if the dropdown fits within the screen', async() => {
|
|
15
|
+
Object.defineProperty(window, 'innerHeight', { value: 800 });
|
|
16
|
+
|
|
17
|
+
const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
|
|
18
|
+
|
|
19
|
+
const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
|
|
20
|
+
|
|
21
|
+
Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
|
|
22
|
+
value: () => ({
|
|
23
|
+
top: 200,
|
|
24
|
+
bottom: 600,
|
|
25
|
+
height: 400,
|
|
26
|
+
}),
|
|
27
|
+
writable: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
|
|
31
|
+
await wrapper.vm.$nextTick();
|
|
32
|
+
|
|
33
|
+
expect(dropdownTarget.style.height).toBe('');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should apply correct height if dropdown exceeds the top edge', async() => {
|
|
37
|
+
Object.defineProperty(window, 'innerHeight', { value: 800 });
|
|
38
|
+
|
|
39
|
+
const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
|
|
40
|
+
|
|
41
|
+
const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
|
|
42
|
+
|
|
43
|
+
Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
|
|
44
|
+
value: () => ({
|
|
45
|
+
top: 2, // Exceeds (top - padding)
|
|
46
|
+
bottom: 300,
|
|
47
|
+
height: 298,
|
|
48
|
+
}),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
|
|
52
|
+
await wrapper.vm.$nextTick();
|
|
53
|
+
|
|
54
|
+
expect(dropdownTarget.style.height).toBe('268px');
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should apply correct height if dropdown exceeds the bottom edge', async() => {
|
|
58
|
+
Object.defineProperty(window, 'innerHeight', { value: 925 });
|
|
59
|
+
|
|
60
|
+
const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
|
|
61
|
+
|
|
62
|
+
const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
|
|
63
|
+
|
|
64
|
+
Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
|
|
65
|
+
value: () => ({
|
|
66
|
+
top: 200,
|
|
67
|
+
bottom: 920, // Exceeds (bottom + padding)
|
|
68
|
+
height: 720,
|
|
69
|
+
}),
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
|
|
73
|
+
await wrapper.vm.$nextTick();
|
|
74
|
+
|
|
75
|
+
expect(dropdownTarget.style.height).toBe('693px');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('should apply correct height if dropdown exceeds both top and bottom edges', async() => {
|
|
79
|
+
Object.defineProperty(window, 'innerHeight', { value: 400 });
|
|
80
|
+
|
|
81
|
+
const wrapper = mount(RcDropdown, { global: { components: { 'v-dropdown': vDropdownMock } } });
|
|
82
|
+
|
|
83
|
+
const dropdownTarget = wrapper.find('[dropdown-menu-collection]').element as HTMLElement;
|
|
84
|
+
|
|
85
|
+
Object.defineProperty(dropdownTarget, 'getBoundingClientRect', {
|
|
86
|
+
value: () => ({
|
|
87
|
+
top: -800, // Exceeds top
|
|
88
|
+
bottom: 800, // Exceeds bottom
|
|
89
|
+
height: 1600,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await wrapper.findComponent(vDropdownMock).vm.$emit('apply-show');
|
|
94
|
+
await wrapper.vm.$nextTick();
|
|
95
|
+
|
|
96
|
+
expect(dropdownTarget.style.height).toBe('368px');
|
|
97
|
+
});
|
|
98
|
+
});
|