@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.5
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/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +39 -10
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/ResourceTable.vue +1 -0
- package/components/SortableTable/index.vue +4 -3
- package/components/Wizard.vue +14 -1
- package/components/__tests__/ButtonGroup.test.ts +56 -0
- package/components/__tests__/PromptRestore.test.ts +169 -19
- package/components/fleet/GitRepoAdvancedTab.vue +1 -0
- package/components/fleet/GitRepoMetadataTab.vue +5 -0
- package/components/fleet/HelmOpAppCoConfigTab.vue +4 -0
- package/components/fleet/HelmOpMetadataTab.vue +5 -0
- package/components/form/FileSelector.vue +39 -1
- package/components/form/PrivateRegistry.constants.ts +7 -0
- package/components/form/PrivateRegistry.vue +253 -18
- package/components/form/SelectOrCreateAuthSecret.vue +140 -17
- package/components/form/__tests__/FileSelector.test.ts +23 -0
- package/components/form/__tests__/PrivateRegistry.test.ts +463 -73
- package/components/form/__tests__/SelectOrCreateAuthSecret.test.ts +122 -0
- package/components/formatter/EtcdSnapshotName.vue +73 -0
- package/components/nav/Header.vue +8 -1
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +2 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/types.js +7 -0
- package/detail/provisioning.cattle.io.cluster.vue +79 -3
- package/dialog/RotateEncryptionKeyDialog.vue +33 -9
- package/dialog/__tests__/RotateEncryptionKeyDialog.test.ts +78 -0
- package/edit/__tests__/fleet.cattle.io.gitrepo.test.ts +92 -0
- package/edit/__tests__/fleet.cattle.io.helmop.test.ts +101 -0
- package/edit/__tests__/management.cattle.io.setting.test.ts +2 -1
- package/edit/compliance.cattle.io.clusterscanprofile.vue +39 -41
- package/edit/fleet.cattle.io.gitrepo.vue +70 -16
- package/edit/fleet.cattle.io.helmop.vue +51 -5
- package/edit/helm.cattle.io.projecthelmchart.vue +1 -0
- package/edit/{management.cattle.io.setting.vue → management.cattle.io.setting/index.vue} +32 -9
- package/edit/management.cattle.io.setting/system-default-registry-pull-secrets.vue +81 -0
- package/edit/provisioning.cattle.io.cluster/SelectCredential.vue +3 -12
- package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +18 -0
- package/edit/provisioning.cattle.io.cluster/rke2.vue +5 -1
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +13 -2
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke.cattle.io.etcdsnapshot.js +17 -9
- package/models/secret.js +19 -0
- package/models/workload.js +12 -7
- package/package.json +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +485 -107
- package/pages/c/_cluster/apps/charts/install.vue +114 -28
- package/pkg/require-asset.lib.js +25 -0
- package/pkg/vue.config.js +7 -0
- package/plugins/dashboard-store/__tests__/resource-class.test.ts +84 -0
- package/plugins/dashboard-store/getters.js +0 -1
- package/plugins/dashboard-store/resource-class.js +52 -12
- package/rancher-components/Form/TextArea/TextAreaAutoGrow.vue +30 -0
- package/rancher-components/Form/TextArea/__tests__/TextAreaAutoGrow.test.ts +95 -0
- package/rancher-components/RcButton/index.ts +1 -1
- package/rancher-components/RcDropdown/RcDropdownTrigger.vue +6 -1
- package/store/__tests__/features.test.ts +131 -0
- package/store/__tests__/growl.test.ts +374 -0
- package/store/__tests__/modal.test.ts +131 -0
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/features.js +4 -0
- package/types/shell/index.d.ts +62 -0
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/operation-cr.js +19 -0
- package/utils/require-asset.ts +7 -0
- package/utils/validators/__tests__/private-registry.test.ts +27 -15
- package/utils/validators/private-registry.ts +15 -4
|
@@ -118,4 +118,60 @@ describe('component: ButtonGroup', () => {
|
|
|
118
118
|
expect(wrapper.emitted('update:value')).toHaveLength(1);
|
|
119
119
|
expect(wrapper.emitted('update:value')![0][0]).toBe('val1');
|
|
120
120
|
});
|
|
121
|
+
|
|
122
|
+
it.each([
|
|
123
|
+
['small', 'btn-sm'],
|
|
124
|
+
['medium', 'btn-md'],
|
|
125
|
+
])('should apply the size class to each button when size is %s', (size, sizeClass) => {
|
|
126
|
+
const options = [
|
|
127
|
+
{
|
|
128
|
+
label: 'label1',
|
|
129
|
+
value: 'val1'
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
label: 'label2',
|
|
133
|
+
value: 'val2'
|
|
134
|
+
},
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
const wrapper = shallowMount(ButtonGroup, {
|
|
138
|
+
props: {
|
|
139
|
+
options,
|
|
140
|
+
value: 'val1',
|
|
141
|
+
size
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const buttons = wrapper.findAll('button');
|
|
146
|
+
|
|
147
|
+
expect(buttons).toHaveLength(2);
|
|
148
|
+
buttons.forEach((button) => {
|
|
149
|
+
expect(button.classes()).toContain(sizeClass);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it.each([
|
|
154
|
+
[undefined],
|
|
155
|
+
['large'],
|
|
156
|
+
])('should not apply a size class when size is %s', (size) => {
|
|
157
|
+
const options = [
|
|
158
|
+
{
|
|
159
|
+
label: 'label1',
|
|
160
|
+
value: 'val1'
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
const wrapper = shallowMount(ButtonGroup, {
|
|
165
|
+
props: {
|
|
166
|
+
options,
|
|
167
|
+
value: 'val1',
|
|
168
|
+
...(size ? { size } : {})
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const button = wrapper.find('button');
|
|
173
|
+
|
|
174
|
+
expect(button.classes()).not.toContain('btn-sm');
|
|
175
|
+
expect(button.classes()).not.toContain('btn-md');
|
|
176
|
+
});
|
|
121
177
|
});
|
|
@@ -4,36 +4,46 @@ import PromptRestore from '@shell/components/PromptRestore.vue';
|
|
|
4
4
|
import { createStore } from 'vuex';
|
|
5
5
|
import { ExtendedVue, Vue } from 'vue/types/vue';
|
|
6
6
|
import { DefaultProps } from 'vue/types/options';
|
|
7
|
-
import { CAPI } from '@shell/config/types';
|
|
7
|
+
import { CAPI, MANAGEMENT, OPERATION, SNAPSHOT } from '@shell/config/types';
|
|
8
8
|
import { STATES_ENUM } from '@shell/plugins/dashboard-store/resource-class';
|
|
9
|
+
import { createOperationCR } from '@shell/utils/operation-cr';
|
|
10
|
+
|
|
11
|
+
jest.mock('@shell/utils/operation-cr', () => ({ createOperationCR: jest.fn() }));
|
|
9
12
|
|
|
10
13
|
const RKE2_CLUSTER_NAME = 'rke2_cluster_name';
|
|
11
14
|
const RKE2_SUCCESSFUL_SNAPSHOT_1 = {
|
|
12
|
-
clusterName:
|
|
13
|
-
type:
|
|
14
|
-
created:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
clusterName: RKE2_CLUSTER_NAME,
|
|
16
|
+
type: CAPI.RANCHER_CLUSTER,
|
|
17
|
+
created: 'Thu Jul 20 2023 11:11:39',
|
|
18
|
+
restoreEnabled: true,
|
|
19
|
+
snapshotFile: { status: STATES_ENUM.SUCCESSFUL },
|
|
20
|
+
id: 'rke2_id_1',
|
|
21
|
+
name: 'rke2_name_1'
|
|
18
22
|
};
|
|
19
23
|
const RKE2_SUCCESSFUL_SNAPSHOT_2 = {
|
|
20
|
-
clusterName:
|
|
21
|
-
type:
|
|
22
|
-
created:
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
clusterName: RKE2_CLUSTER_NAME,
|
|
25
|
+
type: CAPI.RANCHER_CLUSTER,
|
|
26
|
+
created: 'Thu Jul 20 2022 11:11:39',
|
|
27
|
+
restoreEnabled: true,
|
|
28
|
+
snapshotFile: { status: STATES_ENUM.SUCCESSFUL },
|
|
29
|
+
id: 'rke2_id_2',
|
|
30
|
+
name: 'rke2_name_2'
|
|
26
31
|
};
|
|
27
32
|
const RKE2_FAILED_SNAPSHOT = {
|
|
28
|
-
clusterName:
|
|
29
|
-
type:
|
|
30
|
-
created:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
clusterName: RKE2_CLUSTER_NAME,
|
|
34
|
+
type: CAPI.RANCHER_CLUSTER,
|
|
35
|
+
created: 'Thu Jul 20 2021 11:11:39',
|
|
36
|
+
restoreEnabled: false,
|
|
37
|
+
snapshotFile: { status: STATES_ENUM.FAILED },
|
|
38
|
+
id: 'rke2_id_3',
|
|
39
|
+
name: 'rke2_name_3'
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
describe('component: PromptRestore', () => {
|
|
43
|
+
beforeEach(() => {
|
|
44
|
+
jest.clearAllMocks();
|
|
45
|
+
});
|
|
46
|
+
|
|
37
47
|
const rke2TestCases = [
|
|
38
48
|
[[], 0],
|
|
39
49
|
[[RKE2_FAILED_SNAPSHOT], 0],
|
|
@@ -72,4 +82,144 @@ describe('component: PromptRestore', () => {
|
|
|
72
82
|
|
|
73
83
|
expect(wrapper.vm.clusterSnapshots).toHaveLength(expected);
|
|
74
84
|
});
|
|
85
|
+
|
|
86
|
+
it('should restore imported cluster via operation CR', async() => {
|
|
87
|
+
(createOperationCR as jest.Mock).mockResolvedValue(undefined);
|
|
88
|
+
const clusterSave = jest.fn();
|
|
89
|
+
const buttonDone = jest.fn();
|
|
90
|
+
|
|
91
|
+
const importedCluster = {
|
|
92
|
+
isImported: true,
|
|
93
|
+
isImportedWithDayTwoOps: true,
|
|
94
|
+
type: CAPI.RANCHER_CLUSTER,
|
|
95
|
+
metadata: { name: 'imported-cluster' },
|
|
96
|
+
mgmt: { id: 'c-m-imported' },
|
|
97
|
+
save: clusterSave,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const getters: any = {};
|
|
101
|
+
|
|
102
|
+
getters['i18n/t'] = () => (key: string) => key;
|
|
103
|
+
|
|
104
|
+
const store = createStore({
|
|
105
|
+
modules: {
|
|
106
|
+
'action-menu': {
|
|
107
|
+
namespaced: true,
|
|
108
|
+
state: {
|
|
109
|
+
showPromptRestore: true,
|
|
110
|
+
toRestore: [importedCluster]
|
|
111
|
+
},
|
|
112
|
+
mutations: { togglePromptRestore: jest.fn() }
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
getters,
|
|
116
|
+
actions: {
|
|
117
|
+
'management/findAll': jest.fn().mockResolvedValue([]),
|
|
118
|
+
'growl/success': jest.fn(),
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const wrapper = shallowMount(
|
|
123
|
+
PromptRestore as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>,
|
|
124
|
+
{ global: { mocks: { $store: store } } }
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
wrapper.vm.allSnapshots = {
|
|
128
|
+
'snapshot-1': {
|
|
129
|
+
name: 'snapshot-1',
|
|
130
|
+
snapshotFile: { name: 'snapshot-file-1' }
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
wrapper.vm.selectedSnapshot = 'snapshot-1';
|
|
134
|
+
|
|
135
|
+
await wrapper.vm.apply(buttonDone);
|
|
136
|
+
|
|
137
|
+
expect(createOperationCR).toHaveBeenCalledTimes(1);
|
|
138
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][1]).toBe(OPERATION.ETCD_SNAPSHOT_RESTORE);
|
|
139
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][2]).toStrictEqual({
|
|
140
|
+
clusterRef: {
|
|
141
|
+
apiVersion: 'management.cattle.io/v3',
|
|
142
|
+
kind: 'Cluster',
|
|
143
|
+
name: 'c-m-imported',
|
|
144
|
+
},
|
|
145
|
+
args: { name: 'snapshot-file-1' },
|
|
146
|
+
});
|
|
147
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][3]).toBe('c-m-imported');
|
|
148
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][4]).toBe('c-m-imported');
|
|
149
|
+
expect(clusterSave).not.toHaveBeenCalled();
|
|
150
|
+
expect(buttonDone).toHaveBeenCalledWith(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should restore imported snapshot by resolving target cluster from store', async() => {
|
|
154
|
+
(createOperationCR as jest.Mock).mockResolvedValue(undefined);
|
|
155
|
+
const buttonDone = jest.fn();
|
|
156
|
+
const byId = jest.fn();
|
|
157
|
+
|
|
158
|
+
const importedCluster = {
|
|
159
|
+
id: 'fleet-default/imported-cluster',
|
|
160
|
+
isImportedWithDayTwoOps: true,
|
|
161
|
+
mgmt: { id: 'c-m-imported' },
|
|
162
|
+
isImported: true,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
byId.mockImplementation((type: string, id: string) => {
|
|
166
|
+
if (type === CAPI.RANCHER_CLUSTER && id === 'fleet-default/imported-cluster') {
|
|
167
|
+
return importedCluster;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (type === MANAGEMENT.CLUSTER && id === 'c-m-imported') {
|
|
171
|
+
return importedCluster.mgmt;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const getters: any = {};
|
|
178
|
+
|
|
179
|
+
getters['i18n/t'] = () => (key: string) => key;
|
|
180
|
+
getters['management/byId'] = () => byId;
|
|
181
|
+
|
|
182
|
+
const store = createStore({
|
|
183
|
+
modules: {
|
|
184
|
+
'action-menu': {
|
|
185
|
+
namespaced: true,
|
|
186
|
+
state: {
|
|
187
|
+
showPromptRestore: true,
|
|
188
|
+
toRestore: [{
|
|
189
|
+
type: SNAPSHOT,
|
|
190
|
+
metadata: { namespace: 'fleet-default' },
|
|
191
|
+
spec: { clusterName: 'imported-cluster', clusterRef: { name: 'c-m-imported' } },
|
|
192
|
+
snapshotFile: { name: 'snapshot-file-2' },
|
|
193
|
+
nameDisplay: 'snapshot-2',
|
|
194
|
+
}]
|
|
195
|
+
},
|
|
196
|
+
mutations: { togglePromptRestore: jest.fn() }
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
getters,
|
|
200
|
+
actions: { 'growl/success': jest.fn() }
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const wrapper = shallowMount(
|
|
204
|
+
PromptRestore as unknown as ExtendedVue<Vue, {}, {}, {}, DefaultProps>,
|
|
205
|
+
{ global: { mocks: { $store: store } } }
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
await wrapper.vm.apply(buttonDone);
|
|
209
|
+
|
|
210
|
+
expect(createOperationCR).toHaveBeenCalledTimes(1);
|
|
211
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][1]).toBe(OPERATION.ETCD_SNAPSHOT_RESTORE);
|
|
212
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][2]).toStrictEqual({
|
|
213
|
+
clusterRef: {
|
|
214
|
+
apiVersion: 'management.cattle.io/v3',
|
|
215
|
+
kind: 'Cluster',
|
|
216
|
+
name: 'c-m-imported',
|
|
217
|
+
},
|
|
218
|
+
args: { name: 'snapshot-file-2' },
|
|
219
|
+
});
|
|
220
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][3]).toBe('c-m-imported');
|
|
221
|
+
expect((createOperationCR as jest.Mock).mock.calls[0][4]).toBe('c-m-imported');
|
|
222
|
+
expect(buttonDone).toHaveBeenCalledWith(true);
|
|
223
|
+
expect(byId).toHaveBeenCalledWith(CAPI.RANCHER_CLUSTER, 'fleet-default/imported-cluster');
|
|
224
|
+
});
|
|
75
225
|
});
|
|
@@ -157,6 +157,7 @@ const validatePollingInterval = () => {
|
|
|
157
157
|
label-key="fleet.gitRepo.auth.git"
|
|
158
158
|
:cache-secrets="true"
|
|
159
159
|
:show-ssh-known-hosts="true"
|
|
160
|
+
:allow-github-app="true"
|
|
160
161
|
:is-github-dot-com-repository="isGithubDotComRepository"
|
|
161
162
|
@update:value="updateAuth($event, 'clientSecretName')"
|
|
162
163
|
@inputauthval="updateCachedAuthVal($event, 'clientSecretName')"
|
|
@@ -14,6 +14,10 @@ defineProps({
|
|
|
14
14
|
isView: {
|
|
15
15
|
type: Boolean,
|
|
16
16
|
default: false
|
|
17
|
+
},
|
|
18
|
+
nameRules: {
|
|
19
|
+
type: Array,
|
|
20
|
+
default: () => []
|
|
17
21
|
}
|
|
18
22
|
});
|
|
19
23
|
|
|
@@ -31,6 +35,7 @@ const updateValue = (event) => {
|
|
|
31
35
|
:value="value"
|
|
32
36
|
:namespaced="false"
|
|
33
37
|
:mode="mode"
|
|
38
|
+
:rules="{ name: nameRules }"
|
|
34
39
|
@update:value="updateValue"
|
|
35
40
|
/>
|
|
36
41
|
<Labels
|
|
@@ -50,6 +50,7 @@ const props = withDefaults(defineProps<{
|
|
|
50
50
|
hideTarget?: boolean;
|
|
51
51
|
hideAdvanced?: boolean;
|
|
52
52
|
hideChartConfig?: boolean;
|
|
53
|
+
nameRules?: ((val: any) => string | undefined)[];
|
|
53
54
|
}>(), {
|
|
54
55
|
appCoChartEntries: () => ({} as Record<string, ChartEntry[]>),
|
|
55
56
|
appCoChartsLoading: false,
|
|
@@ -69,6 +70,7 @@ const props = withDefaults(defineProps<{
|
|
|
69
70
|
hideTarget: false,
|
|
70
71
|
hideAdvanced: false,
|
|
71
72
|
hideChartConfig: false,
|
|
73
|
+
nameRules: () => [],
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
// eslint-disable-next-line func-call-spacing
|
|
@@ -309,6 +311,7 @@ defineExpose({ refreshYamlEditor });
|
|
|
309
311
|
:mode="mode"
|
|
310
312
|
:name-label="'fleet.helmOp.appCoConfig.name'"
|
|
311
313
|
:no-bottom-margin="true"
|
|
314
|
+
:rules="{ name: nameRules }"
|
|
312
315
|
data-testid="appco-config-name-ns-description"
|
|
313
316
|
@update:value="emit('update:value', $event)"
|
|
314
317
|
/>
|
|
@@ -354,6 +357,7 @@ defineExpose({ refreshYamlEditor });
|
|
|
354
357
|
:namespaced="false"
|
|
355
358
|
:mode="mode"
|
|
356
359
|
:name-label="'fleet.helmOp.appCoConfig.name'"
|
|
360
|
+
:rules="{ name: nameRules }"
|
|
357
361
|
data-testid="appco-config-name-ns-description"
|
|
358
362
|
@update:value="emit('update:value', $event)"
|
|
359
363
|
/>
|
|
@@ -14,6 +14,10 @@ defineProps({
|
|
|
14
14
|
isView: {
|
|
15
15
|
type: Boolean,
|
|
16
16
|
default: false
|
|
17
|
+
},
|
|
18
|
+
nameRules: {
|
|
19
|
+
type: Array,
|
|
20
|
+
default: () => []
|
|
17
21
|
}
|
|
18
22
|
});
|
|
19
23
|
|
|
@@ -31,6 +35,7 @@ const updateValue = (value) => {
|
|
|
31
35
|
:value="value"
|
|
32
36
|
:namespaced="false"
|
|
33
37
|
:mode="mode"
|
|
38
|
+
:rules="{ name: nameRules }"
|
|
34
39
|
@update:value="updateValue"
|
|
35
40
|
/>
|
|
36
41
|
<Labels
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import { _EDIT, _VIEW } from '@shell/config/query-params';
|
|
3
3
|
import { set } from '@shell/utils/object';
|
|
4
|
+
import { RcButton } from '@components/RcButton';
|
|
4
5
|
|
|
5
6
|
export function createOnSelected(field) {
|
|
6
7
|
return function(contents) {
|
|
@@ -11,6 +12,8 @@ export function createOnSelected(field) {
|
|
|
11
12
|
export default {
|
|
12
13
|
emits: ['error', 'selected'],
|
|
13
14
|
|
|
15
|
+
components: { RcButton },
|
|
16
|
+
|
|
14
17
|
props: {
|
|
15
18
|
label: {
|
|
16
19
|
type: String,
|
|
@@ -70,6 +73,15 @@ export default {
|
|
|
70
73
|
class: {
|
|
71
74
|
type: [String, Array],
|
|
72
75
|
default: () => [],
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Render the trigger as a small, secondary RcButton instead of the default
|
|
80
|
+
* plain button.
|
|
81
|
+
*/
|
|
82
|
+
asRcButton: {
|
|
83
|
+
type: Boolean,
|
|
84
|
+
default: false,
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
},
|
|
@@ -82,6 +94,11 @@ export default {
|
|
|
82
94
|
customClass() {
|
|
83
95
|
return ['file-selector', 'btn', ...(Array.isArray(this.class) ? this.class : [this.class])];
|
|
84
96
|
},
|
|
97
|
+
|
|
98
|
+
// RcButton provides its own `btn` class, so we omit it here to avoid clashing styles
|
|
99
|
+
rcButtonClass() {
|
|
100
|
+
return ['file-selector', ...(Array.isArray(this.class) ? this.class : [this.class])];
|
|
101
|
+
},
|
|
85
102
|
},
|
|
86
103
|
|
|
87
104
|
methods: {
|
|
@@ -154,8 +171,29 @@ export default {
|
|
|
154
171
|
</script>
|
|
155
172
|
|
|
156
173
|
<template>
|
|
174
|
+
<RcButton
|
|
175
|
+
v-if="!isView && asRcButton"
|
|
176
|
+
variant="secondary"
|
|
177
|
+
size="small"
|
|
178
|
+
:disabled="disabled"
|
|
179
|
+
:aria-label="label"
|
|
180
|
+
:class="rcButtonClass"
|
|
181
|
+
data-testid="file-selector__uploader-button"
|
|
182
|
+
@click="selectFile"
|
|
183
|
+
>
|
|
184
|
+
<span>{{ label }}</span>
|
|
185
|
+
<input
|
|
186
|
+
ref="uploader"
|
|
187
|
+
type="file"
|
|
188
|
+
class="hide"
|
|
189
|
+
:multiple="multiple"
|
|
190
|
+
:webkitdirectory="directory"
|
|
191
|
+
:accept="accept"
|
|
192
|
+
@change="fileChange"
|
|
193
|
+
>
|
|
194
|
+
</RcButton>
|
|
157
195
|
<button
|
|
158
|
-
v-if="!isView"
|
|
196
|
+
v-else-if="!isView"
|
|
159
197
|
:disabled="disabled"
|
|
160
198
|
:aria-label="label"
|
|
161
199
|
type="button"
|