@rancher/shell 3.0.12-rc.4 → 3.0.12-rc.6
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/apis/intf/resources-api/resource-base.ts +42 -2
- package/apis/intf/resources-api/resource-instance.ts +101 -0
- package/apis/intf/resources-api/resources-api.ts +177 -23
- package/apis/intf/resources.ts +8 -6
- package/apis/resources/__tests__/resources-api-class.test.ts +309 -28
- package/apis/resources/resources-api-class.ts +232 -43
- package/assets/images/providers/traefik.png +0 -0
- package/assets/styles/global/_button.scss +1 -1
- package/assets/translations/en-us.yaml +49 -14
- package/chart/__tests__/rancher-monitoring-dashboards-index.test.ts +269 -0
- package/chart/rancher-monitoring-dashboards/index.vue +155 -0
- package/cloud-credential/__tests__/generic.test.ts +132 -0
- package/cloud-credential/generic.vue +33 -3
- package/components/ActionDropdownShell.vue +5 -3
- package/components/ButtonGroup.vue +26 -1
- package/components/CruResource.vue +51 -2
- package/components/EtcdInfoBanner.vue +2 -12
- package/components/GrafanaDashboard.vue +2 -8
- package/components/PromptRestore.vue +93 -32
- package/components/Questions/index.vue +1 -0
- package/components/Resource/Detail/Masthead/__tests__/index.test.ts +68 -14
- package/components/Resource/Detail/Masthead/index.vue +8 -3
- 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/Security.vue +1 -1
- 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/formatter/Translate.vue +22 -1
- package/components/nav/Group.vue +5 -10
- package/components/nav/Header.vue +8 -1
- package/components/nav/TopLevelMenu.vue +1 -30
- package/components/nav/__tests__/Group.test.ts +61 -0
- package/components/templates/default.vue +7 -0
- package/config/features.js +1 -0
- package/config/labels-annotations.js +3 -0
- package/config/product/manager.js +6 -0
- package/config/secret.ts +10 -0
- package/config/settings.ts +6 -2
- package/config/table-headers.js +5 -2
- package/config/types.js +7 -0
- package/core/__tests__/plugin-products-apply.test.ts +787 -0
- package/core/__tests__/plugin-products-extend.test.ts +189 -0
- package/core/__tests__/plugin-products-helpers.test.ts +81 -78
- package/core/__tests__/plugin-products-new.test.ts +389 -0
- package/core/__tests__/plugin-products-scenarios.test.ts +980 -0
- package/core/__tests__/plugin-products-side-menu.test.ts +1935 -0
- package/core/plugin-products-base.ts +201 -93
- package/core/plugin-products-extending.ts +1 -1
- package/core/plugin-products-external.ts +380 -0
- package/core/plugin-products-helpers.ts +28 -24
- package/core/plugin-products-internal.ts +207 -0
- package/core/plugin-products-top-level.ts +10 -5
- package/core/plugin-products-type-guards.ts +17 -6
- package/core/plugin-products.ts +5 -7
- package/core/plugin-types.ts +1 -389
- package/core/plugin.ts +10 -11
- package/core/types.ts +6 -202
- package/detail/__tests__/provisioning.cattle.io.cluster.test.ts +52 -0
- package/detail/pod.vue +1 -1
- package/detail/provisioning.cattle.io.cluster.vue +79 -5
- 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/auth/AuthProviderWarningBanners.vue +12 -0
- package/edit/auth/__tests__/AuthProviderWarningBanners.test.ts +10 -1
- package/edit/auth/__tests__/azuread.test.ts +1 -0
- package/edit/auth/__tests__/github.test.ts +1 -0
- package/edit/auth/__tests__/oidc.test.ts +1 -0
- package/edit/auth/__tests__/saml.test.ts +1 -0
- package/edit/auth/ldap/__tests__/index.test.ts +1 -0
- 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/index.vue +1 -1
- package/edit/provisioning.cattle.io.cluster/rke2.vue +6 -2
- package/edit/provisioning.cattle.io.cluster/tabs/etcd/index.vue +0 -1
- package/edit/provisioning.cattle.io.cluster/tabs/networking/index.vue +2 -2
- package/edit/provisioning.cattle.io.cluster/tabs/registries/index.vue +14 -55
- package/list/management.cattle.io.user.vue +24 -5
- package/list/utils/management.cattle.io.cluster.utils.ts +1 -1
- package/machine-config/__tests__/generic.test.ts +163 -0
- package/machine-config/components/EC2Networking.vue +31 -7
- package/machine-config/components/__tests__/EC2Networking.test.ts +38 -15
- package/machine-config/generic.vue +32 -5
- package/mixins/resource-fetch.js +1 -1
- package/models/__tests__/provisioning.cattle.io.cluster.test.ts +156 -0
- package/models/__tests__/rke-machine.cattle.io.vmwarevspheremachinetemplate.test.ts +22 -0
- package/models/__tests__/secret.test.ts +68 -1
- package/models/management.cattle.io.cluster.js +21 -3
- package/models/pod.js +25 -12
- package/models/provisioning.cattle.io.cluster.js +59 -9
- package/models/rke-machine.cattle.io.vmwarevspheremachinetemplate.js +9 -0
- 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/auth/login.vue +1 -1
- package/pages/c/_cluster/apps/charts/__tests__/install.test.ts +569 -81
- package/pages/c/_cluster/apps/charts/install.vue +143 -28
- package/pages/c/_cluster/auth/config/index.vue +11 -4
- package/pages/c/_cluster/monitoring/index.vue +6 -1
- 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 +62 -15
- package/plugins/steve/__tests__/steve-class-resource-api.test.ts +147 -0
- package/plugins/steve/steve-class.js +43 -0
- package/plugins/steve/subscribe.js +11 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.test.ts +115 -0
- package/rancher-components/Form/LabeledInput/LabeledInput.vue +83 -3
- 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__/auth.test.ts +18 -0
- 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__/notifications.test.ts +466 -1
- package/store/__tests__/slideInPanel.test.ts +88 -0
- package/store/__tests__/type-map.utils.test.ts +433 -0
- package/store/__tests__/ui-context.test.ts +255 -0
- package/store/auth.js +11 -1
- package/store/features.js +5 -0
- package/store/plugins.js +6 -0
- package/types/rancher/steve.api.ts +2 -2
- package/types/shell/index.d.ts +120 -0
- package/types/store/dashboard-store.types.ts +2 -2
- package/types/store/type-map.ts +262 -1
- package/utils/__tests__/operation-cr.test.ts +34 -0
- package/utils/async.ts +2 -0
- package/utils/grafana.js +2 -17
- package/utils/monitoring.js +38 -1
- 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
- package/core/__tests__/plugin-products.test.ts +0 -4694
|
@@ -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"
|
|
@@ -1,24 +1,185 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { ref, watch } from 'vue';
|
|
2
|
+
import { ref, watch, onMounted, computed } from 'vue';
|
|
3
|
+
import { useStore } from 'vuex';
|
|
4
|
+
import { useI18n } from '@shell/composables/useI18n';
|
|
3
5
|
import Banner from '@components/Banner/Banner.vue';
|
|
4
6
|
import { Checkbox } from '@components/Form/Checkbox';
|
|
5
7
|
import LabeledInput from '@components/Form/LabeledInput/LabeledInput.vue';
|
|
8
|
+
import SelectOrCreateAuthSecret from '@shell/components/form/SelectOrCreateAuthSecret.vue';
|
|
9
|
+
import { MANAGEMENT } from '@shell/config/types';
|
|
10
|
+
import { SETTING } from '@shell/config/settings';
|
|
11
|
+
import { PRIVATE_REGISTRY_CONTEXT } from '@shell/components/form/PrivateRegistry.constants';
|
|
12
|
+
import type { PrivateRegistryContext } from '@shell/components/form/PrivateRegistry.constants';
|
|
6
13
|
|
|
7
|
-
const props = defineProps<{
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
8
15
|
value?: string | null;
|
|
9
16
|
enabled?: boolean;
|
|
10
17
|
mode?: string;
|
|
11
18
|
rules?: Function[];
|
|
12
19
|
checkboxTestId?: string;
|
|
13
20
|
inputTestId?: string;
|
|
14
|
-
|
|
21
|
+
pullSecret?: string;
|
|
22
|
+
registerBeforeHook?: Function;
|
|
23
|
+
context?: PrivateRegistryContext;
|
|
24
|
+
defaultRegistry?: string;
|
|
25
|
+
namespace?: string;
|
|
26
|
+
inStore?: string;
|
|
27
|
+
showPullSecrets?: boolean;
|
|
28
|
+
noneLabel?: string | null;
|
|
29
|
+
skipPullSecrets?: boolean;
|
|
30
|
+
repoDefaultPullSecrets?: string[];
|
|
31
|
+
existingValuesPullSecrets?: string[];
|
|
32
|
+
}>(), {
|
|
33
|
+
value: undefined,
|
|
34
|
+
enabled: false,
|
|
35
|
+
mode: 'edit',
|
|
36
|
+
rules: () => [],
|
|
37
|
+
checkboxTestId: undefined,
|
|
38
|
+
inputTestId: undefined,
|
|
39
|
+
pullSecret: undefined,
|
|
40
|
+
registerBeforeHook: undefined,
|
|
41
|
+
context: PRIVATE_REGISTRY_CONTEXT.PROVISIONING,
|
|
42
|
+
defaultRegistry: undefined,
|
|
43
|
+
namespace: 'fleet-default',
|
|
44
|
+
inStore: 'management',
|
|
45
|
+
showPullSecrets: true,
|
|
46
|
+
noneLabel: undefined,
|
|
47
|
+
skipPullSecrets: false,
|
|
48
|
+
repoDefaultPullSecrets: () => [],
|
|
49
|
+
existingValuesPullSecrets: () => [],
|
|
50
|
+
});
|
|
15
51
|
|
|
16
52
|
const emit = defineEmits<{
|
|
17
|
-
'update:value': [val: string |
|
|
53
|
+
'update:value': [val: string | undefined];
|
|
18
54
|
'update:enabled': [val: boolean];
|
|
55
|
+
'update:pullSecret': [val: string | undefined];
|
|
56
|
+
'update:skipPullSecrets': [val: boolean];
|
|
19
57
|
}>();
|
|
20
58
|
|
|
59
|
+
const store = useStore();
|
|
60
|
+
const { t } = useI18n(store);
|
|
61
|
+
|
|
21
62
|
const showInput = ref(!!props.value);
|
|
63
|
+
const globalRegistry = ref('');
|
|
64
|
+
const defaultPullSecrets = ref<string[]>([]);
|
|
65
|
+
const localSkipPullSecrets = ref(props.skipPullSecrets);
|
|
66
|
+
|
|
67
|
+
const isCharts = computed(() => props.context === PRIVATE_REGISTRY_CONTEXT.CHARTS);
|
|
68
|
+
const isImporting = computed(() => props.context === PRIVATE_REGISTRY_CONTEXT.IMPORTING);
|
|
69
|
+
|
|
70
|
+
const descriptionKey = computed(() => {
|
|
71
|
+
if (isCharts.value) {
|
|
72
|
+
return 'catalog.chart.registry.tooltip';
|
|
73
|
+
}
|
|
74
|
+
if (isImporting.value) {
|
|
75
|
+
return 'cluster.privateRegistry.importedDescription';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return 'cluster.privateRegistry.description';
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const checkboxLabelKey = computed(() => {
|
|
82
|
+
if (isCharts.value) {
|
|
83
|
+
return 'catalog.chart.registry.custom.checkBoxLabel';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return 'cluster.privateRegistry.label';
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const checkboxTooltipKey = computed(() => {
|
|
90
|
+
if (isCharts.value) {
|
|
91
|
+
return 'catalog.chart.registry.tooltip';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return undefined;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
onMounted(() => {
|
|
98
|
+
if (props.defaultRegistry) {
|
|
99
|
+
globalRegistry.value = props.defaultRegistry;
|
|
100
|
+
} else {
|
|
101
|
+
const registrySetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.SYSTEM_DEFAULT_REGISTRY);
|
|
102
|
+
|
|
103
|
+
globalRegistry.value = registrySetting?.value || registrySetting?.defaultValue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const pullSecretsSetting = store.getters['management/byId'](MANAGEMENT.SETTING, SETTING.SYSTEM_DEFAULT_REGISTRY_PULL_SECRETS);
|
|
107
|
+
|
|
108
|
+
if (props.repoDefaultPullSecrets?.length) {
|
|
109
|
+
defaultPullSecrets.value = props.repoDefaultPullSecrets;
|
|
110
|
+
} else if (pullSecretsSetting?.value ) {
|
|
111
|
+
defaultPullSecrets.value = pullSecretsSetting?.value.split(',').map((s: string) => s.trim());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const hasMultipleDefaultSecrets = computed(() => {
|
|
116
|
+
return defaultPullSecrets.value?.length > 1;
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* On upgrade, if the chart values already contain multiple imagePullSecrets,
|
|
121
|
+
* the dropdown cannot represent them. Show a banner instead and let the
|
|
122
|
+
* user edit the values YAML directly.
|
|
123
|
+
*/
|
|
124
|
+
const hasMultipleExistingPullSecrets = computed(() => {
|
|
125
|
+
return (props.existingValuesPullSecrets?.length ?? 0) > 1;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Format a list of names with commas and "and" before the last item.
|
|
130
|
+
* e.g. ["a"] => "<code>a</code>"
|
|
131
|
+
* ["a", "b"] => "<code>a</code> and <code>b</code>"
|
|
132
|
+
* ["a", "b", "c"] => "<code>a</code>, <code>b</code>, and <code>c</code>"
|
|
133
|
+
*/
|
|
134
|
+
function formatSecretList(names: string[]): string {
|
|
135
|
+
const wrapped = names.map((n) => `<code>${ n }</code>`);
|
|
136
|
+
|
|
137
|
+
const and = t('generic.and');
|
|
138
|
+
|
|
139
|
+
if (wrapped.length <= 1) {
|
|
140
|
+
return wrapped[0] || '';
|
|
141
|
+
}
|
|
142
|
+
if (wrapped.length === 2) {
|
|
143
|
+
return `${ wrapped[0] }${ and }${ wrapped[1] }`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return `${ wrapped.slice(0, -1).join(', ') },${ and }${ wrapped[wrapped.length - 1] }`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* If there is exactly one default pull secret configured, we can show that it is the default in the secret dropdown.
|
|
151
|
+
* If there is more than one, UI can't be certain which secret will be used
|
|
152
|
+
* so a banner listing all secrets is shown instead.
|
|
153
|
+
*/
|
|
154
|
+
const defaultPullSecretLabel = computed(() => {
|
|
155
|
+
if (props.noneLabel) {
|
|
156
|
+
return props.noneLabel;
|
|
157
|
+
}
|
|
158
|
+
if (!defaultPullSecrets.value?.length) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (defaultPullSecrets.value?.length === 1) {
|
|
162
|
+
return t('catalog.chart.registry.pullSecret.defaultLabel', { name: defaultPullSecrets.value[0] });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return t('catalog.chart.registry.pullSecret.defaultLabelGeneric');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const existingValuesBannerHtml = computed(() => {
|
|
169
|
+
if (!props.existingValuesPullSecrets?.length) {
|
|
170
|
+
return '';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return t('catalog.chart.registry.pullSecret.existingValuesBanner', { secrets: formatSecretList(props.existingValuesPullSecrets) }, true);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
const defaultSecretsBannerHtml = computed(() => {
|
|
177
|
+
if (!defaultPullSecrets.value?.length) {
|
|
178
|
+
return '';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return t('catalog.chart.registry.pullSecret.defaultSecretsBanner', { secrets: formatSecretList(defaultPullSecrets.value) }, true);
|
|
182
|
+
});
|
|
22
183
|
|
|
23
184
|
watch(() => props.enabled, (neu) => {
|
|
24
185
|
if (typeof neu === 'boolean' && neu !== showInput.value) {
|
|
@@ -31,7 +192,8 @@ watch(showInput, (neu, old) => {
|
|
|
31
192
|
emit('update:enabled', neu);
|
|
32
193
|
}
|
|
33
194
|
if (!neu && old && props.value) {
|
|
34
|
-
emit('update:value',
|
|
195
|
+
emit('update:value', undefined);
|
|
196
|
+
emit('update:pullSecret', undefined);
|
|
35
197
|
}
|
|
36
198
|
});
|
|
37
199
|
|
|
@@ -40,30 +202,103 @@ watch(() => props.value, (neu) => {
|
|
|
40
202
|
showInput.value = true;
|
|
41
203
|
}
|
|
42
204
|
});
|
|
205
|
+
|
|
206
|
+
watch(() => props.pullSecret, (neu, old) => {
|
|
207
|
+
if (neu && !props.value) {
|
|
208
|
+
emit('update:value', globalRegistry.value);
|
|
209
|
+
}
|
|
210
|
+
if (!neu && old && props.value === globalRegistry.value) {
|
|
211
|
+
emit('update:value', undefined);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
watch(localSkipPullSecrets, (neu) => {
|
|
216
|
+
emit('update:skipPullSecrets', neu);
|
|
217
|
+
if (neu) {
|
|
218
|
+
emit('update:pullSecret', undefined);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
watch(() => props.skipPullSecrets, (neu) => {
|
|
223
|
+
if (neu !== localSkipPullSecrets.value) {
|
|
224
|
+
localSkipPullSecrets.value = neu;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
43
228
|
</script>
|
|
44
229
|
|
|
45
230
|
<template>
|
|
46
231
|
<Banner
|
|
47
232
|
color="info"
|
|
48
233
|
class="mt-0"
|
|
49
|
-
label-key="
|
|
234
|
+
:label-key="descriptionKey"
|
|
50
235
|
/>
|
|
51
236
|
<Checkbox
|
|
52
237
|
v-model:value="showInput"
|
|
53
238
|
class="mb-20"
|
|
54
239
|
:mode="mode"
|
|
55
|
-
:label="t(
|
|
240
|
+
:label="t(checkboxLabelKey)"
|
|
241
|
+
:tooltip="checkboxTooltipKey ? t(checkboxTooltipKey) : undefined"
|
|
56
242
|
:data-testid="checkboxTestId"
|
|
57
243
|
/>
|
|
58
|
-
<
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
244
|
+
<template v-if="showInput">
|
|
245
|
+
<div class="row">
|
|
246
|
+
<div class="col span-6">
|
|
247
|
+
<LabeledInput
|
|
248
|
+
:value="value || globalRegistry"
|
|
249
|
+
:mode="mode"
|
|
250
|
+
:rules="rules"
|
|
251
|
+
:required="!globalRegistry"
|
|
252
|
+
label-key="catalog.chart.registry.custom.inputLabel"
|
|
253
|
+
:data-testid="inputTestId"
|
|
254
|
+
:placeholder="t('catalog.chart.registry.custom.placeholder')"
|
|
255
|
+
@update:value="(val) => emit('update:value', val)"
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
<template v-if="showPullSecrets">
|
|
260
|
+
<Checkbox
|
|
261
|
+
v-if="isCharts"
|
|
262
|
+
v-model:value="localSkipPullSecrets"
|
|
263
|
+
class="mb-10 mt-10"
|
|
264
|
+
:mode="mode"
|
|
265
|
+
:label="t('catalog.chart.registry.pullSecret.skipOption')"
|
|
266
|
+
data-testid="registry-skip-pull-secrets-checkbox"
|
|
267
|
+
/>
|
|
268
|
+
<Banner
|
|
269
|
+
v-if="hasMultipleExistingPullSecrets && !localSkipPullSecrets"
|
|
270
|
+
color="info"
|
|
271
|
+
>
|
|
272
|
+
<span v-clean-html="existingValuesBannerHtml" />
|
|
273
|
+
</Banner>
|
|
274
|
+
<Banner
|
|
275
|
+
v-if="!hasMultipleExistingPullSecrets && hasMultipleDefaultSecrets && !localSkipPullSecrets"
|
|
276
|
+
color="info"
|
|
277
|
+
>
|
|
278
|
+
<span v-clean-html="defaultSecretsBannerHtml" />
|
|
279
|
+
</Banner>
|
|
280
|
+
<div :class="{'col span-6': !isCharts}">
|
|
281
|
+
<SelectOrCreateAuthSecret
|
|
282
|
+
v-if="!localSkipPullSecrets && !hasMultipleExistingPullSecrets"
|
|
283
|
+
:value="pullSecret"
|
|
284
|
+
:namespace="namespace"
|
|
285
|
+
:allow-rke="!isCharts"
|
|
286
|
+
:vertical="!isCharts"
|
|
287
|
+
:in-store="inStore"
|
|
288
|
+
limit-to-namespace
|
|
289
|
+
fixed-image-pull-secret
|
|
290
|
+
:none-label="defaultPullSecretLabel"
|
|
291
|
+
:image-pull-secret-docker-json-url-config="value || globalRegistry"
|
|
292
|
+
:register-before-hook="registerBeforeHook"
|
|
293
|
+
@update:value="(val) => emit('update:pullSecret', val)"
|
|
294
|
+
/>
|
|
295
|
+
</div>
|
|
296
|
+
</template>
|
|
297
|
+
</template>
|
|
69
298
|
</template>
|
|
299
|
+
|
|
300
|
+
<style lang="scss" scoped>
|
|
301
|
+
:deep(.banner__content) {
|
|
302
|
+
display: block;
|
|
303
|
+
}
|
|
304
|
+
</style>
|