@rancher/shell 3.0.2-rc.3 → 3.0.2-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/styles/base/_basic.scss +2 -1
- package/assets/styles/global/_form.scss +2 -1
- package/assets/styles/themes/_dark.scss +1 -1
- package/assets/translations/en-us.yaml +22 -4
- package/assets/translations/zh-hans.yaml +2 -3
- package/components/AppModal.vue +50 -0
- package/components/Carousel.vue +54 -47
- package/components/CopyToClipboardText.vue +3 -0
- package/components/Dialog.vue +20 -1
- package/components/PromptChangePassword.vue +3 -0
- package/components/ResourceDetail/Masthead.vue +1 -1
- package/components/Tabbed/index.vue +4 -7
- package/components/__tests__/Carousel.test.ts +56 -27
- package/components/form/LabeledSelect.vue +1 -1
- package/components/form/SSHKnownHosts/KnownHostsEditDialog.vue +192 -0
- package/components/form/SSHKnownHosts/__tests__/KnownHostsEditDialog.test.ts +104 -0
- package/components/form/SSHKnownHosts/index.vue +101 -0
- package/components/form/Select.vue +1 -1
- package/components/form/SelectOrCreateAuthSecret.vue +43 -11
- package/components/form/__tests__/SSHKnownHosts.test.ts +59 -0
- package/composables/focusTrap.ts +68 -0
- package/detail/secret.vue +25 -0
- package/edit/fleet.cattle.io.gitrepo.vue +27 -22
- package/edit/provisioning.cattle.io.cluster/index.vue +26 -19
- package/edit/secret/index.vue +1 -1
- package/edit/secret/ssh.vue +21 -3
- package/list/provisioning.cattle.io.cluster.vue +1 -0
- package/models/fleet.cattle.io.gitrepo.js +2 -2
- package/models/provisioning.cattle.io.cluster.js +2 -12
- package/models/secret.js +5 -0
- package/package.json +1 -1
- package/pages/account/index.vue +4 -0
- package/pages/c/_cluster/explorer/ConfigBadge.vue +5 -4
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +3 -1
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogLoadDialog.vue +3 -0
- package/pages/c/_cluster/uiplugins/CatalogList/CatalogUninstallDialog.vue +7 -1
- package/pages/c/_cluster/uiplugins/CatalogList/index.vue +3 -1
- package/pages/c/_cluster/uiplugins/DeveloperInstallDialog.vue +10 -7
- package/pages/c/_cluster/uiplugins/InstallDialog.vue +7 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +181 -106
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +2 -0
- package/pages/c/_cluster/uiplugins/UninstallDialog.vue +9 -1
- package/pages/c/_cluster/uiplugins/index.vue +50 -12
- package/rancher-components/Card/Card.vue +7 -21
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +1 -0
- package/rancher-components/RcDropdown/RcDropdown.vue +11 -0
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +2 -3
- package/rancher-components/RcDropdown/useDropdownCollection.ts +1 -0
- package/rancher-components/RcDropdown/useDropdownContext.ts +28 -1
|
@@ -61,13 +61,13 @@ BODY {
|
|
|
61
61
|
INPUT,
|
|
62
62
|
SELECT,
|
|
63
63
|
TEXTAREA,
|
|
64
|
-
.labeled-input,
|
|
65
64
|
.checkbox-custom {
|
|
66
65
|
&:focus, &.focused {
|
|
67
66
|
@include form-focus;
|
|
68
67
|
}
|
|
69
68
|
}
|
|
70
69
|
|
|
70
|
+
.labeled-input,
|
|
71
71
|
.radio-custom,
|
|
72
72
|
.labeled-select,
|
|
73
73
|
.unlabeled-select {
|
|
@@ -76,6 +76,7 @@ TEXTAREA,
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
.labeled-input,
|
|
79
80
|
.labeled-select,
|
|
80
81
|
.unlabeled-select {
|
|
81
82
|
&.focused {
|
|
@@ -27,7 +27,8 @@ TEXTAREA,
|
|
|
27
27
|
|
|
28
28
|
@include input-status-color;
|
|
29
29
|
|
|
30
|
-
&:focus:not(.
|
|
30
|
+
&:focus:not(.labeled-input):not(.unlabeled-select):not(.labeled-select),
|
|
31
|
+
&.focused:not(.labeled-input):not(.unlabeled-select):not(.labeled-select) {
|
|
31
32
|
@include form-focus;
|
|
32
33
|
}
|
|
33
34
|
|
|
@@ -16,6 +16,7 @@ generic:
|
|
|
16
16
|
comingSoon: Coming Soon
|
|
17
17
|
comma: ', '
|
|
18
18
|
copy: Copy
|
|
19
|
+
copyToClipboard: Copy text to Clipboard
|
|
19
20
|
create: Create
|
|
20
21
|
created: Created
|
|
21
22
|
customize: Customize
|
|
@@ -2440,6 +2441,7 @@ fleet:
|
|
|
2440
2441
|
resources: Resources
|
|
2441
2442
|
unready: Non-Ready
|
|
2442
2443
|
auth:
|
|
2444
|
+
title: Authentication
|
|
2443
2445
|
label: Authentication
|
|
2444
2446
|
git: Git Authentication
|
|
2445
2447
|
helm: Helm Authentication
|
|
@@ -2450,20 +2452,20 @@ fleet:
|
|
|
2450
2452
|
label: Paths
|
|
2451
2453
|
placeholder: e.g. /directory/in/your/repo
|
|
2452
2454
|
addLabel: Add Path
|
|
2453
|
-
empty: The root of the repo is used by default.
|
|
2455
|
+
empty: The root of the repo is used by default. Multiple different directories can be provided.
|
|
2454
2456
|
repo:
|
|
2457
|
+
title: Source
|
|
2455
2458
|
label: Repository URL
|
|
2456
2459
|
placeholder: e.g. https://github.com/rancher/fleet-examples.git or git@github.com:rancher/fleet-examples.git
|
|
2457
2460
|
addRepo: Add Repository
|
|
2458
2461
|
noRepos: No repositories have been added
|
|
2459
2462
|
noWorkspaces: There are no workspaces. <br/> Please create a workspace before adding repositories
|
|
2460
|
-
protocolBanner: Enter a valid HTTPS or SSH URL to a git repository.
|
|
2461
2463
|
resources:
|
|
2462
2464
|
label: 'Resource Handling'
|
|
2463
2465
|
keepResources: Always Keep Resources
|
|
2464
|
-
|
|
2466
|
+
keepResourcesTooltip: When enabled, resources will be kept when deleting a GitRepo or Bundle - only Helm release secrets will be deleted.
|
|
2465
2467
|
correctDrift: Enable Self-Healing
|
|
2466
|
-
|
|
2468
|
+
correctDriftTooltip: When enabled, Fleet will ensure that the cluster resources are kept in sync with the git repository. All resource changes made on the cluster will be lost.
|
|
2467
2469
|
add:
|
|
2468
2470
|
steps:
|
|
2469
2471
|
repoInfo:
|
|
@@ -4380,6 +4382,8 @@ plugins:
|
|
|
4380
4382
|
incompatibleUiExtensionsApiVersion: "The latest version of this extension ({ version }) is not compatible with the current Extensions API version ({ required })."
|
|
4381
4383
|
incompatibleHost: 'The latest version of this extension ({ version }) has a host of "{ required }" which is not compatible with this application "{ mainHost }".'
|
|
4382
4384
|
currentInstalledVersionBlockedByKubeVersion: "This version is not compatible with the current Kubernetes version ({ kubeVersion } Vs { kubeVersionToCheck })."
|
|
4385
|
+
closePluginPanel: Close plugin description panel
|
|
4386
|
+
viewVersionDetails: View extension {name} version {version} details/Readme
|
|
4383
4387
|
labels:
|
|
4384
4388
|
builtin: Built-in
|
|
4385
4389
|
experimental: Experimental
|
|
@@ -4387,6 +4391,8 @@ plugins:
|
|
|
4387
4391
|
image: Image
|
|
4388
4392
|
installing: Installing ...
|
|
4389
4393
|
uninstalling: Uninstalling ...
|
|
4394
|
+
menu: Extensions menu
|
|
4395
|
+
reloadRancher: Reload Rancher
|
|
4390
4396
|
descriptions:
|
|
4391
4397
|
experimental: This Extension is marked as experimental
|
|
4392
4398
|
third-party: This Extension is provided by a Third-Party
|
|
@@ -5139,10 +5145,22 @@ secret:
|
|
|
5139
5145
|
username: Username
|
|
5140
5146
|
ssh:
|
|
5141
5147
|
keys: Keys
|
|
5148
|
+
keysAndHosts: Keys and Known Hosts
|
|
5142
5149
|
public: Public Key
|
|
5143
5150
|
publicPlaceholder: "Paste in your public key"
|
|
5144
5151
|
private: Private Key
|
|
5145
5152
|
privatePlaceholder: "Paste in your private key"
|
|
5153
|
+
knownHosts: Known Hosts
|
|
5154
|
+
knownHostsPlaceholder: "Known hosts metadata, one per line"
|
|
5155
|
+
editKnownHosts:
|
|
5156
|
+
title: SSH Known Hosts Configuration
|
|
5157
|
+
entries: |-
|
|
5158
|
+
{entries, plural,
|
|
5159
|
+
=0 {Empty}
|
|
5160
|
+
=1 {1 Entry}
|
|
5161
|
+
other {{entries} Entries}
|
|
5162
|
+
}
|
|
5163
|
+
|
|
5146
5164
|
serviceAcct:
|
|
5147
5165
|
ca: CA Certificate
|
|
5148
5166
|
token: Token
|
|
@@ -2153,13 +2153,12 @@ fleet:
|
|
|
2153
2153
|
addRepo: 添加仓库
|
|
2154
2154
|
noRepos: 未添加任何仓库
|
|
2155
2155
|
noWorkspaces: 没有工作空间。<br/>请在添加仓库之前创建一个工作空间
|
|
2156
|
-
protocolBanner: 输入指向 git 仓库的有效 HTTPS 或 SSH URL。
|
|
2157
2156
|
resources:
|
|
2158
2157
|
label: '资源处理'
|
|
2159
2158
|
keepResources: 永远保留资源
|
|
2160
|
-
|
|
2159
|
+
keepResourcesTooltip: 启用时,删除 GitRepo 或 Bundle 后将保留资源,只会删除 Helm Release Secret。
|
|
2161
2160
|
correctDrift: 启用自我修复
|
|
2162
|
-
|
|
2161
|
+
correctDriftTooltip: 启用后,Fleet 将确保集群资源与 Git 仓库保持同步。在 ecluster 上进行的所有资源更改都将丢失。
|
|
2163
2162
|
add:
|
|
2164
2163
|
steps:
|
|
2165
2164
|
repoInfo:
|
package/components/AppModal.vue
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { defineComponent } from 'vue';
|
|
3
|
+
import { DEFAULT_FOCUS_TRAP_OPTS, useBasicSetupFocusTrap, getFirstFocusableElement } from '@shell/composables/focusTrap';
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_ITERABLE_NODE_SELECTOR = 'body;';
|
|
3
6
|
|
|
4
7
|
export default defineComponent({
|
|
5
8
|
name: 'AppModal',
|
|
@@ -56,6 +59,27 @@ export default defineComponent({
|
|
|
56
59
|
name: {
|
|
57
60
|
type: String,
|
|
58
61
|
default: '',
|
|
62
|
+
},
|
|
63
|
+
/**
|
|
64
|
+
* trigger focus trap
|
|
65
|
+
*/
|
|
66
|
+
triggerFocusTrap: {
|
|
67
|
+
type: Boolean,
|
|
68
|
+
default: false,
|
|
69
|
+
},
|
|
70
|
+
/**
|
|
71
|
+
* forcefully set return focus element based on this selector
|
|
72
|
+
*/
|
|
73
|
+
returnFocusSelector: {
|
|
74
|
+
type: String,
|
|
75
|
+
default: '',
|
|
76
|
+
},
|
|
77
|
+
/**
|
|
78
|
+
* will return focus to the first iterable node of this container select
|
|
79
|
+
*/
|
|
80
|
+
returnFocusFirstIterableNodeSelector: {
|
|
81
|
+
type: String,
|
|
82
|
+
default: DEFAULT_ITERABLE_NODE_SELECTOR,
|
|
59
83
|
}
|
|
60
84
|
},
|
|
61
85
|
computed: {
|
|
@@ -85,6 +109,31 @@ export default defineComponent({
|
|
|
85
109
|
};
|
|
86
110
|
}
|
|
87
111
|
},
|
|
112
|
+
setup(props) {
|
|
113
|
+
if (props.triggerFocusTrap) {
|
|
114
|
+
let opts:any = DEFAULT_FOCUS_TRAP_OPTS;
|
|
115
|
+
|
|
116
|
+
// if we have a "returnFocusFirstIterableNodeSelector" on top of "returnFocusSelector"
|
|
117
|
+
// then we will use "returnFocusFirstIterableNodeSelector" as a fallback of "returnFocusSelector"
|
|
118
|
+
if (props.returnFocusFirstIterableNodeSelector && props.returnFocusFirstIterableNodeSelector !== DEFAULT_ITERABLE_NODE_SELECTOR && props.returnFocusSelector) {
|
|
119
|
+
opts = {
|
|
120
|
+
...DEFAULT_FOCUS_TRAP_OPTS,
|
|
121
|
+
setReturnFocus: () => {
|
|
122
|
+
return document.querySelector(props.returnFocusSelector) ? props.returnFocusSelector : getFirstFocusableElement(document.querySelector(props.returnFocusFirstIterableNodeSelector));
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
// otherwise, if we are sure of permanent existance of "returnFocusSelector"
|
|
126
|
+
// we just return to that element
|
|
127
|
+
} else if (props.returnFocusSelector) {
|
|
128
|
+
opts = {
|
|
129
|
+
...DEFAULT_FOCUS_TRAP_OPTS,
|
|
130
|
+
setReturnFocus: props.returnFocusSelector
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
useBasicSetupFocusTrap('#modal-container-element', opts);
|
|
135
|
+
}
|
|
136
|
+
},
|
|
88
137
|
mounted() {
|
|
89
138
|
document.addEventListener('keydown', this.handleEscapeKey);
|
|
90
139
|
},
|
|
@@ -134,6 +183,7 @@ export default defineComponent({
|
|
|
134
183
|
>
|
|
135
184
|
<div
|
|
136
185
|
v-bind="$attrs"
|
|
186
|
+
id="modal-container-element"
|
|
137
187
|
ref="modalRef"
|
|
138
188
|
:class="customClass"
|
|
139
189
|
class="modal-container"
|
package/components/Carousel.vue
CHANGED
|
@@ -43,24 +43,29 @@ export default {
|
|
|
43
43
|
activeItemId: 0,
|
|
44
44
|
autoScroll: true,
|
|
45
45
|
autoScrollSlideInterval: null,
|
|
46
|
+
isTransitionning: false, // prevents showing empty spaces caused by aggressive clicking
|
|
47
|
+
shouldDisableTransition: false // smoothes the move from the first/last slides to the previous/next slide
|
|
46
48
|
};
|
|
47
49
|
},
|
|
48
50
|
|
|
49
51
|
computed: {
|
|
50
52
|
...mapGetters(['clusterId']),
|
|
51
53
|
trackStyle() {
|
|
52
|
-
let sliderItem = ( this.activeItemId + 1) * 100 / (this.slider.length + 2);
|
|
53
|
-
const width = 60 * (this.slider.length + 2);
|
|
54
|
-
|
|
55
54
|
if (this.slider.length === 1) {
|
|
56
|
-
|
|
55
|
+
return `
|
|
56
|
+
width: 100%;
|
|
57
|
+
left: 0%;
|
|
58
|
+
`;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
const width = 60 * (this.slider.length + 2);
|
|
62
|
+
const left = -(40 + this.activeItemId * 60);
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
return `
|
|
65
|
+
width: ${ width }%;
|
|
66
|
+
left: ${ left }%;
|
|
67
|
+
transition: ${ this.shouldDisableTransition ? 'none' : '700ms ease-in-out' };
|
|
68
|
+
`;
|
|
64
69
|
}
|
|
65
70
|
},
|
|
66
71
|
|
|
@@ -77,10 +82,14 @@ export default {
|
|
|
77
82
|
},
|
|
78
83
|
|
|
79
84
|
nextPrev(direction) {
|
|
80
|
-
this.
|
|
81
|
-
|
|
85
|
+
if (this.isTransitionning) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
82
88
|
|
|
83
|
-
|
|
89
|
+
this.isTransitionning = true;
|
|
90
|
+
this.autoScroll = false;
|
|
91
|
+
this.shouldDisableTransition = false;
|
|
92
|
+
const slideTrack = this.$refs.slider;
|
|
84
93
|
|
|
85
94
|
direction !== 'prev' ? (this.activeItemId++) : (this.activeItemId--);
|
|
86
95
|
|
|
@@ -88,21 +97,27 @@ export default {
|
|
|
88
97
|
},
|
|
89
98
|
|
|
90
99
|
slideTransition() {
|
|
91
|
-
const slideTrack = document.getElementById('slide-track');
|
|
92
100
|
const slidesArray = this.slider.length + 2;
|
|
93
101
|
|
|
94
102
|
if (this.activeItemId === -1) {
|
|
95
|
-
|
|
103
|
+
this.shouldDisableTransition = true;
|
|
96
104
|
this.activeItemId = this.slider.length - 1;
|
|
97
105
|
}
|
|
106
|
+
|
|
98
107
|
if (this.activeItemId === slidesArray - 2) {
|
|
99
|
-
|
|
108
|
+
this.shouldDisableTransition = true;
|
|
100
109
|
this.activeItemId = 0;
|
|
101
110
|
}
|
|
111
|
+
|
|
112
|
+
this.isTransitionning = false;
|
|
102
113
|
},
|
|
103
114
|
|
|
104
115
|
autoScrollSlide() {
|
|
105
|
-
if (this.
|
|
116
|
+
if (!this.autoScroll) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (this.activeItemId < (this.slider.length + 1)) {
|
|
106
121
|
this.activeItemId++;
|
|
107
122
|
}
|
|
108
123
|
|
|
@@ -120,23 +135,21 @@ export default {
|
|
|
120
135
|
},
|
|
121
136
|
|
|
122
137
|
mounted() {
|
|
123
|
-
const slideTrack =
|
|
138
|
+
const slideTrack = this.$refs.slider;
|
|
124
139
|
|
|
125
|
-
if (this.slider.length
|
|
126
|
-
|
|
127
|
-
} else {
|
|
128
|
-
const node = document.getElementById('slide0');
|
|
140
|
+
if (this.slider.length > 1) {
|
|
141
|
+
const firstSlide = this.$refs['slide0']?.[0];
|
|
129
142
|
|
|
130
|
-
if (
|
|
131
|
-
const clone =
|
|
143
|
+
if (firstSlide) {
|
|
144
|
+
const clone = firstSlide.cloneNode(true);
|
|
132
145
|
|
|
133
146
|
slideTrack.appendChild(clone);
|
|
134
147
|
}
|
|
135
148
|
|
|
136
|
-
const
|
|
149
|
+
const lastSlide = this.$refs[`slide${ this.slider.length - 1 }`]?.[0];
|
|
137
150
|
|
|
138
|
-
if (
|
|
139
|
-
const cloneLast =
|
|
151
|
+
if (lastSlide) {
|
|
152
|
+
const cloneLast = lastSlide.cloneNode(true);
|
|
140
153
|
|
|
141
154
|
slideTrack.insertBefore(cloneLast, slideTrack.children[0]);
|
|
142
155
|
}
|
|
@@ -159,7 +172,7 @@ export default {
|
|
|
159
172
|
<template>
|
|
160
173
|
<div
|
|
161
174
|
class="slider"
|
|
162
|
-
:class="{'
|
|
175
|
+
:class="{'disabled': sliders.length === 1}"
|
|
163
176
|
>
|
|
164
177
|
<div
|
|
165
178
|
id="slide-track"
|
|
@@ -170,8 +183,8 @@ export default {
|
|
|
170
183
|
<component
|
|
171
184
|
:is="asLink ? 'a' : 'div'"
|
|
172
185
|
v-for="(slide, i) in sliders"
|
|
173
|
-
:id="`slide`
|
|
174
|
-
ref="slide"
|
|
186
|
+
:id="`slide${i}`"
|
|
187
|
+
:ref="`slide${i}`"
|
|
175
188
|
:key="get(slide, keyField)"
|
|
176
189
|
class="slide"
|
|
177
190
|
:class="{'singleSlide': sliders.length === 1}"
|
|
@@ -187,7 +200,7 @@ export default {
|
|
|
187
200
|
<div class="slide-content-right">
|
|
188
201
|
<BadgeState
|
|
189
202
|
:label="slide.repoName"
|
|
190
|
-
color="
|
|
203
|
+
color="slide-badge mb-20"
|
|
191
204
|
/>
|
|
192
205
|
<h1>{{ slide.chartNameDisplay }}</h1>
|
|
193
206
|
<p>{{ slide.chartDescription }}</p>
|
|
@@ -196,12 +209,11 @@ export default {
|
|
|
196
209
|
</component>
|
|
197
210
|
</div>
|
|
198
211
|
<div
|
|
199
|
-
ref="prev"
|
|
200
212
|
role="button"
|
|
201
213
|
class="prev"
|
|
202
214
|
:aria-label="t('carousel.previous')"
|
|
203
215
|
:aria-disabled="sliders.length === 1"
|
|
204
|
-
:class="{'
|
|
216
|
+
:class="{'disabled': sliders.length === 1}"
|
|
205
217
|
tabindex="0"
|
|
206
218
|
@click="nextPrev('prev')"
|
|
207
219
|
@keyup.enter.space="nextPrev('prev')"
|
|
@@ -211,12 +223,11 @@ export default {
|
|
|
211
223
|
/>
|
|
212
224
|
</div>
|
|
213
225
|
<div
|
|
214
|
-
ref="next"
|
|
215
226
|
role="button"
|
|
216
227
|
class="next"
|
|
217
228
|
:aria-label="t('carousel.next')"
|
|
218
229
|
:aria-disabled="sliders.length === 1"
|
|
219
|
-
:class="{'
|
|
230
|
+
:class="{'disabled': sliders.length === 1}"
|
|
220
231
|
tabindex="0"
|
|
221
232
|
@click="nextPrev('next')"
|
|
222
233
|
@keyup.enter.space="nextPrev('next')"
|
|
@@ -226,8 +237,8 @@ export default {
|
|
|
226
237
|
/>
|
|
227
238
|
</div>
|
|
228
239
|
<div
|
|
240
|
+
v-if="sliders.length > 1"
|
|
229
241
|
class="controls"
|
|
230
|
-
:class="{'disable': sliders.length === 1}"
|
|
231
242
|
>
|
|
232
243
|
<div
|
|
233
244
|
v-for="(slide, i) in slider"
|
|
@@ -252,23 +263,21 @@ export default {
|
|
|
252
263
|
place-items: center;
|
|
253
264
|
overflow: hidden;
|
|
254
265
|
margin-bottom: 30px;
|
|
255
|
-
|
|
266
|
+
height: 245px;
|
|
256
267
|
|
|
257
|
-
&.
|
|
258
|
-
&.
|
|
268
|
+
&.disabled::before,
|
|
269
|
+
&.disabled::after {
|
|
259
270
|
display: none;
|
|
260
271
|
}
|
|
261
272
|
}
|
|
262
273
|
|
|
263
274
|
.slide-track {
|
|
264
275
|
display: flex;
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
transition: 1s ease-in-out;
|
|
268
|
-
left: 21%;
|
|
276
|
+
position: absolute;
|
|
277
|
+
top: 0;
|
|
269
278
|
}
|
|
270
279
|
|
|
271
|
-
.
|
|
280
|
+
.slide-badge {
|
|
272
281
|
background: var(--app-partner-accent);
|
|
273
282
|
color: var(--body-bg);
|
|
274
283
|
}
|
|
@@ -333,7 +342,7 @@ export default {
|
|
|
333
342
|
.slider::before {
|
|
334
343
|
left: 0;
|
|
335
344
|
top: 0;
|
|
336
|
-
&.
|
|
345
|
+
&.disabled {
|
|
337
346
|
display: none;
|
|
338
347
|
}
|
|
339
348
|
}
|
|
@@ -344,15 +353,13 @@ export default {
|
|
|
344
353
|
}
|
|
345
354
|
|
|
346
355
|
.controls {
|
|
356
|
+
position: absolute;
|
|
357
|
+
bottom: 0;
|
|
347
358
|
width: 100%;
|
|
348
359
|
display: flex;
|
|
349
360
|
justify-content: center;
|
|
350
361
|
margin-top: 10px;
|
|
351
362
|
|
|
352
|
-
&.disable {
|
|
353
|
-
display: none;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
363
|
.control-item {
|
|
357
364
|
width: 10px;
|
|
358
365
|
height: 10px;
|
|
@@ -48,9 +48,12 @@ export default {
|
|
|
48
48
|
<a
|
|
49
49
|
v-if="text"
|
|
50
50
|
class="copy-to-clipboard-text"
|
|
51
|
+
role="button"
|
|
52
|
+
:aria-label="t('generic.copyToClipboard')"
|
|
51
53
|
:class="{ 'copied': copied, 'plain': plain}"
|
|
52
54
|
href="#"
|
|
53
55
|
@click="clicked"
|
|
56
|
+
@keyup.space="clicked"
|
|
54
57
|
>
|
|
55
58
|
{{ text }} <i
|
|
56
59
|
class="icon"
|
package/components/Dialog.vue
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import AsyncButton from '@shell/components/AsyncButton';
|
|
3
|
-
import AppModal from '@shell/components/AppModal.vue';
|
|
3
|
+
import AppModal, { DEFAULT_ITERABLE_NODE_SELECTOR } from '@shell/components/AppModal.vue';
|
|
4
4
|
|
|
5
5
|
export default {
|
|
6
6
|
emits: ['okay', 'closed'],
|
|
@@ -21,6 +21,22 @@ export default {
|
|
|
21
21
|
mode: {
|
|
22
22
|
type: String,
|
|
23
23
|
default: '',
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* forcefully set return focus element based on this selector
|
|
28
|
+
*/
|
|
29
|
+
returnFocusSelector: {
|
|
30
|
+
type: String,
|
|
31
|
+
default: '',
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* will return focus to the first iterable node of this container select
|
|
36
|
+
*/
|
|
37
|
+
returnFocusFirstIterableNodeSelector: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: DEFAULT_ITERABLE_NODE_SELECTOR,
|
|
24
40
|
}
|
|
25
41
|
},
|
|
26
42
|
|
|
@@ -60,6 +76,9 @@ export default {
|
|
|
60
76
|
:name="name"
|
|
61
77
|
height="auto"
|
|
62
78
|
:scrollable="true"
|
|
79
|
+
:trigger-focus-trap="true"
|
|
80
|
+
:return-focus-selector="returnFocusSelector"
|
|
81
|
+
:return-focus-first-iterable-node-selector="returnFocusFirstIterableNodeSelector"
|
|
63
82
|
@close="closeDialog(false)"
|
|
64
83
|
@before-open="beforeOpen"
|
|
65
84
|
>
|
|
@@ -43,6 +43,7 @@ export default {
|
|
|
43
43
|
name="password-modal"
|
|
44
44
|
:width="500"
|
|
45
45
|
:height="465"
|
|
46
|
+
:trigger-focus-trap="true"
|
|
46
47
|
@close="show(false)"
|
|
47
48
|
>
|
|
48
49
|
<Card
|
|
@@ -68,6 +69,8 @@ export default {
|
|
|
68
69
|
<!-- type reset is required by lastpass -->
|
|
69
70
|
<button
|
|
70
71
|
class="btn role-secondary"
|
|
72
|
+
role="button"
|
|
73
|
+
:aria-label="t('changePassword.cancel')"
|
|
71
74
|
type="reset"
|
|
72
75
|
@click="show(false)"
|
|
73
76
|
>
|
|
@@ -276,12 +276,11 @@ export default {
|
|
|
276
276
|
:data-testid="`btn-${tab.name}`"
|
|
277
277
|
:aria-controls="'#' + tab.name"
|
|
278
278
|
:aria-selected="tab.active"
|
|
279
|
-
:aria-label="tab.labelDisplay"
|
|
279
|
+
:aria-label="tab.labelDisplay || ''"
|
|
280
280
|
role="tab"
|
|
281
281
|
tabindex="0"
|
|
282
282
|
@click.prevent="select(tab.name, $event)"
|
|
283
|
-
@keyup.enter="select(tab.name, $event)"
|
|
284
|
-
@keyup.space="select(tab.name, $event)"
|
|
283
|
+
@keyup.enter.space="select(tab.name, $event)"
|
|
285
284
|
>
|
|
286
285
|
<span>{{ tab.labelDisplay }}</span>
|
|
287
286
|
<span
|
|
@@ -409,10 +408,8 @@ export default {
|
|
|
409
408
|
|
|
410
409
|
&:focus-visible {
|
|
411
410
|
@include focus-outline;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
text-decoration: underline;
|
|
415
|
-
}
|
|
411
|
+
outline-offset: -4px;
|
|
412
|
+
text-decoration: none;
|
|
416
413
|
}
|
|
417
414
|
}
|
|
418
415
|
|
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import Carousel from '@shell/components/Carousel.vue';
|
|
3
3
|
|
|
4
|
+
const sliders = [
|
|
5
|
+
{
|
|
6
|
+
key: 'key-0',
|
|
7
|
+
repoName: 'some-repo-name-0',
|
|
8
|
+
chartNameDisplay: 'chart-name-display-0',
|
|
9
|
+
chartDescription: 'chart-description-0'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
key: 'key-1',
|
|
13
|
+
repoName: 'some-repo-name-1',
|
|
14
|
+
chartNameDisplay: 'chart-name-display-1',
|
|
15
|
+
chartDescription: 'chart-description-1'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
key: 'key-2',
|
|
19
|
+
repoName: 'some-repo-name-2',
|
|
20
|
+
chartNameDisplay: 'chart-name-display-2',
|
|
21
|
+
chartDescription: 'chart-description-2'
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
key: 'key-3',
|
|
25
|
+
repoName: 'some-repo-name-3',
|
|
26
|
+
chartNameDisplay: 'chart-name-display-3',
|
|
27
|
+
chartDescription: 'chart-description-3'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
key: 'key-4',
|
|
31
|
+
repoName: 'some-repo-name-4',
|
|
32
|
+
chartNameDisplay: 'chart-name-display-4',
|
|
33
|
+
chartDescription: 'chart-description-4'
|
|
34
|
+
}
|
|
35
|
+
];
|
|
36
|
+
|
|
4
37
|
describe('component: Carousel', () => {
|
|
5
38
|
it('should render component with the correct data applied', async() => {
|
|
6
|
-
const sliders = [
|
|
7
|
-
{
|
|
8
|
-
key: 'key-0',
|
|
9
|
-
repoName: 'some-repo-name-0',
|
|
10
|
-
chartNameDisplay: 'chart-name-display-0',
|
|
11
|
-
chartDescription: 'chart-description-0'
|
|
12
|
-
},
|
|
13
|
-
{
|
|
14
|
-
key: 'key-1',
|
|
15
|
-
repoName: 'some-repo-name-1',
|
|
16
|
-
chartNameDisplay: 'chart-name-display-1',
|
|
17
|
-
chartDescription: 'chart-description-1'
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
key: 'key-2',
|
|
21
|
-
repoName: 'some-repo-name-2',
|
|
22
|
-
chartNameDisplay: 'chart-name-display-2',
|
|
23
|
-
chartDescription: 'chart-description-2'
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
key: 'key-3',
|
|
27
|
-
repoName: 'some-repo-name-3',
|
|
28
|
-
chartNameDisplay: 'chart-name-display-3',
|
|
29
|
-
chartDescription: 'chart-description-3'
|
|
30
|
-
}
|
|
31
|
-
];
|
|
32
|
-
|
|
33
39
|
const wrapper = mount(Carousel, {
|
|
34
40
|
props: { sliders },
|
|
35
41
|
global: { mocks: { $store: { getters: { clusterId: () => 'some-cluster-id' } } } }
|
|
@@ -40,4 +46,27 @@ describe('component: Carousel', () => {
|
|
|
40
46
|
expect(wrapper.find(`#slide${ index } h1`).text()).toContain(slider.chartNameDisplay);
|
|
41
47
|
});
|
|
42
48
|
});
|
|
49
|
+
|
|
50
|
+
it.each([
|
|
51
|
+
[sliders.slice(0, 2)],
|
|
52
|
+
[sliders.slice(0, 3)],
|
|
53
|
+
[sliders.slice(0, 4)],
|
|
54
|
+
[sliders.slice(0, 5)]
|
|
55
|
+
])('should have the correct width and left position', async(sliders) => {
|
|
56
|
+
const wrapper = mount(Carousel, {
|
|
57
|
+
props: { sliders },
|
|
58
|
+
global: { mocks: { $store: { getters: { clusterId: () => 'some-cluster-id' } } } }
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const width = 60 * (wrapper.vm.slider.length + 2);
|
|
62
|
+
const initialLeft = -(40 + wrapper.vm.activeItemId * 60);
|
|
63
|
+
|
|
64
|
+
expect(wrapper.vm.trackStyle).toContain(`width: ${ width }%`);
|
|
65
|
+
expect(wrapper.vm.trackStyle).toContain(`left: ${ initialLeft }`);
|
|
66
|
+
|
|
67
|
+
wrapper.vm.activeItemId = wrapper.vm.activeItemId + 1; // next slide
|
|
68
|
+
expect(wrapper.vm.trackStyle).toContain(`left: ${ -(40 + wrapper.vm.activeItemId * 60) }`);
|
|
69
|
+
wrapper.vm.activeItemId = wrapper.vm.activeItemId - 1; // previous slide
|
|
70
|
+
expect(wrapper.vm.trackStyle).toContain(`left: ${ initialLeft }`);
|
|
71
|
+
});
|
|
43
72
|
});
|
|
@@ -291,7 +291,7 @@ export default {
|
|
|
291
291
|
]"
|
|
292
292
|
:tabindex="isView || disabled ? -1 : 0"
|
|
293
293
|
@click="focusSearch"
|
|
294
|
-
@
|
|
294
|
+
@keydown.enter.space.down="focusSearch"
|
|
295
295
|
>
|
|
296
296
|
<div
|
|
297
297
|
:class="{ 'labeled-container': true, raised, empty, [mode]: true }"
|