@rancher/shell 3.0.2-rc.5 → 3.0.2
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/images/providers/nutanix.svg +12 -1
- package/assets/styles/base/_basic.scss +2 -1
- package/assets/styles/base/_helpers.scss +4 -0
- package/assets/styles/base/_variables.scss +2 -0
- package/assets/styles/global/_labeled-input.scss +5 -13
- package/assets/styles/global/_layout.scss +4 -1
- package/assets/styles/global/_select.scss +5 -0
- package/assets/styles/themes/_dark.scss +1 -3
- package/assets/styles/themes/_light.scss +5 -1
- package/assets/translations/en-us.yaml +130 -23
- package/assets/translations/zh-hans.yaml +0 -3
- package/cloud-credential/azure.vue +1 -1
- package/components/ActionMenuShell.vue +105 -0
- package/components/AppModal.vue +2 -2
- package/components/AsyncButton.vue +2 -0
- package/components/ButtonGroup.vue +9 -2
- package/components/ClusterBadge.vue +1 -0
- package/components/ClusterIconMenu.vue +3 -0
- package/components/ClusterProviderIcon.vue +14 -1
- package/components/CodeMirror.vue +96 -5
- package/components/Collapse.vue +16 -3
- package/components/CruResource.vue +9 -0
- package/components/CruResourceFooter.vue +1 -1
- package/components/ExplorerMembers.vue +2 -1
- package/components/FixedBanner.vue +19 -12
- package/components/Import.vue +14 -1
- package/components/LandingPagePreference.vue +4 -2
- package/components/PodSecurityAdmission.vue +8 -6
- package/components/PromptChangePassword.vue +1 -0
- package/components/PromptRemove.vue +23 -21
- package/components/ResourceDetail/Masthead.vue +30 -11
- package/components/ResourceDetail/__tests__/Masthead.test.ts +61 -0
- package/components/ResourceDetail/index.vue +6 -0
- package/components/ResourceTable.vue +6 -1
- package/components/ResourceYaml.vue +1 -0
- package/components/Setting.vue +115 -0
- package/components/SortableTable/THead.vue +2 -0
- package/components/SortableTable/index.vue +7 -12
- package/components/StatusBadge.vue +71 -0
- package/components/Tabbed/index.vue +16 -15
- package/components/Wizard.vue +108 -104
- package/components/YamlEditor.vue +12 -2
- package/components/__tests__/Collapse.test.ts +2 -2
- package/components/__tests__/FixedBanner.test.ts +3 -3
- package/components/auth/Principal.vue +29 -17
- package/components/auth/__tests__/Principal.test.ts +40 -0
- package/components/auth/login/ldap.vue +7 -0
- package/components/fleet/FleetBundles.vue +1 -1
- package/components/fleet/FleetRepos.vue +1 -1
- package/components/fleet/FleetResources.vue +0 -2
- package/components/fleet/FleetSummary.vue +60 -65
- package/components/fleet/ForceDirectedTreeChart/index.vue +5 -1
- package/components/fleet/__tests__/FleetSummary.test.ts +49 -9
- package/components/form/ArrayList.vue +6 -2
- package/components/form/ColorInput.vue +1 -0
- package/components/form/KeyValue.vue +11 -12
- package/components/form/LabeledSelect.vue +15 -3
- package/components/form/Labels.vue +8 -1
- package/components/form/Members/MembershipEditor.vue +230 -222
- package/components/form/Members/__tests__/MembershipEditor.test.ts +62 -0
- package/components/form/Password.vue +3 -0
- package/components/form/ProjectMemberEditor.vue +6 -3
- package/components/form/ResourceTabs/index.vue +15 -13
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +5 -4
- package/components/form/SchedulingCustomization.vue +85 -0
- package/components/form/Select.vue +3 -2
- package/components/form/SelectOrCreateAuthSecret.vue +2 -1
- package/components/form/UnitInput.vue +3 -4
- package/components/form/__tests__/ArrayList.test.ts +9 -6
- package/components/form/__tests__/LabeledSelect.test.ts +37 -0
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +34 -0
- package/components/form/__tests__/UnitInput.test.ts +4 -5
- package/components/formatter/LiveDate.vue +3 -1
- package/components/formatter/ServiceType.vue +12 -4
- package/components/formatter/WorkloadHealthScale.vue +2 -1
- package/components/nav/Header.vue +35 -2
- package/components/nav/HeaderPageActionMenu.vue +11 -40
- package/components/nav/Jump.vue +8 -2
- package/components/nav/NamespaceFilter.vue +5 -4
- package/components/nav/Pinned.vue +1 -1
- package/components/nav/TopLevelMenu.helper.ts +5 -5
- package/components/nav/TopLevelMenu.vue +1 -12
- package/components/nav/WindowManager/ContainerLogs.vue +96 -58
- package/components/nav/WindowManager/ContainerShell.vue +99 -18
- package/components/nav/WindowManager/index.vue +74 -6
- package/components/nav/__tests__/TopLevelMenu.test.ts +0 -40
- package/components/templates/default.vue +2 -47
- package/config/features.js +1 -0
- package/config/labels-annotations.js +11 -1
- package/config/router/navigation-guards/index.js +2 -1
- package/config/router/navigation-guards/record-last-route.js +24 -0
- package/config/settings.ts +66 -98
- package/config/version.js +1 -1
- package/core/types-provisioning.ts +7 -0
- package/detail/fleet.cattle.io.bundle.vue +7 -0
- package/detail/fleet.cattle.io.cluster.vue +0 -3
- package/detail/fleet.cattle.io.gitrepo.vue +8 -15
- package/detail/provisioning.cattle.io.cluster.vue +8 -2
- package/dialog/DeactivateDriverDialog.vue +5 -5
- package/dialog/GitRepoForceUpdateDialog.vue +132 -0
- package/directives/strip-html-aria-label.js +19 -0
- package/edit/__tests__/cis.cattle.io.clusterscan.test.ts +87 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +217 -37
- package/edit/auth/__tests__/oidc.test.ts +60 -12
- package/edit/auth/ldap/__tests__/config.test.ts +40 -0
- package/edit/auth/ldap/config.vue +67 -89
- package/edit/auth/oidc.vue +16 -2
- package/edit/catalog.cattle.io.clusterrepo.vue +12 -8
- package/edit/cis.cattle.io.clusterscan.vue +13 -1
- package/edit/fleet.cattle.io.gitrepo.vue +198 -72
- package/edit/logging-flow/Match.vue +0 -21
- package/edit/management.cattle.io.project.vue +1 -1
- package/edit/monitoring.coreos.com.prometheusrule/AlertingRule.vue +10 -3
- package/edit/monitoring.coreos.com.prometheusrule/RecordingRule.vue +5 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +5 -2
- package/edit/provisioning.cattle.io.cluster/CustomCommand.vue +8 -1
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +2 -0
- package/edit/provisioning.cattle.io.cluster/__tests__/Advanced.test.ts +0 -2
- package/edit/provisioning.cattle.io.cluster/__tests__/CustomCommand.test.ts +55 -15
- package/edit/provisioning.cattle.io.cluster/index.vue +28 -30
- package/edit/provisioning.cattle.io.cluster/rke2.vue +64 -13
- package/edit/provisioning.cattle.io.cluster/tabs/AgentConfiguration.vue +37 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +3 -2
- package/edit/resources.cattle.io.backup.vue +150 -15
- package/edit/secret/__tests__/ssh.test.ts +79 -0
- package/edit/secret/ssh.vue +7 -1
- package/edit/service.vue +0 -3
- package/edit/workload/Job.vue +8 -8
- package/edit/workload/__tests__/Job.test.ts +0 -1
- package/edit/workload/index.vue +3 -1
- package/initialize/install-directives.js +2 -0
- package/initialize/install-plugins.js +6 -1
- package/list/catalog.cattle.io.app.vue +21 -4
- package/list/fleet.cattle.io.bundle.vue +1 -1
- package/list/management.cattle.io.setting.vue +34 -132
- package/list/provisioning.cattle.io.cluster.vue +11 -3
- package/machine-config/vmwarevsphere.vue +15 -8
- package/mixins/__tests__/auth-config.test.ts +74 -0
- package/mixins/__tests__/chart.test.ts +5 -4
- package/mixins/__tests__/create-edit-view.test.ts +38 -0
- package/mixins/auth-config.js +8 -0
- package/mixins/chart.js +2 -2
- package/mixins/create-edit-view/impl.js +4 -1
- package/mixins/vue-select-overrides.js +10 -0
- package/models/__tests__/catalog.cattle.io.app.test.ts +148 -0
- package/models/__tests__/fleet.cattle.io.gitrepo.test.ts +157 -0
- package/models/__tests__/secret.test.ts +56 -13
- package/models/catalog.cattle.io.app.js +112 -37
- package/models/cluster.js +11 -0
- package/models/fleet.cattle.io.bundle.js +40 -2
- package/models/fleet.cattle.io.gitrepo.js +169 -109
- package/models/management.cattle.io.fleetworkspace.js +4 -0
- package/models/management.cattle.io.kontainerdriver.js +7 -0
- package/models/nodedriver.js +4 -1
- package/models/provisioning.cattle.io.cluster.js +24 -0
- package/models/secret.js +1 -1
- package/package.json +5 -5
- package/pages/auth/login.vue +5 -11
- package/pages/auth/verify.vue +11 -1
- package/pages/c/_cluster/apps/charts/index.vue +6 -4
- package/pages/c/_cluster/apps/charts/install.vue +1 -1
- package/pages/c/_cluster/explorer/ConfigBadge.vue +3 -5
- package/pages/c/_cluster/explorer/EventsTable.vue +3 -2
- package/pages/c/_cluster/explorer/__tests__/index.test.ts +9 -9
- package/pages/c/_cluster/explorer/index.vue +33 -35
- package/pages/c/_cluster/explorer/tools/index.vue +3 -3
- package/pages/c/_cluster/fleet/index.vue +0 -5
- package/pages/c/_cluster/legacy/project/index.vue +1 -1
- package/pages/c/_cluster/settings/performance.vue +52 -53
- package/pages/c/_cluster/uiplugins/index.vue +19 -22
- package/pages/home.vue +17 -12
- package/pages/prefs.vue +5 -1
- package/plugins/shortkey.js +10 -1
- package/plugins/steve/steve-pagination-utils.ts +58 -8
- package/promptRemove/management.cattle.io.fleetworkspace.vue +98 -0
- package/promptRemove/management.cattle.io.globalrole.vue +1 -1
- package/promptRemove/management.cattle.io.project.vue +2 -8
- package/promptRemove/management.cattle.io.roletemplate.vue +1 -1
- package/promptRemove/mixin/roleDeletionCheck.js +1 -7
- package/promptRemove/pod.vue +7 -28
- package/rancher-components/Card/Card.vue +9 -1
- package/rancher-components/Form/Checkbox/Checkbox.vue +42 -6
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +30 -3
- package/rancher-components/Form/Radio/RadioButton.vue +18 -3
- package/rancher-components/Form/Radio/RadioGroup.vue +39 -5
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +13 -1
- package/rancher-components/RcButton/RcButton.test.ts +97 -0
- package/rancher-components/RcButton/RcButton.vue +14 -9
- package/rancher-components/RcDropdown/RcDropdown.vue +3 -1
- package/rancher-components/RcDropdown/RcDropdownItem.vue +8 -2
- package/rancher-components/RcDropdown/RcDropdownMenu.vue +66 -0
- package/rancher-components/RcDropdown/index.ts +1 -0
- package/rancher-components/RcDropdown/types.ts +27 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +5 -2
- package/scripts/extension/helm/charts/ui-plugin-server/templates/_helpers.tpl +2 -2
- package/scripts/typegen.sh +1 -0
- package/store/__tests__/auth.test.ts +120 -0
- package/store/action-menu.js +13 -3
- package/store/auth.js +14 -9
- package/store/aws.js +9 -2
- package/store/catalog.js +14 -7
- package/store/features.js +1 -0
- package/store/prefs.js +9 -28
- package/store/type-map.utils.ts +4 -0
- package/types/resources/settings.d.ts +27 -20
- package/types/shell/index.d.ts +18 -12
- package/utils/__tests__/array.test.ts +13 -1
- package/utils/__tests__/string.test.ts +80 -1
- package/utils/array.ts +13 -0
- package/utils/auth.js +4 -0
- package/utils/banners.js +0 -45
- package/utils/cluster.js +1 -1
- package/{edit/monitoring.coreos.com.prometheusrule → utils}/duration.js +5 -3
- package/utils/object.js +0 -3
- package/utils/pagination-utils.ts +15 -2
- package/utils/string.js +31 -7
- package/utils/validators/formRules/__tests__/index.test.ts +27 -0
- package/utils/validators/formRules/index.ts +16 -0
- package/edit/provisioning.cattle.io.cluster/import.vue +0 -198
|
@@ -105,6 +105,10 @@ export default defineComponent({
|
|
|
105
105
|
|
|
106
106
|
emits: ['update:value'],
|
|
107
107
|
|
|
108
|
+
data() {
|
|
109
|
+
return { currFocusedElem: undefined as undefined | EventTarget | null };
|
|
110
|
+
},
|
|
111
|
+
|
|
108
112
|
computed: {
|
|
109
113
|
/**
|
|
110
114
|
* Creates a collection of Options from the provided props.
|
|
@@ -151,12 +155,33 @@ export default defineComponent({
|
|
|
151
155
|
}
|
|
152
156
|
},
|
|
153
157
|
|
|
158
|
+
beforeUnmount() {
|
|
159
|
+
const radioGroup = this.$refs?.radioGroup as HTMLInputElement;
|
|
160
|
+
|
|
161
|
+
radioGroup.removeEventListener('focusin', this.focusChanged);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
mounted() {
|
|
165
|
+
const radioGroup = this.$refs?.radioGroup as HTMLInputElement;
|
|
166
|
+
|
|
167
|
+
radioGroup.addEventListener('focusin', this.focusChanged);
|
|
168
|
+
},
|
|
169
|
+
|
|
154
170
|
methods: {
|
|
171
|
+
focusChanged(ev: Event) {
|
|
172
|
+
this.currFocusedElem = ev.target;
|
|
173
|
+
},
|
|
155
174
|
/**
|
|
156
175
|
* Keyboard left/right event listener to select next/previous option. Emits
|
|
157
176
|
* the input event.
|
|
158
177
|
*/
|
|
159
178
|
clickNext(direction: number): void {
|
|
179
|
+
// moving focus away from a custom group element and pressing arrow keys
|
|
180
|
+
// should not have any effect on the group - custom UI for radiogroup option(s)
|
|
181
|
+
if (this.currFocusedElem !== this.$refs?.radioGroup) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
160
185
|
const opts = this.normalizedOptions;
|
|
161
186
|
const selected = opts.find((x) => x.value === this.value);
|
|
162
187
|
let newIndex = (selected ? opts.indexOf(selected) : -1) + direction;
|
|
@@ -205,12 +230,15 @@ export default defineComponent({
|
|
|
205
230
|
|
|
206
231
|
<!-- Group -->
|
|
207
232
|
<div
|
|
233
|
+
ref="radioGroup"
|
|
208
234
|
role="radiogroup"
|
|
209
235
|
:aria-label="radioGroupLabel"
|
|
210
236
|
class="radio-group"
|
|
211
237
|
:class="{'row':row}"
|
|
212
|
-
|
|
213
|
-
@
|
|
238
|
+
tabindex="0"
|
|
239
|
+
@keydown.down.prevent.stop="clickNext(1)"
|
|
240
|
+
@keydown.up.prevent.stop="clickNext(-1)"
|
|
241
|
+
@keydown.space.enter.stop.prevent
|
|
214
242
|
>
|
|
215
243
|
<div
|
|
216
244
|
v-for="(option, i) in normalizedOptions"
|
|
@@ -230,7 +258,9 @@ export default defineComponent({
|
|
|
230
258
|
:description="option.description"
|
|
231
259
|
:val="option.value"
|
|
232
260
|
:disabled="isDisabled"
|
|
261
|
+
:data-testid="`radio-button-${i}`"
|
|
233
262
|
:mode="mode"
|
|
263
|
+
:prevent-focus-on-radio-groups="true"
|
|
234
264
|
@update:value="$emit('update:value', $event)"
|
|
235
265
|
/>
|
|
236
266
|
</slot>
|
|
@@ -241,9 +271,13 @@ export default defineComponent({
|
|
|
241
271
|
|
|
242
272
|
<style lang='scss'>
|
|
243
273
|
.radio-group {
|
|
244
|
-
&:focus {
|
|
245
|
-
border:none;
|
|
246
|
-
outline:none;
|
|
274
|
+
&:focus, &:focus-visible {
|
|
275
|
+
border: none;
|
|
276
|
+
outline: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
&:focus-visible .radio-button-checked {
|
|
280
|
+
@include focus-outline;
|
|
247
281
|
}
|
|
248
282
|
|
|
249
283
|
h3 {
|
|
@@ -31,6 +31,16 @@ export default defineComponent({
|
|
|
31
31
|
computed: {
|
|
32
32
|
iconClass(): string {
|
|
33
33
|
return this.status === 'error' ? 'icon-warning' : 'icon-info';
|
|
34
|
+
},
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
36
|
+
tooltipContent(): {[key: string]: any} | string {
|
|
37
|
+
if (this.isObject(this.value)) {
|
|
38
|
+
return {
|
|
39
|
+
...{ content: this.value.content, popperClass: [`tooltip-${ status }`] }, ...this.value, triggers: ['hover', 'touch', 'focus']
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return this.value ? { content: this.value, triggers: ['hover', 'touch', 'focus'] } : '';
|
|
34
44
|
}
|
|
35
45
|
},
|
|
36
46
|
methods: {
|
|
@@ -49,9 +59,11 @@ export default defineComponent({
|
|
|
49
59
|
>
|
|
50
60
|
<template v-if="hover">
|
|
51
61
|
<i
|
|
52
|
-
v-clean-tooltip="
|
|
62
|
+
v-clean-tooltip="tooltipContent"
|
|
63
|
+
v-stripped-aria-label="isObject(value) ? value.content : value"
|
|
53
64
|
:class="{'hover':!value, [iconClass]: true}"
|
|
54
65
|
class="icon status-icon"
|
|
66
|
+
tabindex="0"
|
|
55
67
|
/>
|
|
56
68
|
</template>
|
|
57
69
|
<template v-else>
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils';
|
|
2
|
+
import RcButton from './RcButton.vue';
|
|
3
|
+
|
|
4
|
+
describe('rcButton.vue', () => {
|
|
5
|
+
it('renders with default role', () => {
|
|
6
|
+
const wrapper = mount(RcButton);
|
|
7
|
+
const button = wrapper.find('button');
|
|
8
|
+
|
|
9
|
+
expect(button.classes()).toContain('btn');
|
|
10
|
+
expect(button.classes()).toContain('role-primary');
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('applies correct role', () => {
|
|
14
|
+
const wrapper = mount(RcButton, { props: { primary: true } });
|
|
15
|
+
const button = wrapper.find('button');
|
|
16
|
+
|
|
17
|
+
expect(button.classes()).toContain('role-primary');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('defaults to primary role if multiple roles are provided', () => {
|
|
21
|
+
const wrapper = mount(
|
|
22
|
+
RcButton,
|
|
23
|
+
{
|
|
24
|
+
props: {
|
|
25
|
+
primary: true,
|
|
26
|
+
secondary: true,
|
|
27
|
+
tertiary: true,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
const button = wrapper.find('button');
|
|
32
|
+
|
|
33
|
+
expect(button.classes()).toContain('role-primary');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('defaults to secondary role if both secondary and tertiary roles are provided', () => {
|
|
37
|
+
const wrapper = mount(
|
|
38
|
+
RcButton,
|
|
39
|
+
{
|
|
40
|
+
props: {
|
|
41
|
+
secondary: true,
|
|
42
|
+
tertiary: true,
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
const button = wrapper.find('button');
|
|
47
|
+
|
|
48
|
+
expect(button.classes()).toContain('role-secondary');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('applies correct size class', () => {
|
|
52
|
+
const wrapper = mount(RcButton, { props: { small: true } });
|
|
53
|
+
const button = wrapper.find('button');
|
|
54
|
+
|
|
55
|
+
expect(button.classes()).toContain('btn-sm');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('renders slots correctly', () => {
|
|
59
|
+
const wrapper = mount(RcButton, {
|
|
60
|
+
slots: {
|
|
61
|
+
default: 'Click Me',
|
|
62
|
+
before: 'Before',
|
|
63
|
+
after: 'After',
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
expect(wrapper.text()).toContain('Before');
|
|
68
|
+
expect(wrapper.text()).toContain('Click Me');
|
|
69
|
+
expect(wrapper.text()).toContain('After');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('focuses the button when focus method is called', async() => {
|
|
73
|
+
const wrapper = mount(
|
|
74
|
+
RcButton,
|
|
75
|
+
{ attachTo: document.body }
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
await wrapper.vm.focus();
|
|
79
|
+
const button = wrapper.find('button');
|
|
80
|
+
|
|
81
|
+
expect(button.element).toBe(document.activeElement);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('applies additional classes from $attrs', () => {
|
|
85
|
+
const wrapper = mount(RcButton, { attrs: { class: 'extra-class' } });
|
|
86
|
+
const button = wrapper.find('button');
|
|
87
|
+
|
|
88
|
+
expect(button.classes()).toContain('extra-class');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('applies ghost button styles correctly', () => {
|
|
92
|
+
const wrapper = mount(RcButton, { props: { ghost: true } });
|
|
93
|
+
const button = wrapper.find('button');
|
|
94
|
+
|
|
95
|
+
expect(button.classes()).toContain('role-ghost');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -65,25 +65,30 @@ defineExpose({ focus });
|
|
|
65
65
|
</template>
|
|
66
66
|
|
|
67
67
|
<style lang="scss" scoped>
|
|
68
|
-
.role-link {
|
|
69
|
-
&:focus, &.focused {
|
|
70
|
-
outline: var(--outline-width) solid var(--border);
|
|
71
|
-
box-shadow: 0 0 0 var(--outline-width) var(--outline);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
68
|
button {
|
|
69
|
+
&.role-link {
|
|
70
|
+
&:focus, &.focused {
|
|
71
|
+
@include focus-outline;
|
|
72
|
+
outline-offset: -2px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
&:hover {
|
|
76
|
+
background-color: var(--accent-btn);
|
|
77
|
+
box-shadow: none;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
76
81
|
&.role-ghost {
|
|
77
82
|
padding: 0;
|
|
78
83
|
background-color: transparent;
|
|
79
84
|
|
|
80
85
|
&:focus, &.focused {
|
|
81
|
-
|
|
86
|
+
@include focus-outline;
|
|
82
87
|
outline-offset: 0;
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
&:focus-visible {
|
|
86
|
-
|
|
91
|
+
@include focus-outline;
|
|
87
92
|
outline-offset: 0;
|
|
88
93
|
}
|
|
89
94
|
}
|
|
@@ -28,6 +28,8 @@ defineProps<{
|
|
|
28
28
|
ariaLabel?: string
|
|
29
29
|
}>();
|
|
30
30
|
|
|
31
|
+
const emit = defineEmits(['update:open']);
|
|
32
|
+
|
|
31
33
|
const {
|
|
32
34
|
isMenuOpen,
|
|
33
35
|
showMenu,
|
|
@@ -36,7 +38,7 @@ const {
|
|
|
36
38
|
provideDropdownContext,
|
|
37
39
|
registerDropdownCollection,
|
|
38
40
|
handleKeydown,
|
|
39
|
-
} = useDropdownContext();
|
|
41
|
+
} = useDropdownContext(emit);
|
|
40
42
|
|
|
41
43
|
provideDropdownContext();
|
|
42
44
|
|
|
@@ -53,12 +53,12 @@ const findNewIndex = (shouldAdvance: boolean, activeIndex: number, itemsArr: Ele
|
|
|
53
53
|
return newIndex;
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
const handleClick = () => {
|
|
56
|
+
const handleClick = (e: MouseEvent) => {
|
|
57
57
|
if (props.disabled) {
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
emits('click');
|
|
61
|
+
emits('click', e);
|
|
62
62
|
close();
|
|
63
63
|
};
|
|
64
64
|
|
|
@@ -85,6 +85,9 @@ const handleActivate = (e: KeyboardEvent) => {
|
|
|
85
85
|
@keydown.enter.space="handleActivate"
|
|
86
86
|
@keydown.up.down.stop="handleKeydown"
|
|
87
87
|
>
|
|
88
|
+
<slot name="before">
|
|
89
|
+
<!--Empty slot content-->
|
|
90
|
+
</slot>
|
|
88
91
|
<slot name="default">
|
|
89
92
|
<!--Empty slot content-->
|
|
90
93
|
</slot>
|
|
@@ -93,6 +96,9 @@ const handleActivate = (e: KeyboardEvent) => {
|
|
|
93
96
|
|
|
94
97
|
<style lang="scss" scoped>
|
|
95
98
|
[dropdown-menu-item] {
|
|
99
|
+
display: flex;
|
|
100
|
+
gap: 8px;
|
|
101
|
+
align-items: center;
|
|
96
102
|
padding: 9px 8px;
|
|
97
103
|
margin: 0 9px;
|
|
98
104
|
border-radius: 4px;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
RcDropdown,
|
|
4
|
+
RcDropdownItem,
|
|
5
|
+
RcDropdownSeparator,
|
|
6
|
+
RcDropdownTrigger
|
|
7
|
+
} from '@components/RcDropdown';
|
|
8
|
+
import { RcDropdownMenuComponentProps, DropdownOption } from './types';
|
|
9
|
+
import IconOrSvg from '@shell/components/IconOrSvg';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line vue/no-setup-props-destructure
|
|
12
|
+
const { buttonRole = 'primary', buttonSize = '' } = defineProps<RcDropdownMenuComponentProps>();
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(['update:open', 'select']);
|
|
15
|
+
|
|
16
|
+
const hasOptions = (options: DropdownOption[]) => {
|
|
17
|
+
return options.length !== undefined ? options.length : Object.keys(options).length > 0;
|
|
18
|
+
};
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<template>
|
|
22
|
+
<rc-dropdown
|
|
23
|
+
:aria-label="dropdownAriaLabel"
|
|
24
|
+
@update:open="(e: boolean) => emit('update:open', e)"
|
|
25
|
+
>
|
|
26
|
+
<rc-dropdown-trigger
|
|
27
|
+
:[buttonRole]="true"
|
|
28
|
+
:[buttonSize]="true"
|
|
29
|
+
:data-testid="dataTestid"
|
|
30
|
+
:aria-label="buttonAriaLabel"
|
|
31
|
+
>
|
|
32
|
+
<i class="icon icon-actions" />
|
|
33
|
+
</rc-dropdown-trigger>
|
|
34
|
+
<template #dropdownCollection>
|
|
35
|
+
<template
|
|
36
|
+
v-for="(a) in options"
|
|
37
|
+
:key="a.label"
|
|
38
|
+
>
|
|
39
|
+
<rc-dropdown-item
|
|
40
|
+
v-if="!a.divider"
|
|
41
|
+
@click="(e: MouseEvent) => emit('select', e, a)"
|
|
42
|
+
>
|
|
43
|
+
<template #before>
|
|
44
|
+
<IconOrSvg
|
|
45
|
+
v-if="a.icon || a.svg"
|
|
46
|
+
:icon="a.icon"
|
|
47
|
+
:src="a.svg"
|
|
48
|
+
class="icon"
|
|
49
|
+
color="header"
|
|
50
|
+
/>
|
|
51
|
+
</template>
|
|
52
|
+
{{ a.label }}
|
|
53
|
+
</rc-dropdown-item>
|
|
54
|
+
<rc-dropdown-separator
|
|
55
|
+
v-else
|
|
56
|
+
/>
|
|
57
|
+
</template>
|
|
58
|
+
<rc-dropdown-item
|
|
59
|
+
v-if="!hasOptions(options)"
|
|
60
|
+
disabled
|
|
61
|
+
>
|
|
62
|
+
No actions available
|
|
63
|
+
</rc-dropdown-item>
|
|
64
|
+
</template>
|
|
65
|
+
</rc-dropdown>
|
|
66
|
+
</template>
|
|
@@ -2,3 +2,4 @@ export { default as RcDropdown } from './RcDropdown.vue';
|
|
|
2
2
|
export { default as RcDropdownItem } from './RcDropdownItem.vue';
|
|
3
3
|
export { default as RcDropdownSeparator } from './RcDropdownSeparator.vue';
|
|
4
4
|
export { default as RcDropdownTrigger } from './RcDropdownTrigger.vue';
|
|
5
|
+
export { default as RcDropdownMenu } from './RcDropdownMenu.vue';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Ref, ref } from 'vue';
|
|
2
2
|
import type { RcButtonType } from '@components/RcButton';
|
|
3
|
+
import { ButtonRoleProps, ButtonSizeProps } from '@components/RcButton/types';
|
|
3
4
|
|
|
4
5
|
export type DropdownContext = {
|
|
5
6
|
handleKeydown: () => void;
|
|
@@ -20,3 +21,29 @@ export const defaultContext: DropdownContext = {
|
|
|
20
21
|
isMenuOpen: ref(false),
|
|
21
22
|
close: () => null,
|
|
22
23
|
};
|
|
24
|
+
|
|
25
|
+
export type DropdownOption = {
|
|
26
|
+
action?: string;
|
|
27
|
+
divider?: boolean;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
icon?: string;
|
|
30
|
+
svg?: string;
|
|
31
|
+
label?: string;
|
|
32
|
+
total: number;
|
|
33
|
+
allEnabled: boolean;
|
|
34
|
+
anyEnabled: boolean;
|
|
35
|
+
available: number;
|
|
36
|
+
bulkable?: boolean;
|
|
37
|
+
bulkAction?: string;
|
|
38
|
+
altAction?: string;
|
|
39
|
+
weight?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type RcDropdownMenuComponentProps = {
|
|
43
|
+
options: DropdownOption[];
|
|
44
|
+
buttonRole?: keyof ButtonRoleProps;
|
|
45
|
+
buttonSize?: keyof ButtonSizeProps;
|
|
46
|
+
buttonAriaLabel?: string;
|
|
47
|
+
dropdownAriaLabel?: string;
|
|
48
|
+
dataTestid?: string;
|
|
49
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { ref, provide, nextTick } from 'vue';
|
|
1
|
+
import { ref, provide, nextTick, defineEmits } from 'vue';
|
|
2
2
|
import { useDropdownCollection } from './useDropdownCollection';
|
|
3
3
|
import { RcButtonType } from '@components/RcButton';
|
|
4
4
|
|
|
5
|
+
const rcDropdownEmits = defineEmits(['update:open']);
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
8
|
* Composable that provides the context for a dropdown menu. Includes methods
|
|
7
9
|
* and state for managing the dropdown's visibility, focus, and keyboard
|
|
@@ -11,7 +13,7 @@ import { RcButtonType } from '@components/RcButton';
|
|
|
11
13
|
* @returns Dropdown context methods and state. Used for programmatic
|
|
12
14
|
* interactions and setting focus.
|
|
13
15
|
*/
|
|
14
|
-
export const useDropdownContext = () => {
|
|
16
|
+
export const useDropdownContext = (emit: typeof rcDropdownEmits) => {
|
|
15
17
|
const {
|
|
16
18
|
dropdownItems,
|
|
17
19
|
firstDropdownItem,
|
|
@@ -30,6 +32,7 @@ export const useDropdownContext = () => {
|
|
|
30
32
|
didKeydown.value = false;
|
|
31
33
|
}
|
|
32
34
|
isMenuOpen.value = show;
|
|
35
|
+
emit('update:open', show);
|
|
33
36
|
};
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -38,7 +38,7 @@ Common labels
|
|
|
38
38
|
helm.sh/chart: {{ include "extension-server.chart" . }}
|
|
39
39
|
{{ include "extension-server.selectorLabels" . }}
|
|
40
40
|
{{- if .Chart.AppVersion }}
|
|
41
|
-
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
|
41
|
+
app.kubernetes.io/version: {{ .Chart.AppVersion | replace "+" "_" | quote }}
|
|
42
42
|
{{- end }}
|
|
43
43
|
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
|
44
44
|
{{- end }}
|
|
@@ -60,4 +60,4 @@ Pkg annotations
|
|
|
60
60
|
{{ $key }}: {{ $value | quote }}
|
|
61
61
|
{{- end }}
|
|
62
62
|
{{- end }}
|
|
63
|
-
{{- end }}
|
|
63
|
+
{{- end }}
|
package/scripts/typegen.sh
CHANGED
|
@@ -34,6 +34,7 @@ ${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/resource-
|
|
|
34
34
|
${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/classify.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null
|
|
35
35
|
${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/dashboard-store/actions.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/dashboard-store/ > /dev/null
|
|
36
36
|
${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/steve/steve-class.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/steve/ > /dev/null
|
|
37
|
+
${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/plugins/steve/hybrid-class.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/plugins/steve/ > /dev/null
|
|
37
38
|
|
|
38
39
|
# # mixins
|
|
39
40
|
${BASE_DIR}/node_modules/.bin/tsc ${SHELL_DIR}/mixins/create-edit-view/index.js --declaration --allowJs --emitDeclarationOnly --outDir ${SHELL_DIR}/tmp/mixins/create-edit-view > /dev/null
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { actions } from '@shell/store/auth';
|
|
2
|
+
import { createStore } from 'vuex';
|
|
3
|
+
|
|
4
|
+
// jest.mock('@shell/utils/url', () => ({
|
|
5
|
+
// addParams: () => ({}),
|
|
6
|
+
// parse: () => ({}),
|
|
7
|
+
// removeParam: () => ({}),
|
|
8
|
+
// }));
|
|
9
|
+
|
|
10
|
+
describe('action: redirectTo', () => {
|
|
11
|
+
it('should include query parameters from redirect', async() => {
|
|
12
|
+
jest.spyOn(window, 'window', 'get');
|
|
13
|
+
const store = { dispatch: jest.fn() };
|
|
14
|
+
const clientId = '123';
|
|
15
|
+
const uri = 'anyURI';
|
|
16
|
+
const scope = 'anything';
|
|
17
|
+
const expectation = `:///?client_id=${ clientId }&redirect_uri=${ uri }&response_type=code&response_mode=query&scope=${ scope }&state=undefined`;
|
|
18
|
+
const options = {
|
|
19
|
+
provider: 'azuread',
|
|
20
|
+
redirect: false,
|
|
21
|
+
redirectUrl: `?client_id=${ clientId }&redirect_uri=${ uri }&scope=${ scope }`,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const url = await actions.redirectTo(store as any, options);
|
|
25
|
+
|
|
26
|
+
expect(url).toStrictEqual(expectation);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it.each([
|
|
30
|
+
['genericoidc', '://myhost/?redirect_uri=anyURI&scope=openid%20profile%20email&state=undefined'],
|
|
31
|
+
])('given provider %p should return URL %p', async(provider, expectation) => {
|
|
32
|
+
jest.spyOn(window, 'window', 'get');
|
|
33
|
+
const store = { dispatch: jest.fn() };
|
|
34
|
+
const uri = 'anyURI'; // This field is added anyway, so we set a random value
|
|
35
|
+
const options = {
|
|
36
|
+
provider,
|
|
37
|
+
redirect: false,
|
|
38
|
+
redirectUrl: `myhost?redirect_uri=${ uri }`,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const url = await actions.redirectTo(store as any, options);
|
|
42
|
+
|
|
43
|
+
expect(url).toStrictEqual(expectation);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe.each([
|
|
47
|
+
// 'whatever',
|
|
48
|
+
// 'github',
|
|
49
|
+
// 'googleoauth',
|
|
50
|
+
// 'azuread',
|
|
51
|
+
// 'keycloakoidc',
|
|
52
|
+
'genericoidc',
|
|
53
|
+
])('given provider %p', (provider) => {
|
|
54
|
+
it('should keep scope from options', async() => {
|
|
55
|
+
const customScope = 'myScope';
|
|
56
|
+
const options = {
|
|
57
|
+
provider,
|
|
58
|
+
redirectUrl: 'anyURL',
|
|
59
|
+
scopes: customScope,
|
|
60
|
+
// scopesJoinChar: ' ', // it's not used for genericoidc
|
|
61
|
+
test: true,
|
|
62
|
+
redirect: false
|
|
63
|
+
};
|
|
64
|
+
const store = { dispatch: jest.fn() };
|
|
65
|
+
|
|
66
|
+
jest.spyOn(window, 'window', 'get');
|
|
67
|
+
const url = await actions.redirectTo(store as any, options);
|
|
68
|
+
|
|
69
|
+
expect(url).toContain(customScope);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should merge scopes into a single string and avoid duplication', async() => {
|
|
73
|
+
const defaultScopes = 'openid profile email';
|
|
74
|
+
const customScope = 'myScope';
|
|
75
|
+
const options = {
|
|
76
|
+
provider,
|
|
77
|
+
redirectUrl: 'anyURL',
|
|
78
|
+
scopes: `${ defaultScopes } ${ customScope }`,
|
|
79
|
+
test: true,
|
|
80
|
+
redirect: false
|
|
81
|
+
};
|
|
82
|
+
const store = { dispatch: jest.fn() };
|
|
83
|
+
|
|
84
|
+
jest.spyOn(window, 'window', 'get');
|
|
85
|
+
const url = await actions.redirectTo(store as any, options);
|
|
86
|
+
|
|
87
|
+
expect(url).toContain('scope=openid%20profile%20email%20myScope&');
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
jest.mock('@shell/utils/auth');
|
|
93
|
+
|
|
94
|
+
describe('action: test', () => {
|
|
95
|
+
describe('given providers with an action (github, google, azuread, oidc)', () => {
|
|
96
|
+
it('should call redirectTo with all the options', async() => {
|
|
97
|
+
const dispatchSpy = jest.fn().mockReturnValue('anyURL');
|
|
98
|
+
const store = createStore({
|
|
99
|
+
actions: {
|
|
100
|
+
getAuthConfig: () => ({ doAction: () => 'no action' }),
|
|
101
|
+
redirectTo: dispatchSpy,
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
const provider = 'anyProvider';
|
|
105
|
+
const redirectUrl = undefined;
|
|
106
|
+
const body = { scope: ['any scope'] };
|
|
107
|
+
const options = {
|
|
108
|
+
provider,
|
|
109
|
+
redirectUrl,
|
|
110
|
+
scopes: body.scope,
|
|
111
|
+
test: true,
|
|
112
|
+
redirect: false
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
await actions.test(store, { provider, body });
|
|
116
|
+
|
|
117
|
+
expect(dispatchSpy.mock.calls[0][1]).toStrictEqual(options);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
});
|
package/store/action-menu.js
CHANGED
|
@@ -27,7 +27,7 @@ export const getters = {
|
|
|
27
27
|
event: (state) => state.event,
|
|
28
28
|
resources: (state) => state.resources,
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
optionsArray(state) {
|
|
31
31
|
let selected = state.resources;
|
|
32
32
|
|
|
33
33
|
if ( !selected ) {
|
|
@@ -41,7 +41,7 @@ export const getters = {
|
|
|
41
41
|
const map = {};
|
|
42
42
|
|
|
43
43
|
for ( const node of selected ) {
|
|
44
|
-
if (node
|
|
44
|
+
if (node?.availableActions) {
|
|
45
45
|
for ( const act of node.availableActions ) {
|
|
46
46
|
_add(map, act);
|
|
47
47
|
}
|
|
@@ -50,7 +50,10 @@ export const getters = {
|
|
|
50
50
|
|
|
51
51
|
const out = _filter(map);
|
|
52
52
|
|
|
53
|
-
return
|
|
53
|
+
return [...out];
|
|
54
|
+
},
|
|
55
|
+
options(_state, getters) {
|
|
56
|
+
return { ...getters.optionsArray };
|
|
54
57
|
},
|
|
55
58
|
|
|
56
59
|
};
|
|
@@ -144,12 +147,19 @@ export const mutations = {
|
|
|
144
147
|
|
|
145
148
|
state.modalData = data;
|
|
146
149
|
},
|
|
150
|
+
|
|
151
|
+
SET_RESOURCE(state, resources) {
|
|
152
|
+
state.resources = !isArray(resources) ? [resources] : resources;
|
|
153
|
+
}
|
|
147
154
|
};
|
|
148
155
|
|
|
149
156
|
export const actions = {
|
|
150
157
|
execute({ state }, { action, args, opts }) {
|
|
151
158
|
return _execute(state.resources, action, args, opts);
|
|
152
159
|
},
|
|
160
|
+
setResource({ commit }, resource) {
|
|
161
|
+
commit('SET_RESOURCE', resource);
|
|
162
|
+
}
|
|
153
163
|
};
|
|
154
164
|
|
|
155
165
|
// -----------------------------
|