@rancher/shell 3.0.5-rc.7 → 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 +1 -1
- package/assets/styles/fonts/_icons.scss +1 -32
- package/assets/styles/global/_layout.scss +1 -1
- package/assets/styles/themes/_dark.scss +262 -260
- package/assets/styles/themes/_light.scss +538 -515
- package/assets/styles/themes/_modern.scss +914 -0
- package/assets/translations/en-us.yaml +84 -25
- 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/Drawer/ResourceDetailDrawer/__tests__/composables.test.ts +1 -26
- package/components/Drawer/ResourceDetailDrawer/composables.ts +0 -23
- package/components/Drawer/ResourceDetailDrawer/index.vue +17 -4
- package/components/InstallHelmCharts.vue +656 -0
- package/components/LazyImage.vue +60 -4
- package/components/LocaleSelector.vue +7 -2
- package/components/Markdown.vue +4 -0
- package/components/Resource/Detail/Masthead/composable.ts +16 -0
- package/components/Resource/Detail/Masthead/index.vue +37 -0
- package/components/Resource/Detail/Metadata/IdentifyingInformation/identifying-fields.ts +5 -5
- 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/TitleBar/__tests__/composables.test.ts +8 -9
- package/components/Resource/Detail/TitleBar/composables.ts +2 -3
- package/components/Resource/Detail/TitleBar/index.vue +10 -1
- package/components/ResourceDetail/index.vue +569 -74
- package/components/SlideInPanelManager.vue +10 -3
- package/components/SortableTable/index.vue +4 -4
- package/components/Tabbed/index.vue +29 -3
- package/components/__tests__/LazyImage.spec.ts +121 -0
- package/components/fleet/FleetStatus.vue +4 -0
- package/components/form/ClusterAppearance.vue +5 -0
- package/components/form/Members/ClusterPermissionsEditor.vue +1 -1
- package/components/form/ProjectMemberEditor.vue +1 -1
- package/components/form/ResourceLabeledSelect.vue +19 -6
- package/components/form/ResourceTabs/index.vue +20 -0
- package/components/form/SecretSelector.vue +9 -0
- package/components/form/labeled-select-utils/labeled-select-pagination.ts +3 -38
- package/components/formatter/FleetApplicationSource.vue +25 -17
- package/components/nav/Favorite.vue +4 -0
- package/components/nav/NotificationCenter/Notification.vue +1 -27
- package/components/nav/WindowManager/index.vue +3 -3
- package/config/labels-annotations.js +1 -2
- 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 +350 -324
- package/detail/secret.vue +49 -308
- package/detail/workload/index.vue +38 -21
- package/dialog/InstallExtensionDialog.vue +8 -5
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +224 -0
- 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 +9 -11
- 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.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/rke2.vue +21 -13
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +9 -7
- 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 +30 -31
- 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/workload/index.vue +5 -14
- package/list/provisioning.cattle.io.cluster.vue +1 -69
- 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/chart.js +0 -2
- package/mixins/resource-fetch-api-pagination.js +3 -4
- 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/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.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 +16 -40
- package/models/rke.cattle.io.etcdsnapshot.js +1 -1
- package/models/secret.js +4 -0
- 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/install.vue +1 -1
- package/pages/c/_cluster/explorer/projectsecret.vue +3 -13
- package/pages/c/_cluster/fleet/__tests__/index.test.ts +608 -314
- package/pages/c/_cluster/fleet/index.vue +103 -44
- 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/actions.js +42 -22
- package/plugins/dashboard-store/resource-class.js +31 -0
- 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/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/resources/settings.d.ts +1 -1
- package/types/shell/index.d.ts +28 -28
- package/types/uiplugins.ts +73 -0
- package/utils/__tests__/back-off.test.ts +354 -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/dynamic-importer.js +8 -0
- package/utils/kontainer.ts +3 -5
- 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/pages/explorer/resource/detail/configmap.vue +0 -42
- package/pages/explorer/resource/detail/projectsecret.vue +0 -9
- package/pages/explorer/resource/detail/secret.vue +0 -63
- package/utils/aws.js +0 -0
|
@@ -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
|
+
});
|
|
@@ -47,6 +47,7 @@ const {
|
|
|
47
47
|
provideDropdownContext,
|
|
48
48
|
registerDropdownCollection,
|
|
49
49
|
handleKeydown,
|
|
50
|
+
setDropdownDimensions
|
|
50
51
|
} = useDropdownContext(emit);
|
|
51
52
|
|
|
52
53
|
provideDropdownContext();
|
|
@@ -57,6 +58,7 @@ const dropdownTarget = ref(null);
|
|
|
57
58
|
useClickOutside(dropdownTarget, () => showMenu(false));
|
|
58
59
|
|
|
59
60
|
const applyShow = () => {
|
|
61
|
+
setDropdownDimensions(dropdownTarget.value);
|
|
60
62
|
registerDropdownCollection(dropdownTarget.value);
|
|
61
63
|
setFocus('down');
|
|
62
64
|
};
|
|
@@ -129,6 +131,9 @@ const applyShow = () => {
|
|
|
129
131
|
}
|
|
130
132
|
|
|
131
133
|
.dropdownTarget {
|
|
134
|
+
overflow: auto;
|
|
135
|
+
padding: 3px 0; // Need padding at top and bottom in order to show the focus border for the notification
|
|
136
|
+
|
|
132
137
|
&:focus-visible, &:focus {
|
|
133
138
|
outline: none;
|
|
134
139
|
}
|
|
@@ -7,7 +7,12 @@ import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
|
|
|
7
7
|
const props = defineProps({ disabled: Boolean });
|
|
8
8
|
const emits = defineEmits(['click']);
|
|
9
9
|
|
|
10
|
-
const {
|
|
10
|
+
const {
|
|
11
|
+
handleKeydown,
|
|
12
|
+
close,
|
|
13
|
+
handleActivate,
|
|
14
|
+
scrollIntoView,
|
|
15
|
+
} = useDropdownItem();
|
|
11
16
|
|
|
12
17
|
const handleClick = (e: MouseEvent) => {
|
|
13
18
|
if (props.disabled) {
|
|
@@ -31,6 +36,7 @@ const handleClick = (e: MouseEvent) => {
|
|
|
31
36
|
@click.stop="handleClick"
|
|
32
37
|
@keydown.enter.space="handleActivate"
|
|
33
38
|
@keydown.up.down.prevent.stop="handleKeydown"
|
|
39
|
+
@focusin="scrollIntoView"
|
|
34
40
|
>
|
|
35
41
|
<slot name="before">
|
|
36
42
|
<!--Empty slot content-->
|
|
@@ -8,7 +8,7 @@ import { useDropdownItem } from '@components/RcDropdown/useDropdownItem';
|
|
|
8
8
|
const props = defineProps({ modelValue: Boolean, disabled: Boolean });
|
|
9
9
|
const emits = defineEmits(['click']);
|
|
10
10
|
|
|
11
|
-
const { handleKeydown, handleActivate } = useDropdownItem();
|
|
11
|
+
const { handleKeydown, handleActivate, scrollIntoView } = useDropdownItem();
|
|
12
12
|
|
|
13
13
|
const handleClick = () => {
|
|
14
14
|
if (props.disabled) {
|
|
@@ -30,6 +30,7 @@ const handleClick = () => {
|
|
|
30
30
|
@click.stop="handleClick"
|
|
31
31
|
@keydown.enter.space="handleActivate"
|
|
32
32
|
@keydown.up.down.prevent.stop="handleKeydown"
|
|
33
|
+
@focusin="scrollIntoView"
|
|
33
34
|
>
|
|
34
35
|
<rc-checkbox :value="modelValue">
|
|
35
36
|
<template #label>
|
|
@@ -25,7 +25,7 @@ defineProps({
|
|
|
25
25
|
});
|
|
26
26
|
const emits = defineEmits(['click', 'select']);
|
|
27
27
|
|
|
28
|
-
const { handleKeydown, handleActivate } = useDropdownItem();
|
|
28
|
+
const { handleKeydown, handleActivate, scrollIntoView } = useDropdownItem();
|
|
29
29
|
|
|
30
30
|
const dropdownMenuItem = ref<HTMLDivElement | null>(null);
|
|
31
31
|
const menuItemSelect = ref<LabeledSelectComponent | null>(null);
|
|
@@ -50,6 +50,7 @@ const focusMenuItem = () => {
|
|
|
50
50
|
@click.stop="handleClick"
|
|
51
51
|
@keydown.enter.space="handleActivate"
|
|
52
52
|
@keydown.up.down.prevent.stop="handleKeydown"
|
|
53
|
+
@focusin="scrollIntoView"
|
|
53
54
|
>
|
|
54
55
|
<LabeledSelect
|
|
55
56
|
ref="menuItemSelect"
|
|
@@ -89,6 +89,26 @@ export const useDropdownContext = (emit: typeof rcDropdownEmits) => {
|
|
|
89
89
|
});
|
|
90
90
|
};
|
|
91
91
|
|
|
92
|
+
const setDropdownDimensions = (target: HTMLElement | null) => {
|
|
93
|
+
if (!target) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { top, bottom } = target.getBoundingClientRect();
|
|
98
|
+
const padding = 32;
|
|
99
|
+
|
|
100
|
+
// The dropdown exceeds the top or bottom edge of the screen (or both).
|
|
101
|
+
if (top - padding < 0 || bottom + padding > window.innerHeight) {
|
|
102
|
+
const height = Math.min(
|
|
103
|
+
bottom,
|
|
104
|
+
window.innerHeight - top,
|
|
105
|
+
window.innerHeight
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
target.style.height = `${ height - padding }px`;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
92
112
|
/**
|
|
93
113
|
* Provides Dropdown Context data and methods to descendants of RcDropdown.
|
|
94
114
|
* Accessed in descendents with the `inject()` function.
|
|
@@ -115,5 +135,6 @@ export const useDropdownContext = (emit: typeof rcDropdownEmits) => {
|
|
|
115
135
|
provideDropdownContext,
|
|
116
136
|
registerDropdownCollection,
|
|
117
137
|
handleKeydown,
|
|
138
|
+
setDropdownDimensions,
|
|
118
139
|
};
|
|
119
140
|
};
|
|
@@ -57,7 +57,36 @@ export const useDropdownItem = () => {
|
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Scroll the item into view smoothly
|
|
62
|
+
* @param event FocusIn Event
|
|
63
|
+
*/
|
|
64
|
+
const scrollIntoView = (event: Event) => {
|
|
65
|
+
const target = event.target;
|
|
66
|
+
|
|
67
|
+
if (!(target instanceof HTMLElement)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const t = target as HTMLElement;
|
|
72
|
+
|
|
73
|
+
// If a button was clicked, then do not scroll into view, as this will scroll to make the button
|
|
74
|
+
// visible and the click will be ignored - so just return, so that the click works as expected
|
|
75
|
+
if (t.tagName === 'BUTTON') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
target?.scrollIntoView({
|
|
80
|
+
behavior: 'smooth',
|
|
81
|
+
block: 'center',
|
|
82
|
+
inline: 'nearest',
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
|
|
60
86
|
return {
|
|
61
|
-
handleKeydown,
|
|
87
|
+
handleKeydown,
|
|
88
|
+
close,
|
|
89
|
+
handleActivate,
|
|
90
|
+
scrollIntoView,
|
|
62
91
|
};
|
|
63
92
|
};
|
|
@@ -186,4 +186,24 @@ describe('rcItemCard', () => {
|
|
|
186
186
|
|
|
187
187
|
expect(icon.attributes('style')).toContain('color: red');
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
it('emits custom action events correctly', async() => {
|
|
191
|
+
const wrapper = mount(RcItemCard, {
|
|
192
|
+
props: {
|
|
193
|
+
...baseProps,
|
|
194
|
+
actions: [
|
|
195
|
+
{ action: 'myActionA', label: 'Edit' },
|
|
196
|
+
{ action: 'myActionB', label: 'Delete' }
|
|
197
|
+
]
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const listeners = wrapper.vm.$.setupState.actionListeners;
|
|
202
|
+
|
|
203
|
+
listeners.myActionA('payload-1');
|
|
204
|
+
listeners.myActionB('payload-2');
|
|
205
|
+
|
|
206
|
+
expect(wrapper.emitted('myActionA')?.[0]).toStrictEqual(['payload-1']);
|
|
207
|
+
expect(wrapper.emitted('myActionB')?.[0]).toStrictEqual(['payload-2']);
|
|
208
|
+
});
|
|
189
209
|
});
|
|
@@ -5,6 +5,7 @@ import { useI18n } from '@shell/composables/useI18n';
|
|
|
5
5
|
import LazyImage from '@shell/components/LazyImage.vue';
|
|
6
6
|
import { DropdownOption } from '@components/RcDropdown/types';
|
|
7
7
|
import ActionMenu from '@shell/components/ActionMenuShell.vue';
|
|
8
|
+
import RcItemCardAction from './RcItemCardAction';
|
|
8
9
|
|
|
9
10
|
const store = useStore();
|
|
10
11
|
const { t } = useI18n(store);
|
|
@@ -79,7 +80,23 @@ interface RcItemCardProps {
|
|
|
79
80
|
/** Optional image to show in card (position depends on variant). A slot is available for it too #item-card-image */
|
|
80
81
|
image?: Image;
|
|
81
82
|
|
|
82
|
-
/** Optional actions that will be displayed inside an action-menu
|
|
83
|
+
/** Optional actions that will be displayed inside an action-menu
|
|
84
|
+
*
|
|
85
|
+
* Each action should include an `action` name, which is emitted as a custom event when selected.
|
|
86
|
+
* To respond to the event, you must also register a matching event listener using the `@` syntax.
|
|
87
|
+
*
|
|
88
|
+
* Example:
|
|
89
|
+
* <rc-item-card
|
|
90
|
+
* :actions="[
|
|
91
|
+
* {
|
|
92
|
+
* action: 'focusSearch',
|
|
93
|
+
* label: t('catalog.charts.search'),
|
|
94
|
+
* enabled: true
|
|
95
|
+
* }
|
|
96
|
+
* ]"
|
|
97
|
+
* @focusSearch="focusSearch"
|
|
98
|
+
* />
|
|
99
|
+
*/
|
|
83
100
|
actions?: DropdownOption[];
|
|
84
101
|
|
|
85
102
|
/** Text content inside the card body. A slot is available for it too #item-card-content */
|
|
@@ -98,9 +115,25 @@ interface RcItemCardProps {
|
|
|
98
115
|
const props = defineProps<RcItemCardProps>();
|
|
99
116
|
|
|
100
117
|
/**
|
|
101
|
-
* Emits
|
|
118
|
+
* Emits:
|
|
119
|
+
* - 'card-click' when card is clicked or activated via keyboard.
|
|
120
|
+
* - custom events defined in the `actions` prop, but only if the corresponding event listener is explicitly declared on the component.
|
|
102
121
|
*/
|
|
103
|
-
const emit = defineEmits<{(
|
|
122
|
+
const emit = defineEmits<{(e: 'card-click', value: ItemValue): void; (e: string, payload: unknown): void;}>();
|
|
123
|
+
|
|
124
|
+
const actionListeners = computed(() => {
|
|
125
|
+
if (!props.actions) return {};
|
|
126
|
+
|
|
127
|
+
const listeners: Record<string, (payload: unknown) => void> = {};
|
|
128
|
+
|
|
129
|
+
for (const a of props.actions) {
|
|
130
|
+
if (a.action) {
|
|
131
|
+
listeners[a.action] = (payload: unknown) => emit(a.action, payload);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return listeners;
|
|
136
|
+
});
|
|
104
137
|
|
|
105
138
|
/**
|
|
106
139
|
* Handles the card click while avoiding nested interactive elements
|
|
@@ -245,12 +278,13 @@ const cardMeta = computed(() => ({
|
|
|
245
278
|
</div>
|
|
246
279
|
</template>
|
|
247
280
|
<template v-else-if="actions">
|
|
248
|
-
<
|
|
281
|
+
<rc-item-card-action class="item-card-header-action-menu">
|
|
249
282
|
<ActionMenu
|
|
250
283
|
data-testid="item-card-header-action-menu"
|
|
251
284
|
:custom-actions="actions"
|
|
285
|
+
v-on="actionListeners"
|
|
252
286
|
/>
|
|
253
|
-
</
|
|
287
|
+
</rc-item-card-action>
|
|
254
288
|
</template>
|
|
255
289
|
</div>
|
|
256
290
|
</div>
|
|
@@ -370,7 +404,7 @@ $image-medium-box-width: 48px;
|
|
|
370
404
|
}
|
|
371
405
|
|
|
372
406
|
&-action-menu {
|
|
373
|
-
margin-left:
|
|
407
|
+
margin-left: 8px;
|
|
374
408
|
}
|
|
375
409
|
}
|
|
376
410
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { CATALOG } from '@shell/config/types';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
state, getters, actions, mutations, filterAndArrangeCharts
|
|
4
|
+
} from '../catalog';
|
|
3
5
|
import { createStore } from 'vuex';
|
|
4
6
|
|
|
5
7
|
const clusterRepo = { _key: 'testClusterRepo' };
|
|
@@ -204,4 +206,94 @@ describe('catalog', () => {
|
|
|
204
206
|
expect(store.state[catalogStoreName].versionInfos).toStrictEqual({ });
|
|
205
207
|
});
|
|
206
208
|
});
|
|
209
|
+
|
|
210
|
+
describe('filterAndArrangeCharts', () => {
|
|
211
|
+
const mockCharts = [
|
|
212
|
+
{
|
|
213
|
+
chartName: 'chart-a',
|
|
214
|
+
chartNameDisplay: 'Chart Alpha',
|
|
215
|
+
chartDescription: 'Description for Alpha',
|
|
216
|
+
keywords: ['keyword1', 'keyword2'],
|
|
217
|
+
versions: [{ annotations: {}, version: '1.0.0' }],
|
|
218
|
+
deprecated: false,
|
|
219
|
+
hidden: false,
|
|
220
|
+
repoKey: 'repo1',
|
|
221
|
+
chartType: 'app',
|
|
222
|
+
categories: [],
|
|
223
|
+
tags: [],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
chartName: 'chart-b',
|
|
227
|
+
chartNameDisplay: 'Chart Beta',
|
|
228
|
+
chartDescription: 'Description for Beta',
|
|
229
|
+
keywords: ['keyword2', 'keyword3'],
|
|
230
|
+
versions: [{ annotations: {}, version: '1.0.0' }],
|
|
231
|
+
deprecated: false,
|
|
232
|
+
hidden: false,
|
|
233
|
+
repoKey: 'repo1',
|
|
234
|
+
chartType: 'app',
|
|
235
|
+
categories: [],
|
|
236
|
+
tags: [],
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
chartName: 'chart-c',
|
|
240
|
+
chartNameDisplay: 'Chart Gamma',
|
|
241
|
+
chartDescription: 'Description for Gamma',
|
|
242
|
+
keywords: ['keyword3', 'keyword4'],
|
|
243
|
+
versions: [{ annotations: {}, version: '1.0.0' }],
|
|
244
|
+
deprecated: false,
|
|
245
|
+
hidden: false,
|
|
246
|
+
repoKey: 'repo1',
|
|
247
|
+
chartType: 'app',
|
|
248
|
+
categories: [],
|
|
249
|
+
tags: [],
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
it('should return all charts when no search query is provided', () => {
|
|
254
|
+
const result = filterAndArrangeCharts(mockCharts, {});
|
|
255
|
+
|
|
256
|
+
expect(result).toHaveLength(mockCharts.length);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should filter charts by name', () => {
|
|
260
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'Chart Alpha' });
|
|
261
|
+
|
|
262
|
+
expect(result).toHaveLength(1);
|
|
263
|
+
expect(result[0].chartNameDisplay).toBe('Chart Alpha');
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it('should filter charts by description', () => {
|
|
267
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'Description for Beta' });
|
|
268
|
+
|
|
269
|
+
expect(result).toHaveLength(1);
|
|
270
|
+
expect(result[0].chartNameDisplay).toBe('Chart Beta');
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('should filter charts by keyword', () => {
|
|
274
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'keyword1' });
|
|
275
|
+
|
|
276
|
+
expect(result).toHaveLength(1);
|
|
277
|
+
expect(result[0].chartNameDisplay).toBe('Chart Alpha');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should handle multiple search tokens', () => {
|
|
281
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'Chart, keyword2' });
|
|
282
|
+
|
|
283
|
+
expect(result).toHaveLength(2);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('should be case-insensitive', () => {
|
|
287
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'chart alpha' });
|
|
288
|
+
|
|
289
|
+
expect(result).toHaveLength(1);
|
|
290
|
+
expect(result[0].chartNameDisplay).toBe('Chart Alpha');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should return an empty array if no charts match', () => {
|
|
294
|
+
const result = filterAndArrangeCharts(mockCharts, { searchQuery: 'nonexistent' });
|
|
295
|
+
|
|
296
|
+
expect(result).toHaveLength(0);
|
|
297
|
+
});
|
|
298
|
+
});
|
|
207
299
|
});
|