@rancher/shell 0.3.15 → 0.3.17
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/wechat-qr-code.jpg +0 -0
- package/assets/translations/en-us.yaml +70 -15
- package/assets/translations/zh-hans.yaml +155 -33
- package/chart/__tests__/S3.test.ts +50 -0
- package/chart/rancher-backup/S3.vue +21 -0
- package/chart/rancher-backup/index.vue +4 -0
- package/cloud-credential/generic.vue +1 -1
- package/components/BannerGraphic.vue +1 -0
- package/components/CommunityLinks.vue +1 -0
- package/components/CruResource.vue +1 -1
- package/components/EmberPage.vue +1 -0
- package/components/FileDiff.vue +92 -85
- package/components/GrafanaDashboard.vue +7 -1
- package/components/ResourceDetail/index.vue +4 -12
- package/components/ResourceList/index.vue +1 -1
- package/components/ResourceTable.vue +50 -2
- package/components/SimpleBox.vue +1 -0
- package/components/SortableTable/index.vue +5 -1
- package/components/YamlEditor.vue +1 -0
- package/components/auth/RoleDetailEdit.vue +1 -0
- package/components/form/GitPicker.vue +1 -1
- package/components/form/NameNsDescription.vue +28 -12
- package/components/form/NodeAffinity.vue +2 -2
- package/components/form/PodAffinity.vue +8 -3
- package/components/form/ResourceTabs/index.vue +8 -2
- package/components/form/Select.vue +16 -0
- package/components/form/__tests__/NodeAffinity.test.ts +38 -0
- package/components/form/__tests__/PodAffinity.test.ts +46 -0
- package/components/formatter/ClusterLink.vue +8 -4
- package/components/formatter/ImageName.vue +23 -0
- package/components/formatter/PodImages.vue +7 -1
- package/components/formatter/__tests__/ClusterLink.test.ts +101 -0
- package/components/nav/Header.vue +2 -2
- package/config/__test__/home-links.test.ts +62 -0
- package/config/home-links.js +15 -3
- package/config/labels-annotations.js +5 -1
- package/config/product/auth.js +1 -1
- package/config/router.js +0 -9
- package/config/settings.ts +4 -0
- package/config/table-headers.js +6 -5
- package/config/uiplugins.js +50 -5
- package/core/plugin-helpers.js +20 -12
- package/core/plugin.ts +9 -0
- package/core/plugins.js +1 -1
- package/core/types-provisioning.ts +253 -0
- package/core/types.ts +17 -3
- package/detail/autoscaling.horizontalpodautoscaler/index.vue +50 -1
- package/detail/catalog.cattle.io.clusterrepo.vue +8 -1
- package/detail/node.vue +6 -6
- package/detail/pod.vue +2 -6
- package/detail/provisioning.cattle.io.cluster.vue +46 -7
- package/detail/workload/index.vue +9 -9
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +62 -0
- package/edit/__tests__/monitoring.coreos.com.prometheusrule.test.ts +56 -0
- package/edit/auth/github.vue +1 -0
- package/edit/autoscaling.horizontalpodautoscaler/hpa-scaling-rule.vue +130 -0
- package/edit/autoscaling.horizontalpodautoscaler/index.vue +79 -0
- package/edit/fleet.cattle.io.gitrepo.vue +18 -1
- package/edit/monitoring.coreos.com.prometheusrule/index.vue +8 -3
- package/edit/namespace.vue +9 -1
- package/edit/networking.k8s.io.ingress/RulePath.vue +0 -2
- package/edit/provisioning.cattle.io.cluster/AgentConfiguration.vue +1 -30
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +79 -1
- package/edit/provisioning.cattle.io.cluster/index.vue +52 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +330 -150
- package/edit/ui.cattle.io.navlink.vue +2 -1
- package/initialize/App.js +3 -13
- package/initialize/layouts.ts +26 -0
- package/list/provisioning.cattle.io.cluster.vue +8 -1
- package/middleware/authenticated.js +93 -5
- package/mixins/brand.js +39 -3
- package/mixins/child-hook.js +2 -2
- package/mixins/create-edit-view/impl.js +2 -2
- package/models/fleet.cattle.io.gitrepo.js +1 -0
- package/models/provisioning.cattle.io.cluster.js +9 -1
- package/package.json +3 -3
- package/pages/about.vue +8 -2
- package/pages/auth/login.vue +10 -0
- package/pages/auth/logout.vue +11 -3
- package/pages/auth/setup.vue +4 -0
- package/pages/c/_cluster/apps/charts/index.vue +5 -2
- package/pages/c/_cluster/apps/charts/install.vue +5 -0
- package/pages/c/_cluster/auth/roles/index.vue +1 -1
- package/pages/c/_cluster/explorer/index.vue +1 -10
- package/pages/c/_cluster/uiplugins/AddExtensionRepos.vue +177 -0
- package/pages/c/_cluster/uiplugins/PluginInfoPanel.vue +19 -3
- package/pages/c/_cluster/uiplugins/RemoveUIPlugins.vue +90 -21
- package/pages/c/_cluster/uiplugins/SetupUIPlugins.vue +107 -37
- package/pages/c/_cluster/uiplugins/index.vue +155 -44
- package/pages/docs/_doc.vue +9 -3
- package/pages/home.vue +10 -5
- package/pages/support/index.vue +10 -4
- package/pkg/auto-import.js +1 -1
- package/plugins/clean-tooltip-directive.js +1 -1
- package/plugins/dashboard-store/resource-class.js +35 -2
- package/plugins/plugin.js +9 -1
- package/plugins/steve/actions.js +22 -0
- package/rancher-components/BadgeState/BadgeState.vue +5 -1
- package/rancher-components/Banner/Banner.test.ts +51 -1
- package/rancher-components/Banner/Banner.vue +134 -53
- package/rancher-components/Card/Card.vue +24 -7
- package/rancher-components/Form/Checkbox/Checkbox.test.ts +20 -29
- package/rancher-components/Form/Checkbox/Checkbox.vue +45 -20
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +2 -8
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +22 -10
- package/rancher-components/Form/Radio/RadioButton.vue +30 -13
- package/rancher-components/Form/Radio/RadioGroup.vue +26 -7
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +7 -6
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.test.ts +25 -38
- package/rancher-components/Form/ToggleSwitch/ToggleSwitch.vue +23 -11
- package/rancher-components/LabeledTooltip/LabeledTooltip.vue +19 -5
- package/rancher-components/StringList/StringList.test.ts +453 -49
- package/rancher-components/StringList/StringList.vue +92 -58
- package/scripts/extension/publish +2 -2
- package/scripts/typegen.sh +1 -0
- package/server/server-middleware.js +4 -12
- package/store/index.js +13 -0
- package/store/prefs.js +0 -3
- package/store/type-map.js +17 -29
- package/types/shell/index.d.ts +243 -90
- package/utils/kube.js +9 -0
- package/utils/object.js +27 -0
- package/utils/settings.ts +2 -2
- package/vue.config.js +3 -2
- package/pages/safeMode.vue +0 -17
- package/rancher-components/components/BadgeState/BadgeState.spec.ts +0 -12
- package/rancher-components/components/BadgeState/BadgeState.vue +0 -111
- package/rancher-components/components/BadgeState/index.ts +0 -1
- package/rancher-components/components/Banner/Banner.test.ts +0 -63
- package/rancher-components/components/Banner/Banner.vue +0 -244
- package/rancher-components/components/Banner/index.ts +0 -1
- package/rancher-components/components/Card/Card.vue +0 -167
- package/rancher-components/components/Card/index.ts +0 -1
- package/rancher-components/components/Form/Checkbox/Checkbox.test.ts +0 -68
- package/rancher-components/components/Form/Checkbox/Checkbox.vue +0 -420
- package/rancher-components/components/Form/Checkbox/index.ts +0 -1
- package/rancher-components/components/Form/LabeledInput/LabeledInput.test.ts +0 -23
- package/rancher-components/components/Form/LabeledInput/LabeledInput.vue +0 -355
- package/rancher-components/components/Form/LabeledInput/index.ts +0 -1
- package/rancher-components/components/Form/Radio/RadioButton.vue +0 -287
- package/rancher-components/components/Form/Radio/RadioGroup.vue +0 -254
- package/rancher-components/components/Form/Radio/index.ts +0 -2
- package/rancher-components/components/Form/TextArea/TextAreaAutoGrow.vue +0 -170
- package/rancher-components/components/Form/TextArea/index.ts +0 -1
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.test.ts +0 -94
- package/rancher-components/components/Form/ToggleSwitch/ToggleSwitch.vue +0 -149
- package/rancher-components/components/Form/ToggleSwitch/index.ts +0 -1
- package/rancher-components/components/Form/index.ts +0 -5
- package/rancher-components/components/LabeledTooltip/LabeledTooltip.vue +0 -151
- package/rancher-components/components/LabeledTooltip/index.ts +0 -1
- package/rancher-components/components/StringList/StringList.test.ts +0 -484
- package/rancher-components/components/StringList/StringList.vue +0 -611
- package/rancher-components/components/StringList/index.ts +0 -1
- /package/rancher-components/{components/Card → Card}/Card.test.ts +0 -0
- /package/rancher-components/{components/Form → Form}/Radio/RadioButton.test.ts +0 -0
package/plugins/plugin.js
CHANGED
|
@@ -12,9 +12,17 @@ export default async function(context) {
|
|
|
12
12
|
// Provide a mechanism to load the UI without the plugins loaded - in case there is a problem
|
|
13
13
|
let loadPlugins = true;
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
const queryKeys = Object.keys(context.route?.query || {}).map((q) => q.toLowerCase());
|
|
16
|
+
|
|
17
|
+
if (queryKeys.includes('safemode')) {
|
|
16
18
|
loadPlugins = false;
|
|
17
19
|
console.warn('Safe Mode - plugins will not be loaded'); // eslint-disable-line no-console
|
|
20
|
+
setTimeout(() => {
|
|
21
|
+
context.store.dispatch('growl/success', {
|
|
22
|
+
title: context.store.getters['i18n/t']('plugins.safeMode.title'),
|
|
23
|
+
message: context.store.getters['i18n/t']('plugins.safeMode.message')
|
|
24
|
+
}, { root: true });
|
|
25
|
+
}, 1000);
|
|
18
26
|
}
|
|
19
27
|
|
|
20
28
|
if (loadPlugins) {
|
package/plugins/steve/actions.js
CHANGED
|
@@ -128,6 +128,10 @@ export default {
|
|
|
128
128
|
|
|
129
129
|
finishDeferred(key, 'resolve', out);
|
|
130
130
|
|
|
131
|
+
if (opt.method === 'post' || opt.method === 'put') {
|
|
132
|
+
handleValidationWarnings(res);
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
return out;
|
|
132
136
|
});
|
|
133
137
|
}
|
|
@@ -192,6 +196,24 @@ export default {
|
|
|
192
196
|
|
|
193
197
|
return Promise.reject(out);
|
|
194
198
|
}
|
|
199
|
+
|
|
200
|
+
function handleValidationWarnings(res) {
|
|
201
|
+
const warnings = (res.headers?.warning || '').split(',');
|
|
202
|
+
|
|
203
|
+
if (!warnings.length || !warnings[0]) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const message = warnings.reduce((message, warning) => {
|
|
208
|
+
return `${ message }\n${ warning.trim() }`;
|
|
209
|
+
}, `Validation Warnings for ${ opt.url }\n`);
|
|
210
|
+
|
|
211
|
+
if (process.env.dev) {
|
|
212
|
+
console.warn(`${ message }\n\n`, res.data); // eslint-disable-line no-console
|
|
213
|
+
} else {
|
|
214
|
+
console.debug(message); // eslint-disable-line no-console
|
|
215
|
+
}
|
|
216
|
+
}
|
|
195
217
|
},
|
|
196
218
|
|
|
197
219
|
promptMove({ commit, state }, resources) {
|
|
@@ -60,7 +60,11 @@ export default Vue.extend({
|
|
|
60
60
|
|
|
61
61
|
<template>
|
|
62
62
|
<span :class="{'badge-state': true, [bg]: true}">
|
|
63
|
-
<i
|
|
63
|
+
<i
|
|
64
|
+
v-if="icon"
|
|
65
|
+
class="icon"
|
|
66
|
+
:class="{[icon]: true, 'mr-5': !!msg}"
|
|
67
|
+
/>{{ msg }}
|
|
64
68
|
</span>
|
|
65
69
|
</template>
|
|
66
70
|
|
|
@@ -1,13 +1,63 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { Banner } from './index';
|
|
3
|
+
import { cleanHtmlDirective } from '@shell/plugins/clean-html-directive';
|
|
3
4
|
|
|
4
5
|
describe('component: Banner', () => {
|
|
5
6
|
it('should display text based on label', () => {
|
|
6
7
|
const label = 'test';
|
|
7
|
-
const wrapper = mount(
|
|
8
|
+
const wrapper = mount(
|
|
9
|
+
Banner,
|
|
10
|
+
{
|
|
11
|
+
directives: { cleanHtmlDirective },
|
|
12
|
+
propsData: { label }
|
|
13
|
+
});
|
|
8
14
|
|
|
9
15
|
const element = wrapper.find('span').element;
|
|
10
16
|
|
|
11
17
|
expect(element.textContent).toBe(label);
|
|
12
18
|
});
|
|
19
|
+
|
|
20
|
+
it('should display an icon', () => {
|
|
21
|
+
const icon = 'my-icon';
|
|
22
|
+
const wrapper = mount(Banner, { propsData: { icon } });
|
|
23
|
+
|
|
24
|
+
const element = wrapper.find(`.${ icon }`).element;
|
|
25
|
+
|
|
26
|
+
expect(element.classList).toContain(icon);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should not display an icon', () => {
|
|
30
|
+
const wrapper = mount(Banner);
|
|
31
|
+
|
|
32
|
+
const element = wrapper.find(`[data-testid="banner-icon"]`).element;
|
|
33
|
+
|
|
34
|
+
expect(element).not.toBeDefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should emit close event', () => {
|
|
38
|
+
const wrapper = mount(Banner, { propsData: { closable: true } });
|
|
39
|
+
const element = wrapper.find(`[data-testid="banner-close"]`).element;
|
|
40
|
+
|
|
41
|
+
element.click();
|
|
42
|
+
|
|
43
|
+
expect(wrapper.emitted('close')).toHaveLength(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should add the right color', () => {
|
|
47
|
+
const color = 'red';
|
|
48
|
+
const wrapper = mount(Banner, { propsData: { color } });
|
|
49
|
+
|
|
50
|
+
const element = wrapper.element;
|
|
51
|
+
|
|
52
|
+
expect(element.classList).toContain(color);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should stack the banner messages', () => {
|
|
56
|
+
const stacked = true;
|
|
57
|
+
const wrapper = mount(Banner, { propsData: { stacked } });
|
|
58
|
+
|
|
59
|
+
const element = wrapper.find(`[data-testid="banner-content"]`).element;
|
|
60
|
+
|
|
61
|
+
expect(element.classList).toContain('stacked');
|
|
62
|
+
});
|
|
13
63
|
});
|
|
@@ -27,6 +27,13 @@ export default Vue.extend({
|
|
|
27
27
|
type: String,
|
|
28
28
|
default: null
|
|
29
29
|
},
|
|
30
|
+
/**
|
|
31
|
+
* Add icon for the banner
|
|
32
|
+
*/
|
|
33
|
+
icon: {
|
|
34
|
+
type: String,
|
|
35
|
+
default: null
|
|
36
|
+
},
|
|
30
37
|
/**
|
|
31
38
|
* Toggles the banner's close button.
|
|
32
39
|
*/
|
|
@@ -58,31 +65,137 @@ export default Vue.extend({
|
|
|
58
65
|
class="banner"
|
|
59
66
|
:class="{
|
|
60
67
|
[color]: true,
|
|
61
|
-
closable,
|
|
62
|
-
stacked
|
|
63
68
|
}"
|
|
64
69
|
>
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
<div
|
|
71
|
+
v-if="icon"
|
|
72
|
+
class="banner__icon"
|
|
73
|
+
data-testid="banner-icon"
|
|
74
|
+
>
|
|
75
|
+
<i
|
|
76
|
+
class="icon icon-2x"
|
|
77
|
+
:class="icon"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div
|
|
81
|
+
class="banner__content"
|
|
82
|
+
data-testid="banner-content"
|
|
83
|
+
:class="{
|
|
84
|
+
closable,
|
|
85
|
+
stacked,
|
|
86
|
+
icon
|
|
87
|
+
}"
|
|
88
|
+
>
|
|
89
|
+
<slot>
|
|
90
|
+
<t
|
|
91
|
+
v-if="labelKey"
|
|
92
|
+
:k="labelKey"
|
|
93
|
+
:raw="true"
|
|
94
|
+
/>
|
|
95
|
+
<span v-else-if="messageLabel">{{ messageLabel }}</span>
|
|
96
|
+
<span
|
|
97
|
+
v-else
|
|
98
|
+
v-clean-html="nlToBr(label)"
|
|
99
|
+
/>
|
|
100
|
+
</slot>
|
|
101
|
+
<div
|
|
102
|
+
v-if="closable"
|
|
103
|
+
class="banner__content__closer"
|
|
104
|
+
@click="$emit('close')"
|
|
105
|
+
>
|
|
106
|
+
<i
|
|
107
|
+
data-testid="banner-close"
|
|
108
|
+
class="icon icon-close closer-icon"
|
|
109
|
+
/>
|
|
110
|
+
</div>
|
|
72
111
|
</div>
|
|
73
112
|
</div>
|
|
74
113
|
</template>
|
|
75
114
|
|
|
76
115
|
<style lang="scss" scoped>
|
|
77
|
-
|
|
116
|
+
$left-border-size: 4px;
|
|
117
|
+
$icon-size: 24px;
|
|
118
|
+
|
|
119
|
+
.banner {
|
|
120
|
+
display: flex;
|
|
121
|
+
margin: 15px 0;
|
|
122
|
+
position: relative;
|
|
123
|
+
width: 100%;
|
|
124
|
+
color: var(--body-text);
|
|
125
|
+
|
|
126
|
+
&__icon {
|
|
127
|
+
width: $icon-size * 2;
|
|
128
|
+
flex-grow: 1;
|
|
129
|
+
display: flex;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
align-items: center;
|
|
132
|
+
box-sizing: content-box;
|
|
133
|
+
|
|
134
|
+
.primary & {
|
|
135
|
+
background: var(--primary);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.secondary & {
|
|
139
|
+
background: var(--default);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.success & {
|
|
143
|
+
background: var(--success);
|
|
144
|
+
}
|
|
78
145
|
|
|
79
|
-
|
|
146
|
+
.info & {
|
|
147
|
+
background: var(--info);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.warning & {
|
|
151
|
+
background: var(--warning);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.error & {
|
|
155
|
+
background: var(--error);
|
|
156
|
+
color: var(--primary-text);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
&__content {
|
|
80
161
|
padding: 10px;
|
|
81
|
-
margin: 15px 0;
|
|
82
|
-
width: 100%;
|
|
83
162
|
transition: all 0.2s ease;
|
|
84
|
-
position: relative;
|
|
85
163
|
line-height: 20px;
|
|
164
|
+
width: 100%;
|
|
165
|
+
border-left: solid $left-border-size transparent;
|
|
166
|
+
display: flex;
|
|
167
|
+
gap: 3px;
|
|
168
|
+
|
|
169
|
+
.primary & {
|
|
170
|
+
background: var(--primary);
|
|
171
|
+
border-color: var(--primary);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.secondary & {
|
|
175
|
+
background: var(--default-banner-bg);
|
|
176
|
+
border-color: var(--default);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.success & {
|
|
180
|
+
background: var(--success-banner-bg);
|
|
181
|
+
border-color: var(--success);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.info & {
|
|
185
|
+
background: var(--info-banner-bg);
|
|
186
|
+
border-color: var(--info);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.warning & {
|
|
190
|
+
background: var(--warning-banner-bg);
|
|
191
|
+
border-color: var(--warning);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.error & {
|
|
195
|
+
background: var(--error-banner-bg);
|
|
196
|
+
border-color: var(--error);
|
|
197
|
+
color: var(--error);
|
|
198
|
+
}
|
|
86
199
|
|
|
87
200
|
&.stacked {
|
|
88
201
|
padding: 0 10px;
|
|
@@ -97,10 +210,10 @@ export default Vue.extend({
|
|
|
97
210
|
}
|
|
98
211
|
|
|
99
212
|
&.closable {
|
|
100
|
-
padding-right:
|
|
213
|
+
padding-right: $icon-size * 2;
|
|
101
214
|
}
|
|
102
215
|
|
|
103
|
-
|
|
216
|
+
&__closer {
|
|
104
217
|
display: flex;
|
|
105
218
|
align-items: center;
|
|
106
219
|
|
|
@@ -109,12 +222,11 @@ export default Vue.extend({
|
|
|
109
222
|
top: 0;
|
|
110
223
|
right: 0;
|
|
111
224
|
bottom: 0;
|
|
112
|
-
width:
|
|
113
|
-
line-height:
|
|
225
|
+
width: $icon-size;
|
|
226
|
+
line-height: $icon-size;
|
|
114
227
|
text-align: center;
|
|
115
228
|
|
|
116
229
|
.closer-icon {
|
|
117
|
-
font-size: 22px;
|
|
118
230
|
opacity: 0.7;
|
|
119
231
|
|
|
120
232
|
&:hover {
|
|
@@ -124,40 +236,9 @@ export default Vue.extend({
|
|
|
124
236
|
}
|
|
125
237
|
}
|
|
126
238
|
|
|
127
|
-
&.
|
|
128
|
-
|
|
129
|
-
border-left: solid $left-border-size var(--primary);
|
|
130
|
-
color: var(--body-text);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
&.secondary {
|
|
134
|
-
background: var(--default-banner-bg);
|
|
135
|
-
border-left: solid $left-border-size var(--default);
|
|
136
|
-
color: var(--body-text);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
&.success {
|
|
140
|
-
background: var(--success-banner-bg);
|
|
141
|
-
border-left: solid $left-border-size var(--success);
|
|
142
|
-
color: var(--body-text);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
&.info {
|
|
146
|
-
background: var(--info-banner-bg);
|
|
147
|
-
border-left: solid $left-border-size var(--info);
|
|
148
|
-
color: var(--body-text);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
&.warning {
|
|
152
|
-
background: var(--warning-banner-bg);
|
|
153
|
-
border-left: solid $left-border-size var(--warning);
|
|
154
|
-
color: var(--body-text);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
&.error {
|
|
158
|
-
background: var(--error-banner-bg);
|
|
159
|
-
border-left: solid $left-border-size var(--error);
|
|
160
|
-
color: var(--error);
|
|
239
|
+
&.icon {
|
|
240
|
+
border-left: none;
|
|
161
241
|
}
|
|
162
242
|
}
|
|
243
|
+
}
|
|
163
244
|
</style>
|
|
@@ -49,28 +49,45 @@ export default Vue.extend({
|
|
|
49
49
|
sticky: {
|
|
50
50
|
type: Boolean,
|
|
51
51
|
default: false,
|
|
52
|
-
},
|
|
52
|
+
},
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
</script>
|
|
56
56
|
|
|
57
57
|
<template>
|
|
58
|
-
<div
|
|
58
|
+
<div
|
|
59
|
+
class="card-container"
|
|
60
|
+
:class="{'highlight-border': showHighlightBorder, 'card-sticky': sticky}"
|
|
61
|
+
data-testid="card"
|
|
62
|
+
>
|
|
59
63
|
<div class="card-wrap">
|
|
60
|
-
<div
|
|
64
|
+
<div
|
|
65
|
+
class="card-title"
|
|
66
|
+
data-testid="card-title-slot"
|
|
67
|
+
>
|
|
61
68
|
<slot name="title">
|
|
62
69
|
{{ title }}
|
|
63
70
|
</slot>
|
|
64
71
|
</div>
|
|
65
|
-
<hr
|
|
66
|
-
<div
|
|
72
|
+
<hr>
|
|
73
|
+
<div
|
|
74
|
+
class="card-body"
|
|
75
|
+
data-testid="card-body-slot"
|
|
76
|
+
>
|
|
67
77
|
<slot name="body">
|
|
68
78
|
{{ content }}
|
|
69
79
|
</slot>
|
|
70
80
|
</div>
|
|
71
|
-
<div
|
|
81
|
+
<div
|
|
82
|
+
v-if="showActions"
|
|
83
|
+
class="card-actions"
|
|
84
|
+
data-testid="card-actions-slot"
|
|
85
|
+
>
|
|
72
86
|
<slot name="actions">
|
|
73
|
-
<button
|
|
87
|
+
<button
|
|
88
|
+
class="btn role-primary"
|
|
89
|
+
@click="buttonAction"
|
|
90
|
+
>
|
|
74
91
|
{{ buttonText }}
|
|
75
92
|
</button>
|
|
76
93
|
</slot>
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import { shallowMount } from '@vue/test-utils';
|
|
1
|
+
import { shallowMount, Wrapper } from '@vue/test-utils';
|
|
2
2
|
import { Checkbox } from './index';
|
|
3
3
|
|
|
4
|
-
describe('
|
|
4
|
+
describe('checkbox.vue', () => {
|
|
5
|
+
const event = {
|
|
6
|
+
target: { tagName: 'input', href: null },
|
|
7
|
+
stopPropagation: () => { },
|
|
8
|
+
preventDefault: () => { }
|
|
9
|
+
} as unknown as MouseEvent;
|
|
10
|
+
|
|
5
11
|
it('is unchecked by default', () => {
|
|
6
12
|
const wrapper = shallowMount(Checkbox);
|
|
7
13
|
const cbInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
@@ -16,7 +22,7 @@ describe('Checkbox.vue', () => {
|
|
|
16
22
|
expect(cbInput.checked).toBe(true);
|
|
17
23
|
});
|
|
18
24
|
|
|
19
|
-
it('updates from false to true when props change', async
|
|
25
|
+
it('updates from false to true when props change', async() => {
|
|
20
26
|
const wrapper = shallowMount(Checkbox);
|
|
21
27
|
const cbInput = wrapper.find('input[type="checkbox"]').element as HTMLInputElement;
|
|
22
28
|
|
|
@@ -27,51 +33,36 @@ describe('Checkbox.vue', () => {
|
|
|
27
33
|
expect(cbInput.checked).toBe(true);
|
|
28
34
|
});
|
|
29
35
|
|
|
30
|
-
it('emits an input event with a true value', async
|
|
31
|
-
const wrapper = shallowMount(Checkbox);
|
|
32
|
-
const event = {
|
|
33
|
-
target: { tagName: 'input', href: null },
|
|
34
|
-
stopPropagation: () => { },
|
|
35
|
-
preventDefault: () => { }
|
|
36
|
-
};
|
|
36
|
+
it('emits an input event with a true value', async() => {
|
|
37
|
+
const wrapper: Wrapper<InstanceType<typeof Checkbox>> = shallowMount(Checkbox);
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
wrapper.vm.clicked(event);
|
|
39
40
|
await wrapper.vm.$nextTick();
|
|
40
41
|
|
|
41
42
|
expect(wrapper.emitted().input?.length).toBe(1);
|
|
42
43
|
expect(wrapper.emitted().input?.[0][0]).toBe(true);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
it('emits an input event with a custom valueWhenTrue', async
|
|
46
|
+
it('emits an input event with a custom valueWhenTrue', async() => {
|
|
46
47
|
const valueWhenTrue = 'BIG IF TRUE';
|
|
47
|
-
const event = {
|
|
48
|
-
target: { tagName: 'input', href: null },
|
|
49
|
-
stopPropagation: () => { },
|
|
50
|
-
preventDefault: () => { }
|
|
51
|
-
};
|
|
52
48
|
|
|
53
|
-
const wrapper = shallowMount(Checkbox, { propsData: { value: false, valueWhenTrue } });
|
|
49
|
+
const wrapper: Wrapper<InstanceType<typeof Checkbox>> = shallowMount(Checkbox, { propsData: { value: false, valueWhenTrue } });
|
|
54
50
|
|
|
55
|
-
|
|
51
|
+
wrapper.vm.clicked(event);
|
|
56
52
|
await wrapper.vm.$nextTick();
|
|
57
53
|
|
|
58
54
|
expect(wrapper.emitted().input?.length).toBe(1);
|
|
59
55
|
expect(wrapper.emitted().input?.[0][0]).toBe(valueWhenTrue);
|
|
60
56
|
});
|
|
61
57
|
|
|
62
|
-
it('updates from valueWhenTrue to falsy', async
|
|
58
|
+
it('updates from valueWhenTrue to falsy', async() => {
|
|
63
59
|
const valueWhenTrue = 'REAL HUGE IF FALSE';
|
|
64
|
-
const event = {
|
|
65
|
-
target: { tagName: 'input', href: null },
|
|
66
|
-
stopPropagation: () => { },
|
|
67
|
-
preventDefault: () => { }
|
|
68
|
-
};
|
|
69
60
|
|
|
70
|
-
const wrapper = shallowMount(Checkbox, { propsData: { value: valueWhenTrue, valueWhenTrue } });
|
|
61
|
+
const wrapper: Wrapper<InstanceType<typeof Checkbox>> = shallowMount(Checkbox, { propsData: { value: valueWhenTrue, valueWhenTrue } });
|
|
71
62
|
|
|
72
|
-
|
|
63
|
+
wrapper.vm.clicked(event);
|
|
73
64
|
await wrapper.vm.$nextTick();
|
|
74
65
|
|
|
75
|
-
expect(wrapper.emitted().input?.[0][0]).
|
|
76
|
-
})
|
|
66
|
+
expect(wrapper.emitted().input?.[0][0]).toBeNull();
|
|
67
|
+
});
|
|
77
68
|
});
|
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
import Vue, { PropType } from 'vue';
|
|
3
3
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
4
4
|
import { addObject, removeObject } from '@shell/utils/array';
|
|
5
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
5
6
|
|
|
6
7
|
export default Vue.extend({
|
|
8
|
+
name: 'Checkbox',
|
|
9
|
+
|
|
7
10
|
props: {
|
|
8
11
|
/**
|
|
9
12
|
* The checkbox value.
|
|
@@ -46,8 +49,8 @@ export default Vue.extend({
|
|
|
46
49
|
},
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
|
-
* Display an indeterminate state. Useful for cases where a checkbox might
|
|
50
|
-
* be the parent to child checkboxes, and we need to show that a subset of
|
|
52
|
+
* Display an indeterminate state. Useful for cases where a checkbox might
|
|
53
|
+
* be the parent to child checkboxes, and we need to show that a subset of
|
|
51
54
|
* children are checked.
|
|
52
55
|
*/
|
|
53
56
|
indeterminate: {
|
|
@@ -110,22 +113,22 @@ export default Vue.extend({
|
|
|
110
113
|
primary: {
|
|
111
114
|
type: Boolean,
|
|
112
115
|
default: false
|
|
113
|
-
},
|
|
116
|
+
},
|
|
114
117
|
},
|
|
115
118
|
|
|
116
119
|
computed: {
|
|
117
120
|
/**
|
|
118
121
|
* Determines if the checkbox is disabled.
|
|
119
|
-
* @returns boolean: True when the disabled prop is true or when mode is
|
|
122
|
+
* @returns boolean: True when the disabled prop is true or when mode is
|
|
120
123
|
* View.
|
|
121
124
|
*/
|
|
122
125
|
isDisabled(): boolean {
|
|
123
126
|
return (this.disabled || this.mode === _VIEW);
|
|
124
127
|
},
|
|
125
128
|
/**
|
|
126
|
-
* Determines if the checkbox is checked when using custom values or
|
|
129
|
+
* Determines if the checkbox is checked when using custom values or
|
|
127
130
|
* multiple values.
|
|
128
|
-
* @returns boolean: True when at least one value is true in a collection or
|
|
131
|
+
* @returns boolean: True when at least one value is true in a collection or
|
|
129
132
|
* when value matches `this.valueWhenTrue`.
|
|
130
133
|
*/
|
|
131
134
|
isChecked(): boolean {
|
|
@@ -162,13 +165,15 @@ export default Vue.extend({
|
|
|
162
165
|
const click = new CustomEvent('click', customEvent);
|
|
163
166
|
|
|
164
167
|
// Flip the value
|
|
165
|
-
|
|
168
|
+
const value = cloneDeep(this.value);
|
|
169
|
+
|
|
170
|
+
if (this.isMulti(value)) {
|
|
166
171
|
if (this.isChecked) {
|
|
167
|
-
removeObject(
|
|
172
|
+
removeObject(value, this.valueWhenTrue);
|
|
168
173
|
} else {
|
|
169
|
-
addObject(
|
|
174
|
+
addObject(value, this.valueWhenTrue);
|
|
170
175
|
}
|
|
171
|
-
this.$emit('input',
|
|
176
|
+
this.$emit('input', value);
|
|
172
177
|
} else if (this.isString(this.valueWhenTrue)) {
|
|
173
178
|
if (this.isChecked) {
|
|
174
179
|
this.$emit('input', null);
|
|
@@ -176,7 +181,7 @@ export default Vue.extend({
|
|
|
176
181
|
this.$emit('input', this.valueWhenTrue);
|
|
177
182
|
}
|
|
178
183
|
} else {
|
|
179
|
-
this.$emit('input', !
|
|
184
|
+
this.$emit('input', !value);
|
|
180
185
|
this.$el.dispatchEvent(click);
|
|
181
186
|
}
|
|
182
187
|
},
|
|
@@ -197,14 +202,17 @@ export default Vue.extend({
|
|
|
197
202
|
* @param value A collection of values for the checkbox.
|
|
198
203
|
*/
|
|
199
204
|
findTrueValues(value: boolean[]): boolean {
|
|
200
|
-
return value.find(v => v === this.valueWhenTrue) || false;
|
|
205
|
+
return value.find((v) => v === this.valueWhenTrue) || false;
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
});
|
|
204
209
|
</script>
|
|
205
210
|
|
|
206
211
|
<template>
|
|
207
|
-
<div
|
|
212
|
+
<div
|
|
213
|
+
class="checkbox-outer-container"
|
|
214
|
+
data-checkbox-ctrl
|
|
215
|
+
>
|
|
208
216
|
<label
|
|
209
217
|
class="checkbox-container"
|
|
210
218
|
:class="{ 'disabled': isDisabled}"
|
|
@@ -214,14 +222,13 @@ export default Vue.extend({
|
|
|
214
222
|
@click="clicked($event)"
|
|
215
223
|
>
|
|
216
224
|
<input
|
|
217
|
-
v-model="value"
|
|
218
225
|
:checked="isChecked"
|
|
219
226
|
:value="valueWhenTrue"
|
|
220
227
|
type="checkbox"
|
|
221
228
|
:tabindex="-1"
|
|
222
229
|
:name="id"
|
|
223
230
|
@click.stop.prevent
|
|
224
|
-
|
|
231
|
+
>
|
|
225
232
|
<span
|
|
226
233
|
class="checkbox-custom"
|
|
227
234
|
:class="{indeterminate: indeterminate}"
|
|
@@ -236,15 +243,33 @@ export default Vue.extend({
|
|
|
236
243
|
:class="{ 'checkbox-primary': primary }"
|
|
237
244
|
>
|
|
238
245
|
<slot name="label">
|
|
239
|
-
<t
|
|
246
|
+
<t
|
|
247
|
+
v-if="labelKey"
|
|
248
|
+
:k="labelKey"
|
|
249
|
+
:raw="true"
|
|
250
|
+
/>
|
|
240
251
|
<template v-else-if="label">{{ label }}</template>
|
|
241
|
-
<i
|
|
242
|
-
|
|
252
|
+
<i
|
|
253
|
+
v-if="tooltipKey"
|
|
254
|
+
v-clean-tooltip="t(tooltipKey)"
|
|
255
|
+
class="checkbox-info icon icon-info icon-lg"
|
|
256
|
+
/>
|
|
257
|
+
<i
|
|
258
|
+
v-else-if="tooltip"
|
|
259
|
+
v-clean-tooltip="tooltip"
|
|
260
|
+
class="checkbox-info icon icon-info icon-lg"
|
|
261
|
+
/>
|
|
243
262
|
</slot>
|
|
244
263
|
</span>
|
|
245
264
|
</label>
|
|
246
|
-
<div
|
|
247
|
-
|
|
265
|
+
<div
|
|
266
|
+
v-if="descriptionKey || description"
|
|
267
|
+
class="checkbox-outer-container-description"
|
|
268
|
+
>
|
|
269
|
+
<t
|
|
270
|
+
v-if="descriptionKey"
|
|
271
|
+
:k="descriptionKey"
|
|
272
|
+
/>
|
|
248
273
|
<template v-else-if="description">
|
|
249
274
|
{{ description }}
|
|
250
275
|
</template>
|
|
@@ -6,15 +6,9 @@ describe('component: LabeledInput', () => {
|
|
|
6
6
|
it('should emit input only once', () => {
|
|
7
7
|
const value = '2';
|
|
8
8
|
const delay = 1;
|
|
9
|
-
const wrapper = mount(LabeledInput, {
|
|
9
|
+
const wrapper = mount(LabeledInput, {
|
|
10
10
|
propsData: { delay },
|
|
11
|
-
mocks:
|
|
12
|
-
$store: {
|
|
13
|
-
getters: {
|
|
14
|
-
'i18n/t': jest.fn()
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
}
|
|
11
|
+
mocks: { $store: { getters: { 'i18n/t': jest.fn() } } }
|
|
18
12
|
});
|
|
19
13
|
|
|
20
14
|
jest.useFakeTimers();
|